diff options
52 files changed, 10431 insertions, 9281 deletions
diff --git a/.gitattributes b/.gitattributes index 3408438cb26..9bb4e97d4ae 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1794,26 +1794,34 @@ src/lib/softfloat/softfloat.c svneol=native#text/plain src/lib/softfloat/softfloat.h svneol=native#text/plain src/lib/util/astring.c svneol=native#text/plain src/lib/util/astring.h svneol=native#text/plain -src/lib/util/avcomp.c svneol=native#text/plain -src/lib/util/avcomp.h svneol=native#text/plain +src/lib/util/avhuff.c svneol=native#text/plain +src/lib/util/avhuff.h svneol=native#text/plain src/lib/util/aviio.c svneol=native#text/plain src/lib/util/aviio.h svneol=native#text/plain src/lib/util/bitmap.c svneol=native#text/plain src/lib/util/bitmap.h svneol=native#text/plain +src/lib/util/bitstream.h svneol=native#text/plain src/lib/util/cdrom.c svneol=native#text/plain src/lib/util/cdrom.h svneol=native#text/plain src/lib/util/chd.c svneol=native#text/plain src/lib/util/chd.h svneol=native#text/plain src/lib/util/chdcd.c svneol=native#text/plain src/lib/util/chdcd.h svneol=native#text/plain +src/lib/util/chdcodec.c svneol=native#text/plain +src/lib/util/chdcodec.h svneol=native#text/plain src/lib/util/corefile.c svneol=native#text/plain src/lib/util/corefile.h svneol=native#text/plain src/lib/util/corestr.c svneol=native#text/plain src/lib/util/corestr.h svneol=native#text/plain +src/lib/util/coretmpl.h svneol=native#text/plain src/lib/util/coreutil.c svneol=native#text/plain src/lib/util/coreutil.h svneol=native#text/plain +src/lib/util/flac.c svneol=native#text/plain +src/lib/util/flac.h svneol=native#text/plain src/lib/util/harddisk.c svneol=native#text/plain src/lib/util/harddisk.h svneol=native#text/plain +src/lib/util/hashing.c svneol=native#text/plain +src/lib/util/hashing.h svneol=native#text/plain src/lib/util/huffman.c svneol=native#text/plain src/lib/util/huffman.h svneol=native#text/plain src/lib/util/jedparse.c svneol=native#text/plain @@ -733,10 +733,8 @@ $(sort $(OBJDIRS)): ifndef EXECUTABLE_DEFINED -# always recompile the version string -$(VERSIONOBJ): $(DRVLIBS) $(LIBOSD) $(LIBCPU) $(LIBEMU) $(LIBSOUND) $(LIBUTIL) $(EXPAT) $(ZLIB) $(7Z_LIB) $(SOFTFLOAT) $(FORMATS_LIB) $(LIBOCORE) $(RESFILE) - $(EMULATOR): $(VERSIONOBJ) $(EMUINFOOBJ) $(DRIVLISTOBJ) $(DEVLISTOBJ) $(DRVLIBS) $(LIBOSD) $(LIBCPU) $(LIBEMU) $(LIBDASM) $(LIBSOUND) $(LIBUTIL) $(EXPAT) $(SOFTFLOAT) $(JPEG_LIB) $(FLAC_LIB) $(7Z_LIB) $(FORMATS_LIB) $(ZLIB) $(LIBOCORE) $(RESFILE) + $(CC) $(CDEFS) $(CFLAGS) -c $(SRC)/version.c -o $(VERSIONOBJ) @echo Linking $@... $(LD) $(LDFLAGS) $(LDFLAGSEMULATOR) $^ $(LIBS) -o $@ ifeq ($(TARGETOS),win32) diff --git a/src/emu/audit.c b/src/emu/audit.c index 7085ea1d6b3..42e2ed77060 100644 --- a/src/emu/audit.c +++ b/src/emu/audit.c @@ -436,27 +436,20 @@ audit_record *media_auditor::audit_one_disk(const rom_entry *rom) audit_record &record = m_record_list.append(*global_alloc(audit_record(*rom, audit_record::MEDIA_DISK))); // open the disk - emu_file *source_file; - chd_file *source; - chd_error err = open_disk_image(m_enumerator.options(), &m_enumerator.driver(), rom, &source_file, &source, NULL); + chd_file source; + chd_error err = chd_error(open_disk_image(m_enumerator.options(), &m_enumerator.driver(), rom, source, NULL)); // if we succeeded, get the hashes if (err == CHDERR_NONE) { - static const UINT8 nullhash[20] = { 0 }; - chd_header header = *chd_get_header(source); hash_collection hashes; // if there's a SHA1 hash, add them to the output hash - if (memcmp(nullhash, header.sha1, sizeof(header.sha1)) != 0) - hashes.add_from_buffer(hash_collection::HASH_SHA1, header.sha1, sizeof(header.sha1)); + if (source.sha1() != sha1_t::null) + hashes.add_from_buffer(hash_collection::HASH_SHA1, source.sha1().m_raw, sizeof(source.sha1().m_raw)); // update the actual values record.set_actual(hashes); - - // close the file and release the source - chd_close(source); - global_free(source_file); } // compute the final status diff --git a/src/emu/clifront.c b/src/emu/clifront.c index 6b623b320cd..a893197d222 100644 --- a/src/emu/clifront.c +++ b/src/emu/clifront.c @@ -38,6 +38,7 @@ ***************************************************************************/ #include "emu.h" +#include "chd.h" #include "emuopts.h" #include "jedparse.h" #include "audit.h" @@ -1492,8 +1493,8 @@ void media_identifier::identify_file(const char *name) m_total++; // attempt to open as a CHD; fail if not - chd_file *chd; - chd_error err = chd_open(name, CHD_OPEN_READ, NULL, &chd); + chd_file chd; + chd_error err = chd.open(name); if (err != CHDERR_NONE) { mame_printf_info("NOT A CHD\n"); @@ -1501,26 +1502,20 @@ void media_identifier::identify_file(const char *name) return; } - // fetch the header and close the file - chd_header header = *chd_get_header(chd); - chd_close(chd); - // error on writable CHDs - if (header.flags & CHDFLAGS_IS_WRITEABLE) + if (!chd.compressed()) { mame_printf_info("is a writeable CHD\n"); return; } // otherwise, get the hash collection for this CHD - static const UINT8 nullhash[20] = { 0 }; hash_collection hashes; - - if (memcmp(nullhash, header.sha1, sizeof(header.sha1)) != 0) - hashes.add_from_buffer(hash_collection::HASH_SHA1, header.sha1, sizeof(header.sha1)); + if (chd.sha1() != sha1_t::null) + hashes.add_from_buffer(hash_collection::HASH_SHA1, chd.sha1().m_raw, sizeof(chd.sha1().m_raw)); // determine whether this file exists - int found = find_by_hash(hashes, header.logicalbytes); + int found = find_by_hash(hashes, chd.logical_bytes()); if (found == 0) mame_printf_info("NO MATCH\n"); else diff --git a/src/emu/emu.h b/src/emu/emu.h index 2051f54af89..1d9335dc374 100644 --- a/src/emu/emu.h +++ b/src/emu/emu.h @@ -59,7 +59,6 @@ #include "profiler.h" // commonly-referenecd utilities imported from lib/util -#include "chd.h" #include "palette.h" #include "unicode.h" diff --git a/src/emu/imagedev/chd_cd.c b/src/emu/imagedev/chd_cd.c index dea7020cf1f..6680929530c 100644 --- a/src/emu/imagedev/chd_cd.c +++ b/src/emu/imagedev/chd_cd.c @@ -129,10 +129,11 @@ bool cdrom_image_device::call_load() if (software_entry() == NULL) { - if (strstr(m_image_name,".chd")) { - err = chd_open_file( image_core_file(), CHD_OPEN_READ, NULL, &chd ); /* CDs are never writeable */ + if (strstr(m_image_name,".chd") && is_loaded()) { + err = m_self_chd.open( *image_core_file() ); /* CDs are never writeable */ if ( err ) goto error; + chd = &m_self_chd; } } else { chd = get_disk_handle(device().machine(), device().subtag(tempstring,"cdrom")); @@ -150,8 +151,8 @@ bool cdrom_image_device::call_load() return IMAGE_INIT_PASS; error: - if ( chd ) - chd_close( chd ); + if ( chd && chd == &m_self_chd ) + m_self_chd.close( ); if ( err ) seterror( IMAGE_ERROR_UNSPECIFIED, chd_get_error_string( err ) ); return IMAGE_INIT_FAIL; diff --git a/src/emu/imagedev/chd_cd.h b/src/emu/imagedev/chd_cd.h index fecb1f7e6b0..f01ec4b1f98 100644 --- a/src/emu/imagedev/chd_cd.h +++ b/src/emu/imagedev/chd_cd.h @@ -57,6 +57,7 @@ protected: virtual void device_config_complete(); virtual void device_start(); + chd_file m_self_chd; cdrom_file *m_cdrom_handle; image_device_format m_format; }; diff --git a/src/emu/imagedev/harddriv.c b/src/emu/imagedev/harddriv.c index b6026f6c788..fe06cfdfb5f 100644 --- a/src/emu/imagedev/harddriv.c +++ b/src/emu/imagedev/harddriv.c @@ -146,7 +146,7 @@ bool harddisk_image_device::call_load() { int our_result; - our_result = internal_load_hd(NULL); + our_result = internal_load_hd(); /* Check if there is an image_load callback defined */ if ( m_device_image_load ) @@ -161,9 +161,9 @@ bool harddisk_image_device::call_load() bool harddisk_image_device::call_create(int create_format, option_resolution *create_args) { int err; - char metadata[256]; UINT32 sectorsize, hunksize; UINT32 cylinders, heads, sectors, totalsectors; + astring metadata; cylinders = option_resolution_lookup_int(create_args, 'C'); heads = option_resolution_lookup_int(create_args, 'H'); @@ -174,12 +174,20 @@ bool harddisk_image_device::call_create(int create_format, option_resolution *cr totalsectors = cylinders * heads * sectors; /* create the CHD file */ - err = chd_create_file(image_core_file(), (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_NONE, NULL); + chd_codec_type compression[4] = { CHD_CODEC_NONE }; + err = m_self_chd.create(*image_core_file(), (UINT64)totalsectors * (UINT64)sectorsize, hunksize, sectorsize, compression); if (err != CHDERR_NONE) goto error; - sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); - return internal_load_hd(metadata); + /* if we created the image and hence, have metadata to set, set the metadata */ + metadata.format(HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); + err = m_self_chd.write_metadata(HARD_DISK_METADATA_TAG, 0, metadata); + m_self_chd.close(); + + if (err != CHDERR_NONE) + goto error; + + return internal_load_hd(); error: return IMAGE_INIT_FAIL; @@ -201,12 +209,13 @@ void harddisk_image_device::call_unload() if (m_chd != NULL) { - chd_close(m_chd); + if (m_self_chd.opened()) + m_self_chd.close(); m_chd = NULL; } } -int harddisk_image_device::internal_load_hd(const char *metadata) +int harddisk_image_device::internal_load_hd() { chd_error err = (chd_error)0; int is_writeable; @@ -220,8 +229,9 @@ int harddisk_image_device::internal_load_hd(const char *metadata) do { is_writeable = !is_readonly(); - m_chd = NULL; - err = chd_open_file(image_core_file(), is_writeable ? CHD_OPEN_READWRITE : CHD_OPEN_READ, NULL, &m_chd); + err = m_self_chd.open(*image_core_file(), is_writeable); + if (err == CHDERR_NONE) + m_chd = &m_self_chd; /* special case; if we get CHDERR_FILE_NOT_WRITEABLE, make the * image read only and repeat */ @@ -233,14 +243,6 @@ int harddisk_image_device::internal_load_hd(const char *metadata) if (!m_chd) goto done; - /* if we created the image and hence, have metadata to set, set the metadata */ - if (metadata) - { - err = chd_set_metadata(m_chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, 0); - if (err != CHDERR_NONE) - goto done; - } - /* open the hard disk file */ m_hard_disk_handle = hard_disk_open(m_chd); if (!m_hard_disk_handle) @@ -252,7 +254,8 @@ done: /* if we had an error, close out the CHD */ if (m_chd != NULL) { - chd_close(m_chd); + if (m_self_chd.opened()) + m_self_chd.close(); m_chd = NULL; } diff --git a/src/emu/imagedev/harddriv.h b/src/emu/imagedev/harddriv.h index 0bf0053f484..5f6a88cf572 100644 --- a/src/emu/imagedev/harddriv.h +++ b/src/emu/imagedev/harddriv.h @@ -64,9 +64,10 @@ protected: virtual void device_config_complete(); virtual void device_start(); - int internal_load_hd(const char *metadata); + int internal_load_hd(); chd_file *m_chd; + chd_file m_self_chd; hard_disk_file *m_hard_disk_handle; image_device_format m_format; diff --git a/src/emu/machine/idectrl.c b/src/emu/machine/idectrl.c index d3399258a1a..52cc6251b4d 100644 --- a/src/emu/machine/idectrl.c +++ b/src/emu/machine/idectrl.c @@ -1476,7 +1476,7 @@ static void ide_controller_write(device_t *device, int bank, offs_t offset, int } else if (ide->command == IDE_COMMAND_TAITO_GNET_UNLOCK_2) { - UINT8 key[5] = { 0, 0, 0, 0, 0 }; + UINT8 key[5] = { 0 }; int i, bad = 0; ide->drive[ide->cur_drive].slot->read_key(key); @@ -2073,7 +2073,8 @@ void ide_hdd_device::device_reset() mame_printf_debug("CHS: %d %d %d\n", m_num_cylinders, m_num_heads, m_num_sectors); } // build the features page - if (chd_get_metadata (m_handle, HARD_DISK_IDENT_METADATA_TAG, 0, m_features, IDE_DISK_SECTOR_SIZE, 0, 0, 0) != CHDERR_NONE) + UINT32 metalength; + if (m_handle->read_metadata (HARD_DISK_IDENT_METADATA_TAG, 0, m_features, IDE_DISK_SECTOR_SIZE, metalength) != CHDERR_NONE) ide_build_features(); } } @@ -2084,7 +2085,8 @@ void ide_hdd_device::device_reset() void ide_hdd_device::read_key(UINT8 key[]) { - chd_get_metadata(m_handle, HARD_DISK_KEY_METADATA_TAG, 0, key, 5, 0, 0, 0); + UINT32 metalength; + m_handle->read_metadata(HARD_DISK_KEY_METADATA_TAG, 0, key, 5, metalength); } //************************************************************************** @@ -2136,7 +2138,8 @@ void ide_hdd_image_device::device_reset() if (PRINTF_IDE_COMMANDS) printf("CHS: %d %d %d\n", m_num_cylinders, m_num_heads, m_num_sectors); } // build the features page - if (chd_get_metadata (m_handle, HARD_DISK_IDENT_METADATA_TAG, 0, m_features, IDE_DISK_SECTOR_SIZE, 0, 0, 0) != CHDERR_NONE) + UINT32 metalength; + if (m_handle->read_metadata (HARD_DISK_IDENT_METADATA_TAG, 0, m_features, IDE_DISK_SECTOR_SIZE, metalength) != CHDERR_NONE) ide_build_features(); } } diff --git a/src/emu/machine/laserdsc.c b/src/emu/machine/laserdsc.c index 42d50493171..a006eebda22 100644 --- a/src/emu/machine/laserdsc.c +++ b/src/emu/machine/laserdsc.c @@ -39,10 +39,11 @@ #include "emu.h" #include "laserdsc.h" -#include "avcomp.h" +#include "avhuff.h" #include "vbiparse.h" #include "config.h" #include "render.h" +#include "chd.h" @@ -93,13 +94,13 @@ laserdisc_device::laserdisc_device(const machine_config &mconfig, device_type ty m_overheight(0), m_overclip(0, -1, 0, -1), m_disc(NULL), - m_vbidata(NULL), m_width(0), m_height(0), m_fps_times_1million(0), m_samplerate(0), m_readresult(CHDERR_NONE), m_chdtracks(0), + m_work_queue(osd_work_queue_alloc(WORK_QUEUE_FLAG_IO)), m_audiosquelch(0), m_videosquelch(0), m_fieldnum(0), @@ -134,6 +135,7 @@ laserdisc_device::laserdisc_device(const machine_config &mconfig, device_type ty laserdisc_device::~laserdisc_device() { + osd_work_queue_free(m_work_queue); } @@ -362,7 +364,7 @@ void laserdisc_device::device_stop() { // make sure all async operations have completed if (m_disc != NULL) - chd_async_complete(m_disc); + osd_work_queue_wait(m_work_queue, osd_ticks_per_second() * 10); // free any textures and palettes if (m_videotex != NULL) @@ -767,13 +769,13 @@ void laserdisc_device::init_disc() m_maxtrack = VIRTUAL_LEAD_IN_TRACKS + MAX_TOTAL_TRACKS + VIRTUAL_LEAD_OUT_TRACKS; if (m_disc != NULL) { - // require the A/V codec - if (chd_get_header(m_disc)->compression != CHDCOMPRESSION_AV) + // require the A/V codec and nothing else + if (m_disc->compression(0) != CHD_CODEC_AVHUFF || m_disc->compression(1) != CHD_CODEC_NONE) throw emu_fatalerror("Laserdisc video must be compressed with the A/V codec!"); // read the metadata - char metadata[256]; - chd_error err = chd_get_metadata(m_disc, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); + astring metadata; + chd_error err = m_disc->read_metadata(AV_METADATA_TAG, 0, metadata); if (err != CHDERR_NONE) throw emu_fatalerror("Non-A/V CHD file specified"); @@ -789,14 +791,12 @@ void laserdisc_device::init_disc() throw emu_fatalerror("Laserdisc video must be interlaced!"); // determine the maximum track and allocate a frame buffer - UINT32 totalhunks = chd_get_header(m_disc)->totalhunks; + UINT32 totalhunks = m_disc->hunk_count(); m_chdtracks = totalhunks / 2; // allocate memory for the precomputed per-frame metadata - m_vbidata = auto_alloc_array(machine(), UINT8, totalhunks * VBI_PACKED_BYTES); - UINT32 vbilength; - err = chd_get_metadata(m_disc, AV_LD_METADATA_TAG, 0, m_vbidata, totalhunks * VBI_PACKED_BYTES, &vbilength, NULL, NULL); - if (err != CHDERR_NONE || vbilength != totalhunks * VBI_PACKED_BYTES) + err = m_disc->read_metadata(AV_LD_METADATA_TAG, 0, m_vbidata); + if (err != CHDERR_NONE || m_vbidata.count() != totalhunks * VBI_PACKED_BYTES) throw emu_fatalerror("Precomputed VBI metadata missing or incorrect size"); } m_maxtrack = MAX(m_maxtrack, VIRTUAL_LEAD_IN_TRACKS + VIRTUAL_LEAD_OUT_TRACKS + m_chdtracks); @@ -1011,7 +1011,7 @@ void laserdisc_device::read_track_data() // cheat and look up the metadata we are about to retrieve vbi_metadata vbidata = { 0 }; - if (m_vbidata != NULL) + if (m_vbidata.count() != 0) vbi_metadata_unpack(&vbidata, NULL, &m_vbidata[readhunk * VBI_PACKED_BYTES]); // if we're in the lead-in area, force the VBI data to be standard lead-in @@ -1040,29 +1040,29 @@ void laserdisc_device::read_track_data() frame->m_lastfield = m_curtrack * 2 + m_fieldnum; // set the video target information - m_avconfig.video.wrap(&frame->m_bitmap.pix16(m_fieldnum), frame->m_bitmap.width(), frame->m_bitmap.height() / 2, frame->m_bitmap.rowpixels() * 2); + m_avhuff_config.video.wrap(&frame->m_bitmap.pix16(m_fieldnum), frame->m_bitmap.width(), frame->m_bitmap.height() / 2, frame->m_bitmap.rowpixels() * 2); // set the audio target information if (m_audiobufin + m_audiomaxsamples <= m_audiobufsize) { // if we can fit without wrapping, just read the data directly - m_avconfig.audio[0] = &m_audiobuffer[0][m_audiobufin]; - m_avconfig.audio[1] = &m_audiobuffer[1][m_audiobufin]; + m_avhuff_config.audio[0] = &m_audiobuffer[0][m_audiobufin]; + m_avhuff_config.audio[1] = &m_audiobuffer[1][m_audiobufin]; } else { // otherwise, read to the beginning of the buffer - m_avconfig.audio[0] = &m_audiobuffer[0][0]; - m_avconfig.audio[1] = &m_audiobuffer[1][0]; + m_avhuff_config.audio[0] = &m_audiobuffer[0][0]; + m_avhuff_config.audio[1] = &m_audiobuffer[1][0]; } // override if we're not decoding - m_avconfig.maxsamples = m_audiomaxsamples; - m_avconfig.actsamples = &m_audiocursamples; + m_avhuff_config.maxsamples = m_audiomaxsamples; + m_avhuff_config.actsamples = &m_audiocursamples; m_audiocursamples = 0; // set the VBI data for the new field from our precomputed data - if (m_vbidata != NULL) + if (m_vbidata.count() != 0) { UINT32 vbiframe; vbi_metadata_unpack(&m_metadata[m_fieldnum], &vbiframe, &m_vbidata[readhunk * VBI_PACKED_BYTES]); @@ -1079,14 +1079,31 @@ void laserdisc_device::read_track_data() m_readresult = CHDERR_FILE_NOT_FOUND; if (m_disc != NULL && !m_videosquelch) { - m_readresult = chd_codec_config(m_disc, AV_CODEC_DECOMPRESS_CONFIG, &m_avconfig); + m_readresult = m_disc->codec_configure(CHD_CODEC_AVHUFF, AVHUFF_CODEC_DECOMPRESS_CONFIG, &m_avhuff_config); if (m_readresult == CHDERR_NONE) - m_readresult = chd_read_async(m_disc, readhunk, NULL); + { + m_queued_hunknum = readhunk; + m_readresult = CHDERR_OPERATION_PENDING; + osd_work_item_queue(m_work_queue, read_async_static, this, WORK_ITEM_FLAG_AUTO_RELEASE); + } } } //------------------------------------------------- +// read_async_static - work item callback for +// asynchronous reads +//------------------------------------------------- + +void *laserdisc_device::read_async_static(void *param, int threadid) +{ + laserdisc_device &ld = *reinterpret_cast<laserdisc_device *>(param); + ld.m_readresult = ld.m_disc->read_hunk(ld.m_queued_hunknum, NULL); + return NULL; +} + + +//------------------------------------------------- // process_track_data - process data from a // track after it has been read //------------------------------------------------- @@ -1095,27 +1112,28 @@ void laserdisc_device::process_track_data() { // wait for the async operation to complete if (m_readresult == CHDERR_OPERATION_PENDING) - m_readresult = chd_async_complete(m_disc); + osd_work_queue_wait(m_work_queue, osd_ticks_per_second() * 10); + assert(m_readresult != CHDERR_OPERATION_PENDING); // remove the video if we had an error if (m_readresult != CHDERR_NONE) - m_avconfig.video.reset(); + m_avhuff_config.video.reset(); // count the field as read if we are successful - if (m_avconfig.video.valid()) + if (m_avhuff_config.video.valid()) { m_frame[m_videoindex].m_numfields++; - player_overlay(m_avconfig.video); + player_overlay(m_avhuff_config.video); } // pass the audio to the callback if (!m_audio_callback.isnull()) - m_audio_callback(*this, m_samplerate, m_audiocursamples, m_avconfig.audio[0], m_avconfig.audio[1]); + m_audio_callback(*this, m_samplerate, m_audiocursamples, m_avhuff_config.audio[0], m_avhuff_config.audio[1]); // shift audio data if we read it into the beginning of the buffer if (m_audiocursamples != 0 && m_audiobufin != 0) for (int chnum = 0; chnum < 2; chnum++) - if (m_avconfig.audio[chnum] == &m_audiobuffer[chnum][0]) + if (m_avhuff_config.audio[chnum] == &m_audiobuffer[chnum][0]) { // move data to the end int samplesleft = m_audiobufsize - m_audiobufin; diff --git a/src/emu/machine/laserdsc.h b/src/emu/machine/laserdsc.h index b7c0df154e9..add1c2e8b5d 100644 --- a/src/emu/machine/laserdsc.h +++ b/src/emu/machine/laserdsc.h @@ -43,6 +43,7 @@ #define __LASERDSC_H__ #include "vbiparse.h" +#include "avhuff.h" //************************************************************************** @@ -315,6 +316,7 @@ private: void vblank_state_changed(screen_device &screen, bool vblank_state); frame_data ¤t_frame(); void read_track_data(); + static void *read_async_static(void *param, int threadid); void process_track_data(); void config_load(int config_type, xml_data_node *parentnode); void config_save(int config_type, xml_data_node *parentnode); @@ -332,14 +334,18 @@ private: // disc parameters chd_file * m_disc; // handle to the disc itself - UINT8 * m_vbidata; // pointer to precomputed VBI data + dynamic_buffer m_vbidata; // pointer to precomputed VBI data int m_width; // width of video int m_height; // height of video UINT32 m_fps_times_1million; // frame rate of video int m_samplerate; // audio samplerate - chd_error m_readresult; // result of the most recent read + int m_readresult; // result of the most recent read UINT32 m_chdtracks; // number of tracks in the CHD - av_codec_decompress_config m_avconfig; // decompression configuration + avhuff_decompress_config m_avhuff_config; // decompression configuration + + // async operations + osd_work_queue * m_work_queue; // work queue + UINT32 m_queued_hunknum; // queued hunk // core states UINT8 m_audiosquelch; // audio squelch state: bit 0 = audio 1, bit 1 = audio 2 diff --git a/src/emu/romload.c b/src/emu/romload.c index 25d2aca3276..85351c74b8d 100644 --- a/src/emu/romload.c +++ b/src/emu/romload.c @@ -38,33 +38,21 @@ class open_chd friend class simple_list<open_chd>; public: - open_chd(const char *region, emu_file &file, chd_file &chdfile, emu_file *difffile = NULL, chd_file *diffchd = NULL) + open_chd(const char *region) : m_next(NULL), - m_region(region), - m_origchd(&chdfile), - m_origfile(&file), - m_diffchd(diffchd), - m_difffile(difffile) { } - - ~open_chd() - { - if (m_diffchd != NULL) chd_close(m_diffchd); - global_free(m_difffile); - chd_close(m_origchd); - global_free(m_origfile); - } + m_region(region) { } open_chd *next() const { return m_next; } const char *region() const { return m_region; } - chd_file *chd() const { return (m_diffchd != NULL) ? m_diffchd : m_origchd; } + chd_file &chd() { return m_diffchd.opened() ? m_diffchd : m_origchd; } + chd_file &orig_chd() { return m_origchd; } + chd_file &diff_chd() { return m_diffchd; } private: open_chd * m_next; /* pointer to next in the list */ astring m_region; /* disk region we came from */ - chd_file * m_origchd; /* handle to the original CHD */ - emu_file * m_origfile; /* file handle to the original CHD file */ - chd_file * m_diffchd; /* handle to the diff CHD */ - emu_file * m_difffile; /* file handle to the diff CHD file */ + chd_file m_origchd; /* handle to the original CHD */ + chd_file m_diffchd; /* handle to the diff CHD */ }; @@ -105,21 +93,15 @@ static void rom_exit(running_machine &machine); HELPERS (also used by devimage.c) ***************************************************************************/ -file_error common_process_file(emu_options &options, const char *location, const char *ext, const rom_entry *romp, emu_file **image_file) +file_error common_process_file(emu_options &options, const char *location, const char *ext, const rom_entry *romp, emu_file &image_file) { - *image_file = global_alloc(emu_file(options.media_path(), OPEN_FLAG_READ)); file_error filerr; if (location != NULL && strcmp(location, "") != 0) - filerr = (*image_file)->open(location, PATH_SEPARATOR, ROM_GETNAME(romp), ext); + filerr = image_file.open(location, PATH_SEPARATOR, ROM_GETNAME(romp), ext); else - filerr = (*image_file)->open(ROM_GETNAME(romp), ext); + filerr = image_file.open(ROM_GETNAME(romp), ext); - if (filerr != FILERR_NONE) - { - global_free(*image_file); - *image_file = NULL; - } return filerr; } @@ -155,7 +137,7 @@ chd_file *get_disk_handle(running_machine &machine, const char *region) { for (open_chd *curdisk = machine.romload_data->chd_list.first(); curdisk != NULL; curdisk = curdisk->next()) if (strcmp(curdisk->region(), region) == 0) - return curdisk->chd(); + return &curdisk->chd(); return NULL; } @@ -165,9 +147,15 @@ chd_file *get_disk_handle(running_machine &machine, const char *region) file associated with the given region -------------------------------------------------*/ -void set_disk_handle(running_machine &machine, const char *region, emu_file &file, chd_file &chdfile) +int set_disk_handle(running_machine &machine, const char *region, const char *fullpath) { - machine.romload_data->chd_list.append(*global_alloc(open_chd(region, file, chdfile))); + open_chd *chd = global_alloc(open_chd(region)); + chd_error err = chd->orig_chd().open(fullpath); + if (err == CHDERR_NONE) + machine.romload_data->chd_list.append(*chd); + else + global_free(chd); + return err; } @@ -1010,16 +998,14 @@ static void process_rom_entries(rom_load_data *romdata, const char *regiontag, c checksum -------------------------------------------------*/ -chd_error open_disk_image(emu_options &options, const game_driver *gamedrv, const rom_entry *romp, emu_file **image_file, chd_file **image_chd, const char *locationtag) +int open_disk_image(emu_options &options, const game_driver *gamedrv, const rom_entry *romp, chd_file &image_chd, const char *locationtag) { + emu_file image_file(options.media_path(), OPEN_FLAG_READ); const rom_entry *region, *rom; const rom_source *source; file_error filerr; chd_error err; - *image_file = NULL; - *image_chd = NULL; - /* attempt to open the properly named file, scanning up through parent directories */ filerr = FILERR_NOT_FOUND; for (int searchdrv = driver_list::find(*gamedrv); searchdrv != -1 && filerr != FILERR_NONE; searchdrv = driver_list::clone(searchdrv)) @@ -1103,14 +1089,13 @@ chd_error open_disk_image(emu_options &options, const game_driver *gamedrv, cons /* did the file open succeed? */ if (filerr == FILERR_NONE) { + astring fullpath(image_file.fullpath()); + image_file.close(); + /* try to open the CHD */ - err = chd_open_file(**image_file, CHD_OPEN_READ, NULL, image_chd); + err = image_chd.open(fullpath); if (err == CHDERR_NONE) return err; - - /* close the file on failure */ - global_free(*image_file); - *image_file = NULL; } else err = CHDERR_FILE_NOT_FOUND; @@ -1141,18 +1126,16 @@ chd_error open_disk_image(emu_options &options, const game_driver *gamedrv, cons /* did the file open succeed? */ if (filerr == FILERR_NONE) { + astring fullpath(image_file.fullpath()); + image_file.close(); + /* try to open the CHD */ - err = chd_open_file(**image_file, CHD_OPEN_READ, NULL, image_chd); + err = image_chd.open(fullpath); if (err == CHDERR_NONE) return err; - - /* close the file on failure */ - global_free(*image_file); - *image_file = NULL; } } } - return err; } @@ -1161,48 +1144,43 @@ chd_error open_disk_image(emu_options &options, const game_driver *gamedrv, cons open_disk_diff - open a DISK diff file -------------------------------------------------*/ -static chd_error open_disk_diff(emu_options &options, const rom_entry *romp, chd_file *source, emu_file **diff_file, chd_file **diff_chd) +static chd_error open_disk_diff(emu_options &options, const rom_entry *romp, chd_file &source, chd_file &diff_chd) { astring fname(ROM_GETNAME(romp), ".dif"); - chd_error err; - - *diff_file = NULL; - *diff_chd = NULL; /* try to open the diff */ LOG(("Opening differencing image file: %s\n", fname.cstr())); - *diff_file = global_alloc(emu_file(options.diff_directory(), OPEN_FLAG_READ | OPEN_FLAG_WRITE)); - file_error filerr = (*diff_file)->open(fname); - if (filerr != FILERR_NONE) + emu_file diff_file(options.diff_directory(), OPEN_FLAG_READ | OPEN_FLAG_WRITE); + file_error filerr = diff_file.open(fname); + if (filerr == FILERR_NONE) { - /* didn't work; try creating it instead */ - LOG(("Creating differencing image: %s\n", fname.cstr())); - (*diff_file)->set_openflags(OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - filerr = (*diff_file)->open(fname); - if (filerr != FILERR_NONE) - { - err = CHDERR_FILE_NOT_FOUND; - goto done; - } + astring fullpath(diff_file.fullpath()); + diff_file.close(); + + LOG(("Opening differencing image file: %s\n", fullpath.cstr())); + return diff_chd.open(fullpath, true, &source); + } + + /* didn't work; try creating it instead */ + LOG(("Creating differencing image: %s\n", fname.cstr())); + diff_file.set_openflags(OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + filerr = diff_file.open(fname); + if (filerr == FILERR_NONE) + { + astring fullpath(diff_file.fullpath()); + diff_file.close(); /* create the CHD */ - err = chd_create_file(**diff_file, 0, 0, CHDCOMPRESSION_NONE, source); + LOG(("Creating differencing image file: %s\n", fullpath.cstr())); + chd_codec_type compression[4] = { CHD_CODEC_NONE }; + chd_error err = diff_chd.create(fullpath, source.logical_bytes(), source.hunk_bytes(), compression, source); if (err != CHDERR_NONE) - goto done; + return err; + + return diff_chd.clone_all_metadata(source); } - LOG(("Opening differencing image file: %s\n", fname.cstr())); - err = chd_open_file(**diff_file, CHD_OPEN_READWRITE, source, diff_chd); - if (err != CHDERR_NONE) - goto done; - -done: - if ((err != CHDERR_NONE) && (*diff_file != NULL)) - { - global_free(*diff_file); - *diff_file = NULL; - } - return err; + return CHDERR_FILE_NOT_FOUND; } @@ -1219,24 +1197,23 @@ static void process_disk_entries(rom_load_data *romdata, const char *regiontag, /* handle files */ if (ROMENTRY_ISFILE(romp)) { + open_chd *chd = global_alloc(open_chd(regiontag)); + hash_collection hashes(ROM_GETHASHDATA(romp)); - chd_header header; chd_error err; /* make the filename of the source */ astring filename(ROM_GETNAME(romp), ".chd"); /* first open the source drive */ - chd_file *origchd; - emu_file *origfile; LOG(("Opening disk image: %s\n", filename.cstr())); - err = open_disk_image(romdata->machine().options(), &romdata->machine().system(), romp, &origfile, &origchd, locationtag); + err = chd_error(open_disk_image(romdata->machine().options(), &romdata->machine().system(), romp, chd->orig_chd(), locationtag)); if (err != CHDERR_NONE) { if (err == CHDERR_FILE_NOT_FOUND) romdata->errorstring.catprintf("%s NOT FOUND\n", filename.cstr()); else - romdata->errorstring.catprintf("%s CHD ERROR: %s\n", filename.cstr(), chd_error_string(err)); + romdata->errorstring.catprintf("%s CHD ERROR: %s\n", filename.cstr(), chd_file::error_string(err)); /* if this is NO_DUMP, keep going, though the system may not be able to handle it */ if (hashes.flag(hash_collection::FLAG_NO_DUMP)) @@ -1245,13 +1222,13 @@ static void process_disk_entries(rom_load_data *romdata, const char *regiontag, romdata->warnings++; else romdata->errors++; + global_free(chd); continue; } /* get the header and extract the SHA1 */ - header = *chd_get_header(origchd); hash_collection acthashes; - acthashes.add_from_buffer(hash_collection::HASH_SHA1, header.sha1, sizeof(header.sha1)); + acthashes.add_from_buffer(hash_collection::HASH_SHA1, chd->orig_chd().sha1().m_raw, sizeof(chd->orig_chd().sha1().m_raw)); /* verify the hash */ if (hashes != acthashes) @@ -1267,23 +1244,22 @@ static void process_disk_entries(rom_load_data *romdata, const char *regiontag, } /* if not read-only, make the diff file */ - chd_file *diffchd = NULL; - emu_file *difffile = NULL; if (!DISK_ISREADONLY(romp)) { /* try to open or create the diff */ - err = open_disk_diff(romdata->machine().options(), romp, origchd, &difffile, &diffchd); + err = open_disk_diff(romdata->machine().options(), romp, chd->orig_chd(), chd->diff_chd()); if (err != CHDERR_NONE) { - romdata->errorstring.catprintf("%s DIFF CHD ERROR: %s\n", filename.cstr(), chd_error_string(err)); + romdata->errorstring.catprintf("%s DIFF CHD ERROR: %s\n", filename.cstr(), chd_file::error_string(err)); romdata->errors++; + global_free(chd); continue; } } /* we're okay, add to the list of disks */ LOG(("Assigning to handle %d\n", DISK_GETINDEX(romp))); - romdata->machine().romload_data->chd_list.append(*global_alloc(open_chd(regiontag, *origfile, *origchd, difffile, diffchd))); + romdata->machine().romload_data->chd_list.append(*chd); } } } diff --git a/src/emu/romload.h b/src/emu/romload.h index 77cc35d0626..108eabd1820 100644 --- a/src/emu/romload.h +++ b/src/emu/romload.h @@ -121,6 +121,7 @@ enum class machine_config; class emu_options; +class chd_file; typedef device_t rom_source; @@ -315,13 +316,13 @@ astring &rom_region_name(astring &result, const game_driver *drv, const rom_sour /* ----- disk handling ----- */ /* open a disk image, searching up the parent and loading by checksum */ -chd_error open_disk_image(emu_options &options, const game_driver *gamedrv, const rom_entry *romp, emu_file **image_file, chd_file **image_chd,const char *locationtag); +int open_disk_image(emu_options &options, const game_driver *gamedrv, const rom_entry *romp, chd_file &image_chd, const char *locationtag); /* return a pointer to the CHD file associated with the given region */ chd_file *get_disk_handle(running_machine &machine, const char *region); /* set a pointer to the CHD file associated with the given region */ -void set_disk_handle(running_machine &machine, const char *region, emu_file &file, chd_file &chdfile); +int set_disk_handle(running_machine &machine, const char *region, const char *fullpath); void load_software_part_region(device_t *device, char *swlist, char *swname, rom_entry *start_region); diff --git a/src/ldplayer/ldplayer.c b/src/ldplayer/ldplayer.c index 9ce38a58147..6b0a059b2ee 100644 --- a/src/ldplayer/ldplayer.c +++ b/src/ldplayer/ldplayer.c @@ -166,8 +166,6 @@ chd_file *ldplayer_state::get_disc() // iterate while we get new objects const osd_directory_entry *dir; - emu_file *image_file = NULL; - chd_file *image_chd = NULL; while ((dir = path.next()) != NULL) { int length = strlen(dir->name); @@ -180,15 +178,17 @@ chd_file *ldplayer_state::get_disc() tolower(dir->name[length - 1]) == 'd') { // open the file itself via our search path - image_file = auto_alloc(machine(), emu_file(machine().options().media_path(), OPEN_FLAG_READ)); - file_error filerr = image_file->open(dir->name); + emu_file image_file(machine().options().media_path(), OPEN_FLAG_READ); + file_error filerr = image_file.open(dir->name); if (filerr == FILERR_NONE) { + astring fullpath(image_file->fullpath(); + image_file.close(); + // try to open the CHD - chd_error chderr = chd_open_file(*image_file, CHD_OPEN_READ, NULL, &image_chd); - if (chderr == CHDERR_NONE) + + if (set_disk_handle(machine(), "laserdisc", fullpath) == CHDERR_NONE) { - set_disk_handle(machine(), "laserdisc", *image_file, *image_chd); m_filename.cpy(dir->name); break; } diff --git a/src/lib/lib.mak b/src/lib/lib.mak index a52f1651a47..5cc276f529f 100644 --- a/src/lib/lib.mak +++ b/src/lib/lib.mak @@ -32,16 +32,19 @@ OBJDIRS += \ UTILOBJS = \ $(LIBOBJ)/util/astring.o \ - $(LIBOBJ)/util/avcomp.o \ + $(LIBOBJ)/util/avhuff.o \ $(LIBOBJ)/util/aviio.o \ $(LIBOBJ)/util/bitmap.o \ $(LIBOBJ)/util/cdrom.o \ $(LIBOBJ)/util/chd.o \ $(LIBOBJ)/util/chdcd.o \ + $(LIBOBJ)/util/chdcodec.o \ $(LIBOBJ)/util/corefile.o \ $(LIBOBJ)/util/corestr.o \ $(LIBOBJ)/util/coreutil.o \ + $(LIBOBJ)/util/flac.o \ $(LIBOBJ)/util/harddisk.o \ + $(LIBOBJ)/util/hashing.o \ $(LIBOBJ)/util/huffman.o \ $(LIBOBJ)/util/jedparse.o \ $(LIBOBJ)/util/md5.o \ @@ -73,7 +76,7 @@ EXPATOBJS = \ $(OBJ)/libexpat.a: $(EXPATOBJS) -$(LIBOBJ)/expat/%.o: $(LIBSRC)/explat/%.c | $(OSPREBUILD) +$(LIBOBJ)/expat/%.o: $(LIBSRC)/expat/%.c | $(OSPREBUILD) @echo Compiling $<... $(CC) $(CDEFS) $(CCOMFLAGS) $(CONLYFLAGS) -c $< -o $@ @@ -273,6 +276,7 @@ $(LIBOBJ)/libjpeg/%.o: $(LIBSRC)/libjpeg/%.c | $(OSPREBUILD) $(CC) $(CDEFS) $(CCOMFLAGS) $(CONLYFLAGS) -I$(LIBSRC)/libjpeg -c $< -o $@ + #------------------------------------------------- # libflac library objects #------------------------------------------------- @@ -303,23 +307,12 @@ $(LIBOBJ)/libflac/%.o: $(LIBSRC)/libflac/libflac/%.c | $(OSPREBUILD) $(CC) $(CDEFS) $(FLACOPTS) $(CONLYFLAGS) -I$(LIBSRC)/libflac/include -c $< -o $@ -# LIBFLACPPOBJS = \ -# $(LIBOBJ)/libflacpp/metadata.o \ -# $(LIBOBJ)/libflacpp/stream_decoder.o \ -# $(LIBOBJ)/libflacpp/stream_encoder.o - -# $(OBJ)/libflac++.a: $(LIBFLACPPOBJS) - -# $(LIBOBJ)/libflacpp/%.o: $(LIBSRC)/libflac/libflac++/%.cpp | $(OSPREBUILD) -# @echo Compiling $<... -# $(CC) $(CDEFS) $(FLACOPTS) $(CPPONLYFLAGS) -I$(LIBSRC)/libflac/include -c $< -o $@ - #------------------------------------------------- # lib7z library objects #------------------------------------------------- -7ZOPTS=-D_7ZIP_PPMD_SUPPPORT +7ZOPTS=-D_7ZIP_PPMD_SUPPPORT -D_7ZIP_ST LIB7ZOBJS = \ $(LIBOBJ)/lib7z/7zBuf.o \ @@ -331,6 +324,9 @@ LIB7ZOBJS = \ $(LIBOBJ)/lib7z/CpuArch.o \ $(LIBOBJ)/lib7z/LzmaDec.o \ $(LIBOBJ)/lib7z/Lzma2Dec.o \ + $(LIBOBJ)/lib7z/LzmaEnc.o \ + $(LIBOBJ)/lib7z/Lzma2Enc.o \ + $(LIBOBJ)/lib7z/LzFind.o \ $(LIBOBJ)/lib7z/Bra.o \ $(LIBOBJ)/lib7z/Bra86.o \ $(LIBOBJ)/lib7z/Bcj2.o \ @@ -343,6 +339,3 @@ $(OBJ)/lib7z.a: $(LIB7ZOBJS) $(LIBOBJ)/lib7z/%.o: $(LIBSRC)/lib7z/%.c | $(OSPREBUILD) @echo Compiling $<... $(CC) $(CDEFS) $(7ZOPTS) $(CONLYFLAGS) -I$(LIBSRC)/lib7z/ -c $< -o $@ - - - diff --git a/src/lib/lib7z/LzmaDec.c b/src/lib/lib7z/LzmaDec.c index 8c1a1486df1..a85edb7dfd0 100644 --- a/src/lib/lib7z/LzmaDec.c +++ b/src/lib/lib7z/LzmaDec.c @@ -967,6 +967,27 @@ SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAll return SZ_OK; } +// why isn't there an interface to pass in the properties directly???? +SRes LzmaDec_Allocate_MAME(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc) +{ + SizeT dicBufSize; + RINOK(LzmaDec_AllocateProbs2(p, propNew, alloc)); + dicBufSize = propNew->dicSize; + if (p->dic == 0 || dicBufSize != p->dicBufSize) + { + LzmaDec_FreeDict(p, alloc); + p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize); + if (p->dic == 0) + { + LzmaDec_FreeProbs(p, alloc); + return SZ_ERROR_MEM; + } + } + p->dicBufSize = dicBufSize; + p->prop = *propNew; + return SZ_OK; +} + SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAlloc *alloc) diff --git a/src/lib/lib7z/Ppmd7.c b/src/lib/lib7z/Ppmd7.c index 060d86d2e0f..bba4d06f0ab 100644 --- a/src/lib/lib7z/Ppmd7.c +++ b/src/lib/lib7z/Ppmd7.c @@ -2,7 +2,7 @@ 2010-03-12 : Igor Pavlov : Public domain This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ -#include <memory.h> +#include <string.h> #include "Ppmd7.h" diff --git a/src/lib/util/astring.h b/src/lib/util/astring.h index 09236de8f5d..665a64786fb 100644 --- a/src/lib/util/astring.h +++ b/src/lib/util/astring.h @@ -52,7 +52,7 @@ // TYPE DEFINITIONS //************************************************************************** -// derived class for C++ +// basic allocated string class class astring { public: diff --git a/src/lib/util/avcomp.c b/src/lib/util/avcomp.c deleted file mode 100644 index 91fa483d16c..00000000000 --- a/src/lib/util/avcomp.c +++ /dev/null @@ -1,898 +0,0 @@ -/*************************************************************************** - - avcomp.c - - Audio/video compression and decompression helpers. - -**************************************************************************** - - Copyright Aaron Giles - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name 'MAME' nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - -**************************************************************************** - - Each frame is compressed as a unit. The raw data is of the form: - (all multibyte values are stored in big-endian format) - - +00 = 'chav' (4 bytes) - fixed header data to identify the format - +04 = metasize (1 byte) - size of metadata in bytes (max=255 bytes) - +05 = channels (1 byte) - number of audio channels - +06 = samples (2 bytes) - number of samples per audio stream - +08 = width (2 bytes) - width of video data - +0A = height (2 bytes) - height of video data - +0C = <metadata> - as raw bytes - <audio stream 0> - as signed 16-bit samples - <audio stream 1> - as signed 16-bit samples - ... - <video data> - as a raw array of 8-bit YUY data in (Cb,Y,Cr,Y) order - - When compressed, the data is stored as follows: - (all multibyte values are stored in big-endian format) - - +00 = metasize (1 byte) - size of metadata in bytes - +01 = channels (1 byte) - number of audio channels - +02 = samples (2 bytes) - number of samples per audio stream - +04 = width (2 bytes) - width of video data - +06 = height (2 bytes) - height of video data - +08 = audio huffman size (2 bytes) - size of audio huffman tables - +0A = str0size (2 bytes) - compressed size of stream 0 - +0C = str1size (2 bytes) - compressed size of stream 1 - ... - <metadata> - as raw data - <audio huffman table> - Huffman table for audio decoding - <audio stream 0 data> - Huffman-compressed deltas - <audio stream 1 data> - Huffman-compressed deltas - <...> - <video huffman tables> - Huffman tables for video decoding - <video data> - compressed data - -**************************************************************************** - - Attempted techniques that have not been worthwhile: - - * Attempted to use integer DCTs from the IJG code; even the "slow" - variants produce a lot of error and thus kill our compression ratio, - since our compression is based on error not bitrate. - - * Tried various other predictors for the lossless video encoding, but - none tended to give any significant gain over predicting the - previous pixel. - -***************************************************************************/ - -#include "avcomp.h" -#include "huffman.h" -#include "chd.h" - -#include <math.h> -#include <stdlib.h> -#include <new> - - -/*************************************************************************** - CONSTANTS -***************************************************************************/ - -#define MAX_CHANNELS 4 - - - -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ - -struct avcomp_state -{ - avcomp_state() - : maxwidth(0), - maxheight(0), - maxchannels(0), - audiodata(NULL), - ycontext(NULL), - cbcontext(NULL), - crcontext(NULL), - audiohicontext(NULL), - audiolocontext(NULL) { } - - /* video parameters */ - UINT32 maxwidth, maxheight; - - /* audio parameters */ - UINT32 maxchannels; - - /* intermediate data */ - UINT8 * audiodata; - - /* huffman contexts */ - huffman_context * ycontext; - huffman_context * cbcontext; - huffman_context * crcontext; - huffman_context * audiohicontext; - huffman_context * audiolocontext; - - /* configuration data */ - av_codec_compress_config compress; - av_codec_decompress_config decompress; -}; - - - -/*************************************************************************** - PROTOTYPES -***************************************************************************/ - -/* encoding helpers */ -static avcomp_error encode_audio(avcomp_state *state, int channels, int samples, const UINT8 **source, int sourcexor, UINT8 *dest, UINT8 *sizes); -static avcomp_error encode_video(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 *complength); -static avcomp_error encode_video_lossless(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 *complength); - -/* decoding helpers */ -static avcomp_error decode_audio(avcomp_state *state, int channels, int samples, const UINT8 *source, UINT8 **dest, UINT32 dxor, const UINT8 *sizes); -static avcomp_error decode_video(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 dstride, UINT32 dxor); -static avcomp_error decode_video_lossless(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 deststride, UINT32 destxor); - - - -/*************************************************************************** - IMPLEMENTATION -***************************************************************************/ - -/*------------------------------------------------- - avcomp_init - allocate and initialize a - new state block for compression or - decompression --------------------------------------------------*/ - -avcomp_state *avcomp_init(UINT32 maxwidth, UINT32 maxheight, UINT32 maxchannels) -{ - huffman_error hufferr; - avcomp_state *state; - - /* error if out of range */ - if (maxchannels > MAX_CHANNELS) - return NULL; - - /* allocate memory for state block */ - state = new(std::nothrow) avcomp_state; - if (state == NULL) - return NULL; - - /* compute the core info */ - state->maxwidth = maxwidth; - state->maxheight = maxheight; - state->maxchannels = maxchannels; - - /* now allocate data buffers */ - state->audiodata = new(std::nothrow) UINT8[65536 * state->maxchannels * 2]; - if (state->audiodata == NULL) - goto cleanup; - - /* create huffman contexts */ - hufferr = huffman_create_context(&state->ycontext, 16); - if (hufferr != HUFFERR_NONE) - goto cleanup; - hufferr = huffman_create_context(&state->cbcontext, 16); - if (hufferr != HUFFERR_NONE) - goto cleanup; - hufferr = huffman_create_context(&state->crcontext, 16); - if (hufferr != HUFFERR_NONE) - goto cleanup; - hufferr = huffman_create_context(&state->audiohicontext, 16); - if (hufferr != HUFFERR_NONE) - goto cleanup; - hufferr = huffman_create_context(&state->audiolocontext, 16); - if (hufferr != HUFFERR_NONE) - goto cleanup; - - return state; - -cleanup: - avcomp_free(state); - return NULL; -} - - -/*------------------------------------------------- - avcomp_free - free a state block --------------------------------------------------*/ - -void avcomp_free(avcomp_state *state) -{ - /* free the data buffers */ - delete[] state->audiodata; - - /* free the contexts */ - if (state->ycontext != NULL) - huffman_free_context(state->ycontext); - if (state->cbcontext != NULL) - huffman_free_context(state->cbcontext); - if (state->crcontext != NULL) - huffman_free_context(state->crcontext); - if (state->audiohicontext != NULL) - huffman_free_context(state->audiohicontext); - if (state->audiolocontext != NULL) - huffman_free_context(state->audiolocontext); - - delete state; -} - - -/*------------------------------------------------- - avcomp_config_compress - configure compression - parameters --------------------------------------------------*/ - -void avcomp_config_compress(avcomp_state *state, av_codec_compress_config *config) -{ - state->compress.video.wrap(config->video, config->video.cliprect()); - state->compress.channels = config->channels; - state->compress.samples = config->samples; - memcpy(state->compress.audio, config->audio, sizeof(state->compress.audio)); - state->compress.metalength = config->metalength; - state->compress.metadata = config->metadata; -} - - -/*------------------------------------------------- - avcomp_config_decompress - configure - decompression parameters --------------------------------------------------*/ - -void avcomp_config_decompress(avcomp_state *state, av_codec_decompress_config *config) -{ - state->decompress.video.wrap(config->video, config->video.cliprect()); - state->decompress.maxsamples = config->maxsamples; - state->decompress.actsamples = config->actsamples; - memcpy(state->decompress.audio, config->audio, sizeof(state->decompress.audio)); - state->decompress.maxmetalength = config->maxmetalength; - state->decompress.actmetalength = config->actmetalength; - state->decompress.metadata = config->metadata; -} - - - -/*************************************************************************** - ENCODING/DECODING FRONTENDS -***************************************************************************/ - -/*------------------------------------------------- - avcomp_encode_data - encode a block of data - into a compressed data stream --------------------------------------------------*/ - -avcomp_error avcomp_encode_data(avcomp_state *state, const UINT8 *source, UINT8 *dest, UINT32 *complength) -{ - const UINT8 *metastart, *videostart, *audiostart[MAX_CHANNELS]; - UINT32 metasize, channels, samples, width, height; - UINT32 audioxor, videoxor, videostride; - avcomp_error err; - UINT32 dstoffs; - int chnum; - - /* extract data from source if present */ - if (source != NULL) - { - /* validate the header */ - if (source[0] != 'c' || source[1] != 'h' || source[2] != 'a' || source[3] != 'v') - return AVCERR_INVALID_DATA; - - /* extract info from the header */ - metasize = source[4]; - channels = source[5]; - samples = (source[6] << 8) + source[7]; - width = (source[8] << 8) + source[9]; - height = (source[10] << 8) + source[11]; - - /* determine the start of each piece of data */ - source += 12; - metastart = source; - source += metasize; - for (chnum = 0; chnum < channels; chnum++) - { - audiostart[chnum] = source; - source += 2 * samples; - } - videostart = source; - - /* data is assumed to be big-endian already */ - audioxor = videoxor = 0; - videostride = 2 * width; - } - - /* otherwise, extract from the state */ - else - { - UINT16 betest = 0; - - /* extract metadata information */ - metastart = state->compress.metadata; - metasize = state->compress.metalength; - if ((metastart == NULL && metasize != 0) || (metastart != NULL && metasize == 0)) - return AVCERR_INVALID_CONFIGURATION; - - /* extract audio information */ - channels = state->compress.channels; - samples = state->compress.samples; - for (chnum = 0; chnum < channels; chnum++) - audiostart[chnum] = (const UINT8 *)state->compress.audio[chnum]; - - /* extract video information */ - videostart = NULL; - videostride = width = height = 0; - if (state->compress.video.valid()) - { - videostart = reinterpret_cast<const UINT8 *>(&state->compress.video.pix(0)); - videostride = state->compress.video.rowpixels() * 2; - width = state->compress.video.width(); - height = state->compress.video.height(); - } - - /* data is assumed to be native-endian */ - *(UINT8 *)&betest = 1; - audioxor = videoxor = (betest == 1) ? 1 : 0; - } - - /* validate the info from the header */ - if (width > state->maxwidth || height > state->maxheight) - return AVCERR_VIDEO_TOO_LARGE; - if (channels > state->maxchannels) - return AVCERR_AUDIO_TOO_LARGE; - - /* write the basics to the new header */ - dest[0] = metasize; - dest[1] = channels; - dest[2] = samples >> 8; - dest[3] = samples; - dest[4] = width >> 8; - dest[5] = width; - dest[6] = height >> 8; - dest[7] = height; - - /* starting offsets */ - dstoffs = 10 + 2 * channels; - - /* copy the metadata first */ - if (metasize > 0) - { - memcpy(dest + dstoffs, metastart, metasize); - dstoffs += metasize; - } - - /* encode the audio channels */ - if (channels > 0) - { - /* encode the audio */ - err = encode_audio(state, channels, samples, audiostart, audioxor, dest + dstoffs, &dest[8]); - if (err != AVCERR_NONE) - return err; - - /* advance the pointers past the data */ - dstoffs += (dest[8] << 8) + dest[9]; - for (chnum = 0; chnum < channels; chnum++) - dstoffs += (dest[10 + 2 * chnum] << 8) + dest[11 + 2 * chnum]; - } - - /* encode the video data */ - if (width > 0 && height > 0) - { - UINT32 vidlength = 0; - - /* encode the video */ - err = encode_video(state, width, height, videostart, videostride, videoxor, dest + dstoffs, &vidlength); - if (err != AVCERR_NONE) - return err; - - /* advance the pointers past the data */ - dstoffs += vidlength; - } - - /* set the total compression */ - *complength = dstoffs; - return AVCERR_NONE; -} - - -/*------------------------------------------------- - avcomp_decode_data - decode both - audio and video from a raw data stream --------------------------------------------------*/ - -avcomp_error avcomp_decode_data(avcomp_state *state, const UINT8 *source, UINT32 complength, UINT8 *dest) -{ - UINT8 *metastart, *videostart, *audiostart[MAX_CHANNELS]; - UINT32 metasize, channels, samples, width, height; - UINT32 audioxor, videoxor, videostride; - UINT32 srcoffs, totalsize; - avcomp_error err; - int chnum; - - /* extract info from the header */ - if (complength < 8) - return AVCERR_INVALID_DATA; - metasize = source[0]; - channels = source[1]; - samples = (source[2] << 8) + source[3]; - width = (source[4] << 8) + source[5]; - height = (source[6] << 8) + source[7]; - - /* validate the info from the header */ - if (width > state->maxwidth || height > state->maxheight) - return AVCERR_VIDEO_TOO_LARGE; - if (channels > state->maxchannels) - return AVCERR_AUDIO_TOO_LARGE; - - /* validate that the sizes make sense */ - if (complength < 10 + 2 * channels) - return AVCERR_INVALID_DATA; - totalsize = 10 + 2 * channels; - totalsize += (source[8] << 8) | source[9]; - for (chnum = 0; chnum < channels; chnum++) - totalsize += (source[10 + 2 * chnum] << 8) | source[11 + 2 * chnum]; - if (totalsize >= complength) - return AVCERR_INVALID_DATA; - - /* starting offsets */ - srcoffs = 10 + 2 * channels; - - /* if we are decoding raw, set up the output parameters */ - if (dest != NULL) - { - /* create a header */ - dest[0] = 'c'; - dest[1] = 'h'; - dest[2] = 'a'; - dest[3] = 'v'; - dest[4] = metasize; - dest[5] = channels; - dest[6] = samples >> 8; - dest[7] = samples; - dest[8] = width >> 8; - dest[9] = width; - dest[10] = height >> 8; - dest[11] = height; - - /* determine the start of each piece of data */ - dest += 12; - metastart = dest; - dest += metasize; - for (chnum = 0; chnum < channels; chnum++) - { - audiostart[chnum] = dest; - dest += 2 * samples; - } - videostart = dest; - - /* data is assumed to be big-endian already */ - audioxor = videoxor = 0; - videostride = 2 * width; - } - - /* otherwise, extract from the state */ - else - { - UINT16 betest = 0; - - /* determine the start of each piece of data */ - metastart = state->decompress.metadata; - for (chnum = 0; chnum < channels; chnum++) - audiostart[chnum] = (UINT8 *)state->decompress.audio[chnum]; - videostart = (state->decompress.video.valid()) ? reinterpret_cast<UINT8 *>(&state->decompress.video.pix(0)) : NULL; - videostride = (state->decompress.video.valid()) ? state->decompress.video.rowpixels() * 2 : 0; - - /* data is assumed to be native-endian */ - *(UINT8 *)&betest = 1; - audioxor = videoxor = (betest == 1) ? 1 : 0; - - /* verify against sizes */ - if (state->decompress.video.valid() && (state->decompress.video.width() < width || state->decompress.video.height() < height)) - return AVCERR_VIDEO_TOO_LARGE; - for (chnum = 0; chnum < channels; chnum++) - if (state->decompress.audio[chnum] != NULL && state->decompress.maxsamples < samples) - return AVCERR_AUDIO_TOO_LARGE; - if (state->decompress.metadata != NULL && state->decompress.maxmetalength < metasize) - return AVCERR_METADATA_TOO_LARGE; - - /* set the output values */ - if (state->decompress.actsamples != NULL) - *state->decompress.actsamples = samples; - if (state->decompress.actmetalength != NULL) - *state->decompress.actmetalength = metasize; - } - - /* copy the metadata first */ - if (metasize > 0) - { - if (metastart != NULL) - memcpy(metastart, source + srcoffs, metasize); - srcoffs += metasize; - } - - /* decode the audio channels */ - if (channels > 0) - { - /* decode the audio */ - err = decode_audio(state, channels, samples, source + srcoffs, audiostart, audioxor, &source[8]); - if (err != AVCERR_NONE) - return err; - - /* advance the pointers past the data */ - srcoffs += (source[8] << 8) + source[9]; - for (chnum = 0; chnum < channels; chnum++) - srcoffs += (source[10 + 2 * chnum] << 8) + source[11 + 2 * chnum]; - } - - /* decode the video data */ - if (width > 0 && height > 0 && videostart != NULL) - { - /* decode the video */ - err = decode_video(state, width, height, source + srcoffs, complength - srcoffs, videostart, videostride, videoxor); - if (err != AVCERR_NONE) - return err; - } - return AVCERR_NONE; -} - - - -/*************************************************************************** - ENCODING HELPERS -***************************************************************************/ - -/*------------------------------------------------- - encode_audio - encode raw audio data - to the destination --------------------------------------------------*/ - -static avcomp_error encode_audio(avcomp_state *state, int channels, int samples, const UINT8 **source, int sourcexor, UINT8 *dest, UINT8 *sizes) -{ - UINT32 size, huffsize, totalsize; - huffman_context *contexts[2]; - huffman_error hufferr; - UINT8 *output = dest; - int chnum, sampnum; - UINT8 *deltabuf; - - /* iterate over channels to compute deltas */ - deltabuf = state->audiodata; - for (chnum = 0; chnum < channels; chnum++) - { - const UINT8 *srcdata = source[chnum]; - INT16 prevsample = 0; - - /* extract audio data into hi and lo deltas stored in big-endian order */ - for (sampnum = 0; sampnum < samples; sampnum++) - { - INT16 newsample = (srcdata[0 ^ sourcexor] << 8) | srcdata[1 ^ sourcexor]; - INT16 delta = newsample - prevsample; - prevsample = newsample; - *deltabuf++ = delta >> 8; - *deltabuf++ = delta; - srcdata += 2; - } - } - - /* compute the trees */ - contexts[0] = state->audiohicontext; - contexts[1] = state->audiolocontext; - hufferr = huffman_compute_tree_interleaved(2, contexts, state->audiodata, samples * 2, channels, samples * 2, 0); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - - /* export them to the output */ - hufferr = huffman_export_tree(state->audiohicontext, output, 256, &size); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += size; - hufferr = huffman_export_tree(state->audiolocontext, output, 256, &size); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += size; - - /* note the size of the two trees */ - huffsize = output - dest; - sizes[0] = huffsize >> 8; - sizes[1] = huffsize; - - /* iterate over channels */ - totalsize = huffsize; - for (chnum = 0; chnum < channels; chnum++) - { - const UINT8 *input = state->audiodata + chnum * samples * 2; - - /* encode the data */ - hufferr = huffman_encode_data_interleaved(2, contexts, input, samples * 2, 1, 0, 0, output, samples * 2, &size); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += size; - - /* store the size of this stream */ - totalsize += size; - if (totalsize >= channels * samples * 2) - break; - sizes[chnum * 2 + 2] = size >> 8; - sizes[chnum * 2 + 3] = size; - } - - /* if we ran out of room, throw it all away and just store raw */ - if (chnum < channels) - { - memcpy(dest, state->audiodata, channels * samples * 2); - size = samples * 2; - sizes[0] = sizes[1] = 0; - for (chnum = 0; chnum < channels; chnum++) - { - sizes[chnum * 2 + 2] = size >> 8; - sizes[chnum * 2 + 3] = size; - } - } - - return AVCERR_NONE; -} - - -/*------------------------------------------------- - encode_video - encode raw video data - to the destination --------------------------------------------------*/ - -static avcomp_error encode_video(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 *complength) -{ - /* only lossless supported at this time */ - return encode_video_lossless(state, width, height, source, sstride, sxor, dest, complength); -} - - -/*------------------------------------------------- - encode_video_lossless - do a lossless video - encoding using deltas and huffman encoding --------------------------------------------------*/ - -static avcomp_error encode_video_lossless(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 *complength) -{ - UINT32 srcbytes = width * height * 2; - huffman_context *contexts[4]; - huffman_error hufferr; - UINT32 outbytes; - UINT8 *output; - - /* set up the output; first byte is 0x80 to indicate lossless encoding */ - output = dest; - *output++ = 0x80; - - /* now encode to the destination using two trees, one for the Y and one for the Cr/Cb */ - contexts[0] = state->ycontext; - contexts[1] = state->cbcontext; - contexts[2] = state->ycontext; - contexts[3] = state->crcontext; - - /* compute the histograms for the data */ - hufferr = huffman_deltarle_compute_tree_interleaved(4, contexts, source, width * 2, height, sstride, sxor); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - - /* export the trees to the data stream */ - hufferr = huffman_deltarle_export_tree(state->ycontext, output, 256, &outbytes); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += outbytes; - hufferr = huffman_deltarle_export_tree(state->cbcontext, output, 256, &outbytes); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += outbytes; - hufferr = huffman_deltarle_export_tree(state->crcontext, output, 256, &outbytes); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += outbytes; - - /* encode the data using the trees */ - hufferr = huffman_deltarle_encode_data_interleaved(4, contexts, source, width * 2, height, sstride, sxor, output, srcbytes, &outbytes); - if (hufferr != HUFFERR_NONE) - return AVCERR_COMPRESSION_ERROR; - output += outbytes; - - /* set the final length */ - *complength = output - dest; - return AVCERR_NONE; -} - - - -/*************************************************************************** - DECODING HELPERS -***************************************************************************/ - -/*------------------------------------------------- - decode_audio - decode audio from a - compressed data stream --------------------------------------------------*/ - -static avcomp_error decode_audio(avcomp_state *state, int channels, int samples, const UINT8 *source, UINT8 **dest, UINT32 dxor, const UINT8 *sizes) -{ - huffman_context *contexts[2]; - UINT32 actsize, huffsize; - huffman_error hufferr; - int chnum, sampnum; - UINT16 size; - - /* if no huffman length, just copy the data */ - size = (sizes[0] << 8) | sizes[1]; - if (size == 0) - { - /* loop over channels */ - for (chnum = 0; chnum < channels; chnum++) - { - UINT8 *curdest = dest[chnum]; - - /* extract the size of this channel */ - size = (sizes[chnum * 2 + 2] << 8) | sizes[chnum * 2 + 3]; - - /* extract data from the deltas */ - if (dest[chnum] != NULL) - { - INT16 prevsample = 0; - for (sampnum = 0; sampnum < samples; sampnum++) - { - INT16 delta = (source[0] << 8) | source[1]; - INT16 newsample = prevsample + delta; - prevsample = newsample; - - curdest[0 ^ dxor] = newsample >> 8; - curdest[1 ^ dxor] = newsample; - source += 2; - curdest += 2; - } - } - else - source += size; - } - return AVCERR_NONE; - } - - /* extract the huffman trees */ - hufferr = huffman_import_tree(state->audiohicontext, source, size, &actsize); - if (hufferr != HUFFERR_NONE) - return AVCERR_INVALID_DATA; - source += actsize; - huffsize = actsize; - - hufferr = huffman_import_tree(state->audiolocontext, source, size, &actsize); - if (hufferr != HUFFERR_NONE) - return AVCERR_INVALID_DATA; - source += actsize; - huffsize += actsize; - if (huffsize != size) - return AVCERR_INVALID_DATA; - - /* set up the contexts */ - contexts[0] = state->audiohicontext; - contexts[1] = state->audiolocontext; - - /* now loop over channels and decode their data */ - for (chnum = 0; chnum < channels; chnum++) - { - /* extract the size of this channel */ - size = (sizes[chnum * 2 + 2] << 8) | sizes[chnum * 2 + 3]; - - /* decode the data */ - if (dest[chnum] != NULL) - { - UINT8 *deltabuf = state->audiodata + chnum * samples * 2; - hufferr = huffman_decode_data_interleaved(2, contexts, source, size, deltabuf, samples * 2, 1, 0, 0, &actsize); - if (hufferr != HUFFERR_NONE || actsize != size) - return AVCERR_INVALID_DATA; - } - - /* advance */ - source += size; - } - - /* reassemble audio from the deltas */ - for (chnum = 0; chnum < channels; chnum++) - if (dest[chnum] != NULL) - { - UINT8 *deltabuf = state->audiodata + chnum * samples * 2; - UINT8 *curdest = dest[chnum]; - INT16 prevsample = 0; - - for (sampnum = 0; sampnum < samples; sampnum++) - { - INT16 delta = (deltabuf[0] << 8) | deltabuf[1]; - INT16 newsample = prevsample + delta; - prevsample = newsample; - - curdest[0 ^ dxor] = newsample >> 8; - curdest[1 ^ dxor] = newsample; - deltabuf += 2; - curdest += 2; - } - } - - return AVCERR_NONE; -} - - -/*------------------------------------------------- - decode_video - decode video from a - compressed data stream --------------------------------------------------*/ - -static avcomp_error decode_video(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 dstride, UINT32 dxor) -{ - /* if the high bit of the first byte is set, we decode losslessly */ - if (source[0] & 0x80) - return decode_video_lossless(state, width, height, source, complength, dest, dstride, dxor); - else - return AVCERR_INVALID_DATA; -} - - -/*------------------------------------------------- - decode_video_lossless - do a lossless video - decoding using deltas and huffman encoding --------------------------------------------------*/ - -static avcomp_error decode_video_lossless(avcomp_state *state, int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 deststride, UINT32 destxor) -{ - const UINT8 *sourceend = source + complength; - huffman_context *contexts[4]; - huffman_error hufferr; - UINT32 actsize; - - /* skip the first byte */ - source++; - - /* import the tables */ - hufferr = huffman_deltarle_import_tree(state->ycontext, source, sourceend - source, &actsize); - if (hufferr != HUFFERR_NONE) - return AVCERR_INVALID_DATA; - source += actsize; - hufferr = huffman_deltarle_import_tree(state->cbcontext, source, sourceend - source, &actsize); - if (hufferr != HUFFERR_NONE) - return AVCERR_INVALID_DATA; - source += actsize; - hufferr = huffman_deltarle_import_tree(state->crcontext, source, sourceend - source, &actsize); - if (hufferr != HUFFERR_NONE) - return AVCERR_INVALID_DATA; - source += actsize; - - /* set up the decoding contexts */ - contexts[0] = state->ycontext; - contexts[1] = state->cbcontext; - contexts[2] = state->ycontext; - contexts[3] = state->crcontext; - - /* decode to the destination */ - hufferr = huffman_deltarle_decode_data_interleaved(4, contexts, source, sourceend - source, dest, width * 2, height, deststride, destxor, &actsize); - if (hufferr != HUFFERR_NONE) - return AVCERR_INVALID_DATA; - if (actsize != sourceend - source) - return AVCERR_INVALID_DATA; - - return AVCERR_NONE; -} diff --git a/src/lib/util/avcomp.h b/src/lib/util/avcomp.h deleted file mode 100644 index ee9bba8a192..00000000000 --- a/src/lib/util/avcomp.h +++ /dev/null @@ -1,140 +0,0 @@ -/*************************************************************************** - - avcomp.h - - Audio/video compression and decompression helpers. - -**************************************************************************** - - Copyright Aaron Giles - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - * Neither the name 'MAME' nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - -***************************************************************************/ - -#ifndef __AVCOMP_H__ -#define __AVCOMP_H__ - -#include "osdcore.h" -#include "bitmap.h" - - -/*************************************************************************** - CONSTANTS -***************************************************************************/ - -/* errors */ -enum _avcomp_error -{ - AVCERR_NONE = 0, - AVCERR_INVALID_DATA, - AVCERR_VIDEO_TOO_LARGE, - AVCERR_AUDIO_TOO_LARGE, - AVCERR_METADATA_TOO_LARGE, - AVCERR_OUT_OF_MEMORY, - AVCERR_COMPRESSION_ERROR, - AVCERR_TOO_MANY_CHANNELS, - AVCERR_INVALID_CONFIGURATION -}; -typedef enum _avcomp_error avcomp_error; - -/* default decompression parameters */ -#define AVCOMP_ENABLE_META (1 << 0) -#define AVCOMP_ENABLE_VIDEO (1 << 1) -#define AVCOMP_ENABLE_AUDIO(x) (1 << (2 + (x))) -#define AVCOMP_ENABLE_DEFAULT (~0) - - - -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ - -/* compression configuration */ -struct av_codec_compress_config -{ - av_codec_compress_config() - : channels(0), - samples(0), - metalength(0), - metadata(NULL) - { - memset(audio, 0, sizeof(audio)); - } - - bitmap_yuy16 video; /* pointer to video bitmap */ - UINT32 channels; /* number of channels */ - UINT32 samples; /* number of samples per channel */ - INT16 * audio[16]; /* pointer to individual audio channels */ - UINT32 metalength; /* length of metadata */ - UINT8 * metadata; /* pointer to metadata buffer */ -}; - - -/* decompression configuration */ -struct av_codec_decompress_config -{ - av_codec_decompress_config() - : maxsamples(0), - actsamples(0), - maxmetalength(0), - actmetalength(0), - metadata(NULL) - { - memset(audio, 0, sizeof(audio)); - } - - bitmap_yuy16 video; /* pointer to video bitmap */ - UINT32 maxsamples; /* maximum number of samples per channel */ - UINT32 * actsamples; /* actual number of samples per channel */ - INT16 * audio[16]; /* pointer to individual audio channels */ - UINT32 maxmetalength; /* maximum length of metadata */ - UINT32 * actmetalength; /* actual length of metadata */ - UINT8 * metadata; /* pointer to metadata buffer */ -}; - - -/* opaque state */ -struct avcomp_state; - - - -/*************************************************************************** - PROTOTYPES -***************************************************************************/ - -avcomp_state *avcomp_init(UINT32 maxwidth, UINT32 maxheight, UINT32 maxchannels); -void avcomp_free(avcomp_state *state); - -void avcomp_config_compress(avcomp_state *state, av_codec_compress_config *config); -void avcomp_config_decompress(avcomp_state *state, av_codec_decompress_config *config); - -avcomp_error avcomp_encode_data(avcomp_state *state, const UINT8 *source, UINT8 *dest, UINT32 *complength); -avcomp_error avcomp_decode_data(avcomp_state *state, const UINT8 *source, UINT32 complength, UINT8 *dest); - -#endif diff --git a/src/lib/util/avhuff.c b/src/lib/util/avhuff.c new file mode 100644 index 00000000000..3e1126abea6 --- /dev/null +++ b/src/lib/util/avhuff.c @@ -0,0 +1,977 @@ +/*************************************************************************** + + avhuff.c + + Audio/video compression and decompression helpers. + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +**************************************************************************** + + Each frame is compressed as a unit. The raw data is of the form: + (all multibyte values are stored in big-endian format) + + +00 = 'chav' (4 bytes) - fixed header data to identify the format + +04 = metasize (1 byte) - size of metadata in bytes (max=255 bytes) + +05 = channels (1 byte) - number of audio channels + +06 = samples (2 bytes) - number of samples per audio stream + +08 = width (2 bytes) - width of video data + +0A = height (2 bytes) - height of video data + +0C = <metadata> - as raw bytes + <audio stream 0> - as signed 16-bit samples + <audio stream 1> - as signed 16-bit samples + ... + <video data> - as a raw array of 8-bit YUY data in (Cb,Y,Cr,Y) order + + When compressed, the data is stored as follows: + (all multibyte values are stored in big-endian format) + + +00 = metasize (1 byte) - size of metadata in bytes + +01 = channels (1 byte) - number of audio channels + +02 = samples (2 bytes) - number of samples per audio stream + +04 = width (2 bytes) - width of video data + +06 = height (2 bytes) - height of video data + +08 = audio huffman size (2 bytes) - size of audio huffman tables + (0x0000 => uncompressed deltas are used) + +0A = str0size (2 bytes) - compressed size of stream 0 + +0C = str1size (2 bytes) - compressed size of stream 1 + ... + <metadata> - as raw data + <audio huffman table> - Huffman table for audio decoding + <audio stream 0 data> - Huffman-compressed deltas + <audio stream 1 data> - Huffman-compressed deltas + <...> + <video huffman tables> - Huffman tables for video decoding + <video data> - compressed data + +**************************************************************************** + + Attempted techniques that have not been worthwhile: + + * Attempted to use integer DCTs from the IJG code; even the "slow" + variants produce a lot of error and thus kill our compression ratio, + since our compression is based on error not bitrate. + + * Tried various other predictors for the lossless video encoding, but + none tended to give any significant gain over predicting the + previous pixel. + +***************************************************************************/ + +#include "avhuff.h" +#include "huffman.h" +#include "chd.h" + +#include <math.h> +#include <stdlib.h> +#include <new> + + + +//************************************************************************** +// INLINE FUNCTIONS +//************************************************************************** + +//------------------------------------------------- +// code_to_rlecount - number of RLE repetitions +// encoded in a given byte +//------------------------------------------------- + +inline int code_to_rlecount(int code) +{ + if (code == 0x00) + return 1; + if (code <= 0x107) + return 8 + (code - 0x100); + return 16 << (code - 0x108); +} + + +//------------------------------------------------- +// rlecount_to_byte - return a byte encoding +// the maximum RLE count less than or equal to +// the provided amount +//------------------------------------------------- + +inline int rlecount_to_code(int rlecount) +{ + if (rlecount >= 2048) + return 0x10f; + if (rlecount >= 1024) + return 0x10e; + if (rlecount >= 512) + return 0x10d; + if (rlecount >= 256) + return 0x10c; + if (rlecount >= 128) + return 0x10b; + if (rlecount >= 64) + return 0x10a; + if (rlecount >= 32) + return 0x109; + if (rlecount >= 16) + return 0x108; + if (rlecount >= 8) + return 0x100 + (rlecount - 8); + return 0x00; +} + + +//------------------------------------------------- +// encode_one - encode data +//------------------------------------------------- + +inline void avhuff_encoder::deltarle_encoder::encode_one(bitstream_out &bitbuf, UINT16 *&rleptr) +{ + // return RLE data if we still have some + if (m_rlecount != 0) + { + m_rlecount--; + return; + } + + // fetch the data and process + UINT16 data = *rleptr++; + m_encoder.encode_one(bitbuf, data); + if (data >= 0x100) + m_rlecount = code_to_rlecount(data) - 1; +} + + +//------------------------------------------------- +// decode_one - decode data +//------------------------------------------------- + +inline UINT32 avhuff_decoder::deltarle_decoder::decode_one(bitstream_in &bitbuf) +{ + // return RLE data if we still have some + if (m_rlecount != 0) + { + m_rlecount--; + return m_prevdata; + } + + // fetch the data and process + int data = m_decoder.decode_one(bitbuf); + if (data < 0x100) + { + m_prevdata += UINT8(data); + return m_prevdata; + } + else + { + m_rlecount = code_to_rlecount(data); + m_rlecount--; + return m_prevdata; + } +} + + + +//************************************************************************** +// AVHUFF ENCODER +//************************************************************************** + +//------------------------------------------------- +// avhuff_encoder - constructor +//------------------------------------------------- + +avhuff_encoder::avhuff_encoder() +{ +m_flac_encoder.set_sample_rate(48000); +m_flac_encoder.set_num_channels(1); +m_flac_encoder.set_strip_metadata(true); +} + + +//------------------------------------------------- +// encode_data - encode a block of data into a +// compressed data stream +//------------------------------------------------- + +avhuff_error avhuff_encoder::encode_data(const UINT8 *source, UINT8 *dest, UINT32 &complength) +{ + // validate the header + if (source[0] != 'c' || source[1] != 'h' || source[2] != 'a' || source[3] != 'v') + return AVHERR_INVALID_DATA; + + // extract info from the header + UINT32 metasize = source[4]; + UINT32 channels = source[5]; + UINT32 samples = (source[6] << 8) + source[7]; + UINT32 width = (source[8] << 8) + source[9]; + UINT32 height = (source[10] << 8) + source[11]; + source += 12; + + // write the basics to the new header + dest[0] = metasize; + dest[1] = channels; + dest[2] = samples >> 8; + dest[3] = samples; + dest[4] = width >> 8; + dest[5] = width; + dest[6] = height >> 8; + dest[7] = height; + + // starting offsets + UINT32 dstoffs = 10 + 2 * channels; + + // copy the metadata first + if (metasize > 0) + { + memcpy(dest + dstoffs, source, metasize); + source += metasize; + dstoffs += metasize; + } + + // encode the audio channels + if (channels > 0) + { + // encode the audio + avhuff_error err = encode_audio(source, channels, samples, dest + dstoffs, &dest[8]); + source += channels * samples * 2; + if (err != AVHERR_NONE) + return err; + + // advance the pointers past the data + UINT16 treesize = (dest[8] << 8) + dest[9]; + if (treesize != 0xffff) + dstoffs += treesize; + for (int chnum = 0; chnum < channels; chnum++) + dstoffs += (dest[10 + 2 * chnum] << 8) + dest[11 + 2 * chnum]; + } + + // encode the video data + if (width > 0 && height > 0) + { + // encode the video + UINT32 vidlength = 0; + avhuff_error err = encode_video(source, width, height, dest + dstoffs, vidlength); + if (err != AVHERR_NONE) + return err; + + // advance the pointers past the data + dstoffs += vidlength; + } + + // set the total compression + complength = dstoffs; + return AVHERR_NONE; +} + + +//------------------------------------------------- +// raw_data_size - return the raw data size of +// a raw stream based on the header +//------------------------------------------------- + +UINT32 avhuff_encoder::raw_data_size(const UINT8 *data) +{ + // make sure we have a correct header + int size = 0; + if (data[0] == 'c' && data[1] == 'h' && data[2] == 'a' && data[3] == 'v') + { + // add in header size plus metadata length + size = 12 + data[4]; + + // add in channels * samples + size += 2 * data[5] * ((data[6] << 8) + data[7]); + + // add in 2 * width * height + size += 2 * ((data[8] << 8) + data[9]) * (((data[10] << 8) + data[11]) & 0x7fff); + } + return size; +} + + +//------------------------------------------------- +// assemble_data - assemble a datastream from raw +// bits +//------------------------------------------------- + +avhuff_error avhuff_encoder::assemble_data(UINT8 *dest, UINT32 dlength, bitmap_yuy16 &bitmap, UINT8 channels, UINT32 numsamples, INT16 **samples, UINT8 *metadata, UINT32 metadatasize) +{ + // sanity check the inputs + if (metadatasize > 255) + return AVHERR_METADATA_TOO_LARGE; + if (numsamples > 65535) + return AVHERR_AUDIO_TOO_LARGE; + if (bitmap.width() > 65535 || bitmap.height() > 65535) + return AVHERR_VIDEO_TOO_LARGE; + if (dlength < 12 + metadatasize + numsamples * channels * 2 + bitmap.width() * bitmap.height() * 2) + return AVHERR_BUFFER_TOO_SMALL; + + // fill in the header + *dest++ = 'c'; + *dest++ = 'h'; + *dest++ = 'a'; + *dest++ = 'v'; + *dest++ = metadatasize; + *dest++ = channels; + *dest++ = numsamples >> 8; + *dest++ = numsamples & 0xff; + *dest++ = bitmap.width() >> 8; + *dest++ = bitmap.width() & 0xff; + *dest++ = bitmap.height() >> 8; + *dest++ = bitmap.height() & 0xff; + + // copy the metadata + if (metadatasize > 0) + memcpy(dest, metadata, metadatasize); + dest += metadatasize; + + // copy the audio streams + for (UINT8 curchan = 0; curchan < channels; curchan++) + for (UINT32 cursamp = 0; cursamp < numsamples; cursamp++) + { + *dest++ = samples[curchan][cursamp] >> 8; + *dest++ = samples[curchan][cursamp] & 0xff; + } + + // copy the video data + for (INT32 y = 0; y < bitmap.height(); y++) + { + UINT16 *src = &bitmap.pix(y); + for (INT32 x = 0; x < bitmap.width(); x++) + { + *dest++ = src[x] >> 8; + *dest++ = src[x] & 0xff; + } + } + return AVHERR_NONE; +} + + +//------------------------------------------------- +// encode_audio - encode raw audio data to the +// destination +//------------------------------------------------- + +avhuff_error avhuff_encoder::encode_audio(const UINT8 *source, int channels, int samples, UINT8 *dest, UINT8 *sizes) +{ +#if AVHUFF_USE_FLAC + + // input data is big-endian; determine our platform endianness + UINT16 be_test = 0; + *(UINT8 *)&be_test = 1; + bool swap_endian = (be_test == 1); + + // set huffman tree size to 0xffff to indicate FLAC + sizes[0] = 0xff; + sizes[1] = 0xff; + + // set the block size for this round and iterate over channels + m_flac_encoder.set_block_size(samples); + for (int chnum = 0; chnum < channels; chnum++) + { + // encode the data + m_flac_encoder.reset(dest, samples * 2); + if (!m_flac_encoder.encode_interleaved(reinterpret_cast<const INT16 *>(source) + chnum * samples, samples, swap_endian)) + return AVHERR_COMPRESSION_ERROR; + + // set the size for this channel + UINT32 cursize = m_flac_encoder.finish(); + sizes[chnum * 2 + 2] = cursize >> 8; + sizes[chnum * 2 + 3] = cursize; + dest += cursize; + } + +#else + + // expand the delta buffer if needed + m_audiobuffer.resize(channels * samples * 2); + UINT8 *deltabuf = m_audiobuffer; + + // iterate over channels to compute deltas + m_audiohi_encoder.histo_reset(); + m_audiolo_encoder.histo_reset(); + for (int chnum = 0; chnum < channels; chnum++) + { + // extract audio data into hi and lo deltas stored in big-endian order + INT16 prevsample = 0; + for (int sampnum = 0; sampnum < samples; sampnum++) + { + INT16 newsample = (source[0] << 8) | source[1]; + source += 2; + + INT16 delta = newsample - prevsample; + prevsample = newsample; + m_audiohi_encoder.histo_one(*deltabuf++ = delta >> 8); + m_audiolo_encoder.histo_one(*deltabuf++ = delta); + } + } + + // compute the trees + huffman_error hufferr = m_audiohi_encoder.compute_tree_from_histo(); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + hufferr = m_audiolo_encoder.compute_tree_from_histo(); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + + // export the trees to the output + bitstream_out bitbuf(dest, 2 * channels * samples); + hufferr = m_audiohi_encoder.export_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + bitbuf.flush(); + hufferr = m_audiolo_encoder.export_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + + // note the size of the two trees + UINT32 huffsize = bitbuf.flush(); + sizes[0] = huffsize >> 8; + sizes[1] = huffsize; + + // iterate over channels + UINT32 totalsize = huffsize; + int chnum; + for (chnum = 0; chnum < channels; chnum++) + { + // encode the data + const UINT8 *input = m_audiobuffer + chnum * samples * 2; + for (int sampnum = 0; sampnum < samples; sampnum++) + { + m_audiohi_encoder.encode_one(bitbuf, *input++); + m_audiolo_encoder.encode_one(bitbuf, *input++); + } + + // store the size of this stream + UINT32 cursize = bitbuf.flush() - totalsize; + totalsize += cursize; + if (totalsize >= channels * samples * 2) + break; + sizes[chnum * 2 + 2] = cursize >> 8; + sizes[chnum * 2 + 3] = cursize; + } + + // if we ran out of room, throw it all away and just store raw + if (chnum < channels) + { + memcpy(dest, m_audiobuffer, channels * samples * 2); + UINT32 size = samples * 2; + sizes[0] = sizes[1] = 0; + for (chnum = 0; chnum < channels; chnum++) + { + sizes[chnum * 2 + 2] = size >> 8; + sizes[chnum * 2 + 3] = size; + } + } + +#endif + + return AVHERR_NONE; +} + + +//------------------------------------------------- +// encode_video - encode raw video data to the +// destination +//------------------------------------------------- + +avhuff_error avhuff_encoder::encode_video(const UINT8 *source, int width, int height, UINT8 *dest, UINT32 &complength) +{ + // only lossless supported at this time + return encode_video_lossless(source, width, height, dest, complength); +} + + +//------------------------------------------------- +// encode_video_lossless - do a lossless video +// encoding using deltas and huffman encoding +//------------------------------------------------- + +avhuff_error avhuff_encoder::encode_video_lossless(const UINT8 *source, int width, int height, UINT8 *dest, UINT32 &complength) +{ + // set up the output; first byte is 0x80 to indicate lossless encoding + bitstream_out bitbuf(dest, width * height * 2); + bitbuf.write(0x80, 8); + + // compute the histograms for the data + UINT16 *yrle = m_ycontext.rle_and_histo_bitmap(source + 0, width, 2, height); + UINT16 *cbrle = m_cbcontext.rle_and_histo_bitmap(source + 1, width / 2, 4, height); + UINT16 *crrle = m_crcontext.rle_and_histo_bitmap(source + 3, width / 2, 4, height); + + // export the trees to the data stream + huffman_error hufferr = m_ycontext.export_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + bitbuf.flush(); + hufferr = m_cbcontext.export_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + bitbuf.flush(); + hufferr = m_crcontext.export_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_COMPRESSION_ERROR; + bitbuf.flush(); + + // encode the data using the trees + for (UINT32 sy = 0; sy < height; sy++) + { + m_ycontext.flush_rle(); + m_cbcontext.flush_rle(); + m_crcontext.flush_rle(); + for (UINT32 sx = 0; sx < width / 2; sx++) + { + m_ycontext.encode_one(bitbuf, yrle); + m_cbcontext.encode_one(bitbuf, cbrle); + m_ycontext.encode_one(bitbuf, yrle); + m_crcontext.encode_one(bitbuf, crrle); + } + } + + // set the final length + complength = bitbuf.flush(); + return AVHERR_NONE; +} + + + +//************************************************************************** +// DELTA-RLE ENCODER +//************************************************************************** + +//------------------------------------------------- +// rle_and_histo_bitmap - RLE compress and +// histogram a bitmap's worth of data +//------------------------------------------------- + +UINT16 *avhuff_encoder::deltarle_encoder::rle_and_histo_bitmap(const UINT8 *source, UINT32 items_per_row, UINT32 item_advance, UINT32 row_count) +{ + // resize our RLE buffer + m_rlebuffer.resize(items_per_row * row_count); + UINT16 *dest = m_rlebuffer; + + // iterate over rows + m_encoder.histo_reset(); + UINT8 prevdata = 0; + for (UINT32 row = 0; row < row_count; row++) + { + const UINT8 *end = source + items_per_row * item_advance; + for ( ; source < end; source += item_advance) + { + // fetch current data + UINT8 curdelta = *source - prevdata; + prevdata = *source; + + // 0 deltas scan forward for a count + if (curdelta == 0) + { + int zerocount = 1; + + // count the number of consecutive values + const UINT8 *scandata; + for (scandata = source + item_advance; scandata < end; scandata += item_advance) + if (*scandata == prevdata) + zerocount++; + else + break; + + // if we hit the end of a row, maximize the count + if (scandata >= end && zerocount >= 8) + zerocount = 100000; + + // encode the maximal count we can + int rlecode = rlecount_to_code(zerocount); + m_encoder.histo_one(*dest++ = rlecode); + + // advance past the run + source += (code_to_rlecount(rlecode) - 1) * item_advance; + } + + // otherwise, encode the actual data + else + m_encoder.histo_one(*dest++ = curdelta); + } + + // advance to the next row + source = end; + } + + // compute the tree for our histogram + m_encoder.compute_tree_from_histo(); + return m_rlebuffer; +} + + + +//************************************************************************** +// AVHUFF DECODER +//************************************************************************** + +//------------------------------------------------- +// avhuff_decoder - constructor +//------------------------------------------------- + +avhuff_decoder::avhuff_decoder() +{ +} + + +//------------------------------------------------- +// configure - configure decompression parameters +//------------------------------------------------- + +void avhuff_decoder::configure(const avhuff_decompress_config &config) +{ + m_config.video.wrap(config.video, config.video.cliprect()); + m_config.maxsamples = config.maxsamples; + m_config.actsamples = config.actsamples; + memcpy(m_config.audio, config.audio, sizeof(m_config.audio)); + m_config.maxmetalength = config.maxmetalength; + m_config.actmetalength = config.actmetalength; + m_config.metadata = config.metadata; +} + + +//------------------------------------------------- +// decode_data - decode both audio and video from +// a raw data stream +//------------------------------------------------- + +avhuff_error avhuff_decoder::decode_data(const UINT8 *source, UINT32 complength, UINT8 *dest) +{ + // extract info from the header + if (complength < 8) + return AVHERR_INVALID_DATA; + UINT32 metasize = source[0]; + UINT32 channels = source[1]; + UINT32 samples = (source[2] << 8) + source[3]; + UINT32 width = (source[4] << 8) + source[5]; + UINT32 height = (source[6] << 8) + source[7]; + + // validate that the sizes make sense + if (complength < 10 + 2 * channels) + return AVHERR_INVALID_DATA; + UINT32 totalsize = 10 + 2 * channels; + totalsize += (source[8] << 8) | source[9]; + for (int chnum = 0; chnum < channels; chnum++) + totalsize += (source[10 + 2 * chnum] << 8) | source[11 + 2 * chnum]; + if (totalsize >= complength) + return AVHERR_INVALID_DATA; + + // starting offsets + UINT32 srcoffs = 10 + 2 * channels; + + // if we are decoding raw, set up the output parameters + UINT8 *metastart, *videostart, *audiostart[16]; + UINT32 audioxor, videoxor, videostride; + if (dest != NULL) + { + // create a header + dest[0] = 'c'; + dest[1] = 'h'; + dest[2] = 'a'; + dest[3] = 'v'; + dest[4] = metasize; + dest[5] = channels; + dest[6] = samples >> 8; + dest[7] = samples; + dest[8] = width >> 8; + dest[9] = width; + dest[10] = height >> 8; + dest[11] = height; + dest += 12; + + // determine the start of each piece of data + metastart = dest; + dest += metasize; + for (int chnum = 0; chnum < channels; chnum++) + { + audiostart[chnum] = dest; + dest += 2 * samples; + } + videostart = dest; + + // data is assumed to be big-endian already + audioxor = videoxor = 0; + videostride = 2 * width; + } + + // otherwise, extract from the state + else + { + // determine the start of each piece of data + metastart = m_config.metadata; + for (int chnum = 0; chnum < channels; chnum++) + audiostart[chnum] = (UINT8 *)m_config.audio[chnum]; + videostart = (m_config.video.valid()) ? reinterpret_cast<UINT8 *>(&m_config.video.pix(0)) : NULL; + videostride = (m_config.video.valid()) ? m_config.video.rowpixels() * 2 : 0; + + // data is assumed to be native-endian + UINT16 betest = 0; + *(UINT8 *)&betest = 1; + audioxor = videoxor = (betest == 1) ? 1 : 0; + + // verify against sizes + if (m_config.video.valid() && (m_config.video.width() < width || m_config.video.height() < height)) + return AVHERR_VIDEO_TOO_LARGE; + for (int chnum = 0; chnum < channels; chnum++) + if (m_config.audio[chnum] != NULL && m_config.maxsamples < samples) + return AVHERR_AUDIO_TOO_LARGE; + if (m_config.metadata != NULL && m_config.maxmetalength < metasize) + return AVHERR_METADATA_TOO_LARGE; + + // set the output values + if (m_config.actsamples != NULL) + *m_config.actsamples = samples; + if (m_config.actmetalength != NULL) + *m_config.actmetalength = metasize; + } + + // copy the metadata first + if (metasize > 0) + { + if (metastart != NULL) + memcpy(metastart, source + srcoffs, metasize); + srcoffs += metasize; + } + + // decode the audio channels + if (channels > 0) + { + // decode the audio + avhuff_error err = decode_audio(channels, samples, source + srcoffs, audiostart, audioxor, &source[8]); + if (err != AVHERR_NONE) + return err; + + // advance the pointers past the data + UINT32 treesize = (source[8] << 8) + source[9]; + if (treesize != 0xffff) + srcoffs += treesize; + for (int chnum = 0; chnum < channels; chnum++) + srcoffs += (source[10 + 2 * chnum] << 8) + source[11 + 2 * chnum]; + } + + // decode the video data + if (width > 0 && height > 0 && videostart != NULL) + { + // decode the video + avhuff_error err = decode_video(width, height, source + srcoffs, complength - srcoffs, videostart, videostride, videoxor); + if (err != AVHERR_NONE) + return err; + } + return AVHERR_NONE; +} + + +//------------------------------------------------- +// decode_audio - decode audio from a compressed +// data stream +//------------------------------------------------- + +avhuff_error avhuff_decoder::decode_audio(int channels, int samples, const UINT8 *source, UINT8 **dest, UINT32 dxor, const UINT8 *sizes) +{ + // extract the huffman trees + UINT16 treesize = (sizes[0] << 8) | sizes[1]; + +#if AVHUFF_USE_FLAC + + // if the tree size is 0xffff, the streams are FLAC-encoded + if (treesize == 0xffff) + { + // output data is big-endian; determine our platform endianness + UINT16 be_test = 0; + *(UINT8 *)&be_test = 1; + bool swap_endian = (be_test == 1); + if (dxor != 0) + swap_endian = !swap_endian; + + // loop over channels + for (int chnum = 0; chnum < channels; chnum++) + { + // extract the size of this channel + UINT16 size = (sizes[chnum * 2 + 2] << 8) | sizes[chnum * 2 + 3]; + + // only process if the data is requested + UINT8 *curdest = dest[chnum]; + if (curdest != NULL) + { + // reset and decode + if (!m_flac_decoder.reset(48000, 1, samples, source, size)) + throw CHDERR_DECOMPRESSION_ERROR; + if (!m_flac_decoder.decode_interleaved(reinterpret_cast<INT16 *>(curdest), samples, swap_endian)) + throw CHDERR_DECOMPRESSION_ERROR; + + // finish up + m_flac_decoder.finish(); + } + + // advance to the next channel's data + source += size; + } + return AVHERR_NONE; + } + +#endif + + // if we have a non-zero tree size, extract the trees + if (treesize != 0) + { + bitstream_in bitbuf(source, treesize); + huffman_error hufferr = m_audiohi_decoder.import_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_INVALID_DATA; + bitbuf.flush(); + hufferr = m_audiolo_decoder.import_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_INVALID_DATA; + if (bitbuf.flush() != treesize) + return AVHERR_INVALID_DATA; + source += treesize; + } + + // loop over channels + for (int chnum = 0; chnum < channels; chnum++) + { + // extract the size of this channel + UINT16 size = (sizes[chnum * 2 + 2] << 8) | sizes[chnum * 2 + 3]; + + // only process if the data is requested + UINT8 *curdest = dest[chnum]; + if (curdest != NULL) + { + INT16 prevsample = 0; + + // if no huffman length, just copy the data + if (treesize == 0) + { + const UINT8 *cursource = source; + for (int sampnum = 0; sampnum < samples; sampnum++) + { + INT16 delta = (cursource[0] << 8) | cursource[1]; + cursource += 2; + + INT16 newsample = prevsample + delta; + prevsample = newsample; + + curdest[0 ^ dxor] = newsample >> 8; + curdest[1 ^ dxor] = newsample; + curdest += 2; + } + } + + // otherwise, Huffman-decode the data + else + { + bitstream_in bitbuf(source, size); + for (int sampnum = 0; sampnum < samples; sampnum++) + { + INT16 delta = m_audiohi_decoder.decode_one(bitbuf) << 8; + delta |= m_audiolo_decoder.decode_one(bitbuf); + + INT16 newsample = prevsample + delta; + prevsample = newsample; + + curdest[0 ^ dxor] = newsample >> 8; + curdest[1 ^ dxor] = newsample; + curdest += 2; + } + if (bitbuf.overflow()) + return AVHERR_INVALID_DATA; + } + } + + // advance to the next channel's data + source += size; + } + return AVHERR_NONE; +} + + +//------------------------------------------------- +// decode_video - decode video from a compressed +// data stream +//------------------------------------------------- + +avhuff_error avhuff_decoder::decode_video(int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 dstride, UINT32 dxor) +{ + // if the high bit of the first byte is set, we decode losslessly + if (source[0] & 0x80) + return decode_video_lossless(width, height, source, complength, dest, dstride, dxor); + else + return AVHERR_INVALID_DATA; +} + + +//------------------------------------------------- +// decode_video_lossless - do a lossless video +// decoding using deltas and huffman encoding +//------------------------------------------------- + +avhuff_error avhuff_decoder::decode_video_lossless(int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 dstride, UINT32 dxor) +{ + // skip the first byte + bitstream_in bitbuf(source, complength); + bitbuf.read(8); + + // import the tables + huffman_error hufferr = m_ycontext.import_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_INVALID_DATA; + bitbuf.flush(); + hufferr = m_cbcontext.import_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_INVALID_DATA; + bitbuf.flush(); + hufferr = m_crcontext.import_tree_rle(bitbuf); + if (hufferr != HUFFERR_NONE) + return AVHERR_INVALID_DATA; + bitbuf.flush(); + + // decode to the destination + m_ycontext.reset(); + m_cbcontext.reset(); + m_crcontext.reset(); + for (UINT32 dy = 0; dy < height; dy++) + { + UINT8 *row = dest + dy * dstride; + for (UINT32 dx = 0; dx < width / 2; dx++) + { + row[0 ^ dxor] = m_ycontext.decode_one(bitbuf); + row[1 ^ dxor] = m_cbcontext.decode_one(bitbuf); + row[2 ^ dxor] = m_ycontext.decode_one(bitbuf); + row[3 ^ dxor] = m_crcontext.decode_one(bitbuf); + row += 4; + } + m_ycontext.flush_rle(); + m_cbcontext.flush_rle(); + m_crcontext.flush_rle(); + } + + // check for errors if we overflowed or decoded too little data + if (bitbuf.overflow() || bitbuf.flush() != complength) + return AVHERR_INVALID_DATA; + return AVHERR_NONE; +} diff --git a/src/lib/util/avhuff.h b/src/lib/util/avhuff.h new file mode 100644 index 00000000000..6b192d3338e --- /dev/null +++ b/src/lib/util/avhuff.h @@ -0,0 +1,231 @@ +/*************************************************************************** + + avhuff.h + + Audio/video compression and decompression helpers. + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#pragma once + +#ifndef __AVHUFF_H__ +#define __AVHUFF_H__ + +#include "osdcore.h" +#include "coretmpl.h" +#include "bitmap.h" +#include "huffman.h" +#include "flac.h" + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +#define AVHUFF_USE_FLAC (1) + + +// errors +enum avhuff_error +{ + AVHERR_NONE = 0, + AVHERR_INVALID_DATA, + AVHERR_VIDEO_TOO_LARGE, + AVHERR_AUDIO_TOO_LARGE, + AVHERR_METADATA_TOO_LARGE, + AVHERR_OUT_OF_MEMORY, + AVHERR_COMPRESSION_ERROR, + AVHERR_TOO_MANY_CHANNELS, + AVHERR_INVALID_CONFIGURATION, + AVHERR_INVALID_PARAMETER, + AVHERR_BUFFER_TOO_SMALL +}; + + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> av_codec_decompress_config + +// decompression configuration +class avhuff_decompress_config +{ +public: + avhuff_decompress_config() + : maxsamples(0), + actsamples(NULL), + maxmetalength(0), + actmetalength(NULL), + metadata(NULL) + { + memset(audio, 0, sizeof(audio)); + } + + bitmap_yuy16 video; // pointer to video bitmap + UINT32 maxsamples; // maximum number of samples per channel + UINT32 * actsamples; // actual number of samples per channel + INT16 * audio[16]; // pointer to individual audio channels + UINT32 maxmetalength; // maximum length of metadata + UINT32 * actmetalength; // actual length of metadata + UINT8 * metadata; // pointer to metadata buffer +}; + + +// ======================> avhuff_encoder + +// core state for the codec +class avhuff_encoder +{ +public: + // construction/destruction + avhuff_encoder(); + + // encode/decode + avhuff_error encode_data(const UINT8 *source, UINT8 *dest, UINT32 &complength); + + // static helpers + static UINT32 raw_data_size(const UINT8 *data); + static UINT32 raw_data_size(UINT32 width, UINT32 height, UINT8 channels, UINT32 numsamples, UINT32 metadatasize = 0) { return 12 + channels * numsamples * 2 + width * height * 2; } + static avhuff_error assemble_data(UINT8 *dest, UINT32 dlength, bitmap_yuy16 &bitmap, UINT8 channels, UINT32 numsamples, INT16 **samples, UINT8 *metadata = NULL, UINT32 metadatasize = 0); + +private: + // delta-RLE Huffman encoder + class deltarle_encoder + { + public: + // construction/destruction + deltarle_encoder() + : m_rlecount(0) { } + + // histogramming + UINT16 *rle_and_histo_bitmap(const UINT8 *source, UINT32 items_per_row, UINT32 item_advance, UINT32 row_count); + + // encoding + void flush_rle() { m_rlecount = 0; } + void encode_one(bitstream_out &bitbuf, UINT16 *&rleptr); + huffman_error export_tree_rle(bitstream_out &bitbuf) { return m_encoder.export_tree_rle(bitbuf); } + + private: + // internal state + int m_rlecount; + huffman_encoder<256 + 16> m_encoder; + dynamic_array<UINT16> m_rlebuffer; + }; + + // internal helpers + avhuff_error encode_audio(const UINT8 *source, int channels, int samples, UINT8 *dest, UINT8 *sizes); + avhuff_error encode_video(const UINT8 *source, int width, int height, UINT8 *dest, UINT32 &complength); + avhuff_error encode_video_lossless(const UINT8 *source, int width, int height, UINT8 *dest, UINT32 &complength); + + // video encoding contexts + deltarle_encoder m_ycontext; + deltarle_encoder m_cbcontext; + deltarle_encoder m_crcontext; + + // audio encoding contexts + dynamic_buffer m_audiobuffer; +#if AVHUFF_USE_FLAC + flac_encoder m_flac_encoder; +#else + huffman_8bit_encoder m_audiohi_encoder; + huffman_8bit_encoder m_audiolo_encoder; +#endif +}; + + +// ======================> avhuff_decoder + +// core state for the codec +class avhuff_decoder +{ +public: + // construction/destruction + avhuff_decoder(); + + // configuration + void configure(const avhuff_decompress_config &config); + + // encode/decode + avhuff_error decode_data(const UINT8 *source, UINT32 complength, UINT8 *dest); + +private: + // delta-RLE Huffman decoder + class deltarle_decoder + { + public: + // construction/destruction + deltarle_decoder() + : m_rlecount(0), m_prevdata(0) { } + + // general + void reset() { m_rlecount = m_prevdata = 0; } + + // decoding + void flush_rle() { m_rlecount = 0; } + UINT32 decode_one(bitstream_in &bitbuf); + huffman_error import_tree_rle(bitstream_in &bitbuf) { return m_decoder.import_tree_rle(bitbuf); } + + private: + // internal state + int m_rlecount; + UINT8 m_prevdata; + huffman_decoder<256 + 16> m_decoder; + }; + + + // internal helpers + avhuff_error decode_audio(int channels, int samples, const UINT8 *source, UINT8 **dest, UINT32 dxor, const UINT8 *sizes); + avhuff_error decode_video(int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 dstride, UINT32 dxor); + avhuff_error decode_video_lossless(int width, int height, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 dstride, UINT32 dxor); + + // internal state + avhuff_decompress_config m_config; + + // video decoding contexts + deltarle_decoder m_ycontext; + deltarle_decoder m_cbcontext; + deltarle_decoder m_crcontext; + + // audio decoding contexts + huffman_8bit_decoder m_audiohi_decoder; + huffman_8bit_decoder m_audiolo_decoder; +#if AVHUFF_USE_FLAC + flac_decoder m_flac_decoder; +#endif +}; + + +#endif diff --git a/src/lib/util/aviio.c b/src/lib/util/aviio.c index 33dd799fcf7..d8c7edaf649 100644 --- a/src/lib/util/aviio.c +++ b/src/lib/util/aviio.c @@ -1573,7 +1573,7 @@ static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk { const UINT8 *base = &chunkdata[24 + entry * 4 * longs_per_entry]; UINT32 offset = fetch_32bits(&base[0]); - UINT32 size = fetch_32bits(&base[4]); + UINT32 size = fetch_32bits(&base[4]) & 0x7fffffff; // bit 31 == NOT a keyframe /* set the info for this chunk and advance */ avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset - 8, size + 8); diff --git a/src/lib/util/bitmap.c b/src/lib/util/bitmap.c index a178e43beea..e8dffaa6b58 100644 --- a/src/lib/util/bitmap.c +++ b/src/lib/util/bitmap.c @@ -271,7 +271,7 @@ void bitmap_t::wrap(void *base, int width, int height, int rowpixels) // bitmap does not own the memory //------------------------------------------------- -void bitmap_t::wrap(bitmap_t &source, const rectangle &subrect) +void bitmap_t::wrap(const bitmap_t &source, const rectangle &subrect) { assert(m_format == source.m_format); assert(m_bpp == source.m_bpp); diff --git a/src/lib/util/bitmap.h b/src/lib/util/bitmap.h index b6ae74b5a80..cbbc9191b4e 100644 --- a/src/lib/util/bitmap.h +++ b/src/lib/util/bitmap.h @@ -177,7 +177,7 @@ public: protected: // for use by subclasses only to ensure type correctness void wrap(void *base, int width, int height, int rowpixels); - void wrap(bitmap_t &source, const rectangle &subrect); + void wrap(const bitmap_t &source, const rectangle &subrect); private: // internal helpers @@ -306,7 +306,7 @@ public: bitmap_ind8(UINT8 *base, int width, int height, int rowpixels) : bitmap8_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_ind8(bitmap_ind8 &source, const rectangle &subrect) : bitmap8_t(k_bitmap_format, source, subrect) { } void wrap(UINT8 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } @@ -323,7 +323,7 @@ public: bitmap_ind16(UINT16 *base, int width, int height, int rowpixels) : bitmap16_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_ind16(bitmap_ind16 &source, const rectangle &subrect) : bitmap16_t(k_bitmap_format, source, subrect) { } void wrap(UINT16 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } @@ -340,7 +340,7 @@ public: bitmap_ind32(UINT32 *base, int width, int height, int rowpixels) : bitmap32_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_ind32(bitmap_ind32 &source, const rectangle &subrect) : bitmap32_t(k_bitmap_format, source, subrect) { } void wrap(UINT32 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } @@ -357,7 +357,7 @@ public: bitmap_ind64(UINT64 *base, int width, int height, int rowpixels) : bitmap64_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_ind64(bitmap_ind64 &source, const rectangle &subrect) : bitmap64_t(k_bitmap_format, source, subrect) { } void wrap(UINT64 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_ind8 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } @@ -377,7 +377,7 @@ public: bitmap_yuy16(UINT16 *base, int width, int height, int rowpixels) : bitmap16_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_yuy16(bitmap_yuy16 &source, const rectangle &subrect) : bitmap16_t(k_bitmap_format, source, subrect) { } void wrap(UINT16 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_yuy16 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_yuy16 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } @@ -394,7 +394,7 @@ public: bitmap_rgb32(UINT32 *base, int width, int height, int rowpixels) : bitmap32_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_rgb32(bitmap_rgb32 &source, const rectangle &subrect) : bitmap32_t(k_bitmap_format, source, subrect) { } void wrap(UINT32 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_rgb32 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_rgb32 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } @@ -411,7 +411,7 @@ public: bitmap_argb32(UINT32 *base, int width, int height, int rowpixels) : bitmap32_t(k_bitmap_format, base, width, height, rowpixels) { } bitmap_argb32(bitmap_argb32 &source, const rectangle &subrect) : bitmap32_t(k_bitmap_format, source, subrect) { } void wrap(UINT32 *base, int width, int height, int rowpixels) { bitmap_t::wrap(base, width, height, rowpixels); } - void wrap(bitmap_argb32 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<bitmap_t &>(source), subrect); } + void wrap(const bitmap_argb32 &source, const rectangle &subrect) { bitmap_t::wrap(static_cast<const bitmap_t &>(source), subrect); } // getters bitmap_format format() const { return k_bitmap_format; } diff --git a/src/lib/util/bitstream.h b/src/lib/util/bitstream.h new file mode 100644 index 00000000000..d95cf6c6bd1 --- /dev/null +++ b/src/lib/util/bitstream.h @@ -0,0 +1,265 @@ +/*************************************************************************** + + bitstream.h + + Helper classes for reading/writing at the bit level. + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#pragma once + +#ifndef __BITSTREAM_H__ +#define __BITSTREAM_H__ + +#include "osdcore.h" + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// helper class for reading from a bit buffer +class bitstream_in +{ +public: + // construction/destruction + bitstream_in(const void *src, UINT32 srclength); + + // getters + bool overflow() const { return ((m_doffset - m_bits / 8) > m_dlength); } + UINT32 read_offset() const; + + // operations + UINT32 read(int numbits); + UINT32 peek(int numbits); + void remove(int numbits); + UINT32 flush(); + +private: + // internal state + UINT32 m_buffer; // current bit accumulator + int m_bits; // number of bits in the accumulator + const UINT8 * m_read; // read pointer + UINT32 m_doffset; // byte offset within the data + UINT32 m_dlength; // length of the data +}; + + +// helper class for writing to a bit buffer +class bitstream_out +{ +public: + // construction/destruction + bitstream_out(void *dest, UINT32 destlength); + + // getters + bool overflow() const { return (m_doffset > m_dlength); } + + // operations + void write(UINT32 newbits, int numbits); + UINT32 flush(); + +private: + // internal state + UINT32 m_buffer; // current bit accumulator + int m_bits; // number of bits in the accumulator + UINT8 * m_write; // write pointer + UINT32 m_doffset; // byte offset within the data + UINT32 m_dlength; // length of the data +}; + + + +//************************************************************************** +// INLINE FUNCTIONS +//************************************************************************** + +//------------------------------------------------- +// bitstream_in - constructor +//------------------------------------------------- + +inline bitstream_in::bitstream_in(const void *src, UINT32 srclength) + : m_buffer(NULL), + m_bits(0), + m_read(reinterpret_cast<const UINT8 *>(src)), + m_doffset(0), + m_dlength(srclength) +{ +} + + +//------------------------------------------------- +// peek - fetch the requested number of bits +// but don't advance the input pointer +//------------------------------------------------- + +inline UINT32 bitstream_in::peek(int numbits) +{ + // fetch data if we need more + if (numbits > m_bits) + { + while (m_bits <= 24) + { + if (m_doffset < m_dlength) + m_buffer |= m_read[m_doffset] << (24 - m_bits); + m_doffset++; + m_bits += 8; + } + } + + // return the data + return m_buffer >> (32 - numbits); +} + + +//------------------------------------------------- +// remove - advance the input pointer by the +// specified number of bits +//------------------------------------------------- + +inline void bitstream_in::remove(int numbits) +{ + m_buffer <<= numbits; + m_bits -= numbits; +} + + +//------------------------------------------------- +// read - fetch the requested number of bits +//------------------------------------------------- + +inline UINT32 bitstream_in::read(int numbits) +{ + UINT32 result = peek(numbits); + remove(numbits); + return result; +} + + +//------------------------------------------------- +// read_offset - return the current read offset +//------------------------------------------------- + +inline UINT32 bitstream_in::read_offset() const +{ + UINT32 result = m_doffset; + int bits = m_bits; + while (bits >= 8) + { + result--; + bits -= 8; + } + return result; +} + + +//------------------------------------------------- +// flush - flush to the nearest byte +//------------------------------------------------- + +inline UINT32 bitstream_in::flush() +{ + while (m_bits >= 8) + { + m_doffset--; + m_bits -= 8; + } + m_bits = m_buffer = 0; + return m_doffset; +} + + +//------------------------------------------------- +// bitstream_out - constructor +//------------------------------------------------- + +inline bitstream_out::bitstream_out(void *dest, UINT32 destlength) + : m_buffer(0), + m_bits(0), + m_write(reinterpret_cast<UINT8 *>(dest)), + m_doffset(0), + m_dlength(destlength) +{ +} + + + +//------------------------------------------------- +// write - write the given number of bits to the +// data stream +//------------------------------------------------- + +inline void bitstream_out::write(UINT32 newbits, int numbits) +{ + // flush the buffer if we're going to overflow it + if (m_bits + numbits > 32) + while (m_bits >= 8) + { + if (m_doffset < m_dlength) + m_write[m_doffset] = m_buffer >> 24; + m_doffset++; + m_buffer <<= 8; + m_bits -= 8; + } + + // shift the bits to the top + newbits <<= 32 - numbits; + + // now shift it down to account for the number of bits we already have and OR them in + m_buffer |= newbits >> m_bits; + m_bits += numbits; +} + + +//------------------------------------------------- +// flush - output remaining bits and return the +// final output size in bytes +//------------------------------------------------- + +inline UINT32 bitstream_out::flush() +{ + while (m_bits > 0) + { + if (m_doffset < m_dlength) + m_write[m_doffset] = m_buffer >> 24; + m_doffset++; + m_buffer <<= 8; + m_bits -= 8; + } + m_bits = m_buffer = 0; + return m_doffset; +} + + +#endif diff --git a/src/lib/util/cdrom.c b/src/lib/util/cdrom.c index 17d6bf75c12..c14aa692f3a 100644 --- a/src/lib/util/cdrom.c +++ b/src/lib/util/cdrom.c @@ -40,7 +40,7 @@ IMPORTANT: "physical" block addresses are the actual addresses on the emulated CD. "chd" block addresses are the block addresses in the CHD file. - Because we pad each track to a hunk boundry, these addressing + Because we pad each track to a 4-frame boundry, these addressing schemes will differ after track 1! ***************************************************************************/ @@ -75,23 +75,12 @@ struct _cdrom_file chd_file * chd; /* CHD file */ cdrom_toc cdtoc; /* TOC for the CD */ chdcd_track_input_info track_info; /* track info */ - UINT32 hunksectors; /* sectors per hunk */ - UINT32 cachehunk; /* which hunk is cached */ - UINT8 * cache; /* cache of the current hunk */ core_file * fhandle[CD_MAX_TRACKS];/* file handle */ }; /*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ - -static chd_error read_sector_into_cache(cdrom_file *file, UINT32 lbasector, UINT32 *sectoroffs, UINT32 *tracknum); - - - -/*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ @@ -100,7 +89,7 @@ static chd_error read_sector_into_cache(cdrom_file *file, UINT32 lbasector, UINT and the track number -------------------------------------------------*/ -INLINE UINT32 physical_to_chd_lba(cdrom_file *file, UINT32 physlba, UINT32 *tracknum) +INLINE UINT32 physical_to_chd_lba(cdrom_file *file, UINT32 physlba, UINT32 &tracknum) { UINT32 chdlba; int track; @@ -110,8 +99,7 @@ INLINE UINT32 physical_to_chd_lba(cdrom_file *file, UINT32 physlba, UINT32 *trac if (physlba < file->cdtoc.tracks[track + 1].physframeofs) { chdlba = physlba - file->cdtoc.tracks[track].physframeofs + file->cdtoc.tracks[track].chdframeofs; - if (tracknum != NULL) - *tracknum = track; + tracknum = track; return chdlba; } @@ -136,31 +124,29 @@ cdrom_file *cdrom_open(const char *inputfile) return NULL; /* setup the CDROM module and get the disc info */ - chd_error err = chdcd_parse_toc(inputfile, &file->cdtoc, &file->track_info); + chd_error err = chdcd_parse_toc(inputfile, file->cdtoc, file->track_info); if (err != CHDERR_NONE) { - fprintf(stderr, "Error reading input file: %s\n", chd_error_string(err)); + fprintf(stderr, "Error reading input file: %s\n", chd_file::error_string(err)); return NULL; } /* fill in the data */ file->chd = NULL; - file->hunksectors = 1; - file->cachehunk = -1; LOG(("CD has %d tracks\n", file->cdtoc.numtrks)); for (i = 0; i < file->cdtoc.numtrks; i++) { - file_error filerr = core_fopen(file->track_info.fname[i], OPEN_FLAG_READ, &file->fhandle[i]); + file_error filerr = core_fopen(file->track_info.track[i].fname, OPEN_FLAG_READ, &file->fhandle[i]); if (filerr != FILERR_NONE) { - fprintf(stderr, "Unable to open file: %s\n", file->track_info.fname[i]); + fprintf(stderr, "Unable to open file: %s\n", file->track_info.track[i].fname.cstr()); return NULL; } } /* calculate the starting frame for each track, keeping in mind that CHDMAN - pads tracks out with extra frames to fit hunk size boundries + pads tracks out with extra frames to fit 4-frame size boundries */ physofs = 0; for (i = 0; i < file->cdtoc.numtrks; i++) @@ -185,14 +171,6 @@ cdrom_file *cdrom_open(const char *inputfile) file->cdtoc.tracks[i].physframeofs = physofs; file->cdtoc.tracks[i].chdframeofs = 0; - /* allocate a cache */ - file->cache = (UINT8 *)malloc(CD_FRAME_SIZE); - if (file->cache == NULL) - { - free(file); - return NULL; - } - return file; } @@ -203,7 +181,6 @@ cdrom_file *cdrom_open(const char *inputfile) cdrom_file *cdrom_open(chd_file *chd) { - const chd_header *header = chd_get_header(chd); int i; cdrom_file *file; UINT32 physofs, chdofs; @@ -214,7 +191,9 @@ cdrom_file *cdrom_open(chd_file *chd) return NULL; /* validate the CHD information */ - if (header->hunkbytes % CD_FRAME_SIZE != 0) + if (chd->hunk_bytes() % CD_FRAME_SIZE != 0) + return NULL; + if (chd->unit_bytes() != CD_FRAME_SIZE) return NULL; /* allocate memory for the CD-ROM file */ @@ -224,8 +203,6 @@ cdrom_file *cdrom_open(chd_file *chd) /* fill in the data */ file->chd = chd; - file->hunksectors = header->hunkbytes / CD_FRAME_SIZE; - file->cachehunk = -1; /* read the CD-ROM metadata */ err = cdrom_parse_metadata(chd, &file->cdtoc); @@ -238,7 +215,7 @@ cdrom_file *cdrom_open(chd_file *chd) LOG(("CD has %d tracks\n", file->cdtoc.numtrks)); /* calculate the starting frame for each track, keeping in mind that CHDMAN - pads tracks out with extra frames to fit hunk size boundries + pads tracks out with extra frames to fit 4-frame size boundries */ physofs = chdofs = 0; for (i = 0; i < file->cdtoc.numtrks; i++) @@ -265,14 +242,6 @@ cdrom_file *cdrom_open(chd_file *chd) file->cdtoc.tracks[i].physframeofs = physofs; file->cdtoc.tracks[i].chdframeofs = chdofs; - /* allocate a cache */ - file->cache = (UINT8 *)malloc(chd_get_header(chd)->hunkbytes); - if (file->cache == NULL) - { - free(file); - return NULL; - } - return file; } @@ -286,10 +255,6 @@ void cdrom_close(cdrom_file *file) if (file == NULL) return; - /* free the cache */ - if (file->cache) - free(file->cache); - if (file->chd == NULL) { for (int i = 0; i < file->cdtoc.numtrks; i++) @@ -307,6 +272,37 @@ void cdrom_close(cdrom_file *file) CORE READ ACCESS ***************************************************************************/ +chd_error read_partial_sector(cdrom_file *file, void *dest, UINT32 chdsector, UINT32 tracknum, UINT32 startoffs, UINT32 length) +{ + // if a CHD, just read + if (file->chd != NULL) + return file->chd->read_bytes(UINT64(chdsector) * UINT64(CD_FRAME_SIZE) + startoffs, dest, length); + + // else read from the appropriate file + core_file *srcfile = file->fhandle[tracknum]; + + UINT64 sourcefileoffset = file->track_info.track[tracknum].offset; + int bytespersector = file->cdtoc.tracks[tracknum].datasize + file->cdtoc.tracks[tracknum].subsize; + + sourcefileoffset += chdsector * bytespersector + startoffs; + + core_fseek(srcfile, sourcefileoffset, SEEK_SET); + core_fread(srcfile, dest, length); + + if (file->track_info.track[tracknum].swap) + { + UINT8 *buffer = (UINT8 *)dest - startoffs; + for (int swapindex = startoffs; swapindex < 2352; swapindex += 2 ) + { + int swaptemp = buffer[ swapindex ]; + buffer[ swapindex ] = buffer[ swapindex + 1 ]; + buffer[ swapindex + 1 ] = swaptemp; + } + } + return CHDERR_NONE; +} + + /*------------------------------------------------- cdrom_read_data - read one or more sectors from a CD-ROM @@ -314,31 +310,25 @@ void cdrom_close(cdrom_file *file) UINT32 cdrom_read_data(cdrom_file *file, UINT32 lbasector, void *buffer, UINT32 datatype) { - UINT32 tracktype, tracknum, sectoroffs; - chd_error err; - static const UINT8 syncbytes[12] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; - if (file == NULL) return 0; - /* cache in the sector */ - err = read_sector_into_cache(file, lbasector, §oroffs, &tracknum); - if (err != CHDERR_NONE) - return 0; + // compute CHD sector and tracknumber + UINT32 tracknum = 0; + UINT32 chdsector = physical_to_chd_lba(file, lbasector, tracknum); /* copy out the requested sector */ - tracktype = file->cdtoc.tracks[tracknum].trktype; + UINT32 tracktype = file->cdtoc.tracks[tracknum].trktype; if ((datatype == tracktype) || (datatype == CD_TRACK_RAW_DONTCARE)) { - memcpy(buffer, &file->cache[sectoroffs * CD_FRAME_SIZE], file->cdtoc.tracks[tracknum].datasize); + return (read_partial_sector(file, buffer, chdsector, tracknum, 0, file->cdtoc.tracks[tracknum].datasize) == CHDERR_NONE); } else { /* return 2048 bytes of mode 1 data from a 2352 byte mode 1 raw sector */ if ((datatype == CD_TRACK_MODE1) && (tracktype == CD_TRACK_MODE1_RAW)) { - memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + 16], 2048); - return 1; + return (read_partial_sector(file, buffer, chdsector, tracknum, 16, 2048) == CHDERR_NONE); } /* return 2352 byte mode 1 raw sector from 2048 bytes of mode 1 data */ @@ -347,34 +337,31 @@ UINT32 cdrom_read_data(cdrom_file *file, UINT32 lbasector, void *buffer, UINT32 UINT8 *bufptr = (UINT8 *)buffer; UINT32 msf = lba_to_msf(lbasector); + static const UINT8 syncbytes[12] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; memcpy(bufptr, syncbytes, 12); bufptr[12] = msf>>16; bufptr[13] = msf>>8; bufptr[14] = msf&0xff; bufptr[15] = 1; // mode 1 - memcpy(bufptr+16, &file->cache[(sectoroffs * CD_FRAME_SIZE)], 2048); LOG(("CDROM: promotion of mode1/form1 sector to mode1 raw is not complete!\n")); - return 1; + return (read_partial_sector(file, bufptr+16, chdsector, tracknum, 0, 2048) == CHDERR_NONE); } /* return 2048 bytes of mode 1 data from a mode2 form1 or raw sector */ if ((datatype == CD_TRACK_MODE1) && ((tracktype == CD_TRACK_MODE2_FORM1)||(tracktype == CD_TRACK_MODE2_RAW))) { - memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + 24], 2048); - return 1; + return (read_partial_sector(file, buffer, chdsector, tracknum, 24, 2048) == CHDERR_NONE); } /* return mode 2 2336 byte data from a 2352 byte mode 1 or 2 raw sector (skip the header) */ if ((datatype == CD_TRACK_MODE2) && ((tracktype == CD_TRACK_MODE1_RAW) || (tracktype == CD_TRACK_MODE2_RAW))) { - memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + 16], 2336); - return 1; + return (read_partial_sector(file, buffer, chdsector, tracknum, 16, 2336) == CHDERR_NONE); } LOG(("CDROM: Conversion from type %d to type %d not supported!\n", tracktype, datatype)); return 0; } - return 1; } @@ -385,20 +372,18 @@ UINT32 cdrom_read_data(cdrom_file *file, UINT32 lbasector, void *buffer, UINT32 UINT32 cdrom_read_subcode(cdrom_file *file, UINT32 lbasector, void *buffer) { - UINT32 sectoroffs, tracknum; - chd_error err; - if (file == NULL) return ~0; - /* cache in the sector */ - err = read_sector_into_cache(file, lbasector, §oroffs, &tracknum); - if (err != CHDERR_NONE) - return 0; - - /* copy out the requested data */ - memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + file->cdtoc.tracks[tracknum].datasize], file->cdtoc.tracks[tracknum].subsize); - return 1; + // compute CHD sector and tracknumber + UINT32 tracknum = 0; + UINT32 chdsector = physical_to_chd_lba(file, lbasector, tracknum); + if (file->cdtoc.tracks[tracknum].subsize == 0) + return 1; + + // read the data + chd_error err = read_partial_sector(file, buffer, chdsector, tracknum, file->cdtoc.tracks[tracknum].datasize, file->cdtoc.tracks[tracknum].subsize); + return (err == CHDERR_NONE); } @@ -420,7 +405,7 @@ UINT32 cdrom_get_track(cdrom_file *file, UINT32 frame) return ~0; /* convert to a CHD sector offset and get track information */ - physical_to_chd_lba(file, frame, &track); + physical_to_chd_lba(file, frame, track); return track; } @@ -704,81 +689,27 @@ const char *cdrom_get_subtype_string(UINT32 subtype) ***************************************************************************/ /*------------------------------------------------- - read_sector_into_cache - cache a sector at - the given physical LBA --------------------------------------------------*/ - -static chd_error read_sector_into_cache(cdrom_file *file, UINT32 lbasector, UINT32 *sectoroffs, UINT32 *tracknum) -{ - UINT32 chdsector, hunknum; - chd_error err; - - /* convert to a CHD sector offset and get track information */ - *tracknum = 0; - chdsector = physical_to_chd_lba(file, lbasector, tracknum); - hunknum = chdsector / file->hunksectors; - *sectoroffs = chdsector % file->hunksectors; - - /* if we haven't cached this hunk, read it now */ - if (file->cachehunk != hunknum) - { - if (file->chd) { - err = chd_read(file->chd, hunknum, file->cache); - if (err != CHDERR_NONE) - return err; - } else { - core_file *srcfile = file->fhandle[*tracknum]; - - UINT64 sourcefileoffset = file->track_info.offset[*tracknum]; - int bytespersector = file->cdtoc.tracks[*tracknum].datasize + file->cdtoc.tracks[*tracknum].subsize; - - sourcefileoffset += chdsector * bytespersector; - - core_fseek(srcfile, sourcefileoffset, SEEK_SET); - core_fread(srcfile, file->cache, bytespersector); - - if (file->track_info.swap[*tracknum]) - { - for (int swapindex = 0; swapindex < 2352; swapindex += 2 ) - { - int swaptemp = file->cache[ swapindex ]; - file->cache[ swapindex ] = file->cache[ swapindex + 1 ]; - file->cache[ swapindex + 1 ] = swaptemp; - } - } - } - - file->cachehunk = hunknum; - } - return CHDERR_NONE; -} - - -/*------------------------------------------------- cdrom_parse_metadata - parse metadata into the TOC structure -------------------------------------------------*/ chd_error cdrom_parse_metadata(chd_file *chd, cdrom_toc *toc) { - static UINT32 oldmetadata[CD_METADATA_WORDS], *mrp; - const chd_header *header = chd_get_header(chd); - UINT32 hunksectors = header->hunkbytes / CD_FRAME_SIZE; - char metadata[512]; + astring metadata; chd_error err; int i; /* start with no tracks */ for (toc->numtrks = 0; toc->numtrks < CD_MAX_TRACKS; toc->numtrks++) { - int tracknum = -1, frames = 0, hunks, pregap, postgap; + int tracknum = -1, frames = 0, pregap, postgap; char type[16], subtype[16], pgtype[16], pgsub[16]; cdrom_track_info *track; pregap = postgap = 0; /* fetch the metadata for this track */ - err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, toc->numtrks, metadata, sizeof(metadata), NULL, NULL, NULL); + err = chd->read_metadata(CDROM_TRACK_METADATA_TAG, toc->numtrks, metadata); if (err == CHDERR_NONE) { /* parse the metadata */ @@ -791,7 +722,7 @@ chd_error cdrom_parse_metadata(chd_file *chd, cdrom_toc *toc) } else { - err = chd_get_metadata(chd, CDROM_TRACK_METADATA2_TAG, toc->numtrks, metadata, sizeof(metadata), NULL, NULL, NULL); + err = chd->read_metadata(CDROM_TRACK_METADATA2_TAG, toc->numtrks, metadata); if (err != CHDERR_NONE) break; /* parse the metadata */ @@ -818,8 +749,8 @@ chd_error cdrom_parse_metadata(chd_file *chd, cdrom_toc *toc) /* set the frames and extra frames data */ track->frames = frames; - hunks = (frames + hunksectors - 1) / hunksectors; - track->extraframes = hunks * hunksectors - frames; + int padded = (frames + CD_TRACK_PADDING - 1) / CD_TRACK_PADDING; + track->extraframes = padded * CD_TRACK_PADDING - frames; /* set the pregap info */ track->pregap = pregap; @@ -839,12 +770,13 @@ chd_error cdrom_parse_metadata(chd_file *chd, cdrom_toc *toc) return CHDERR_NONE; /* look for old-style metadata */ - err = chd_get_metadata(chd, CDROM_OLD_METADATA_TAG, 0, oldmetadata, sizeof(oldmetadata), NULL, NULL, NULL); + dynamic_buffer oldmetadata; + err = chd->read_metadata(CDROM_OLD_METADATA_TAG, 0, oldmetadata); if (err != CHDERR_NONE) return err; /* reconstruct the TOC from it */ - mrp = &oldmetadata[0]; + UINT32 *mrp = reinterpret_cast<UINT32 *>(&oldmetadata[0]); toc->numtrks = *mrp++; for (i = 0; i < CD_MAX_TRACKS; i++) @@ -855,6 +787,12 @@ chd_error cdrom_parse_metadata(chd_file *chd, cdrom_toc *toc) toc->tracks[i].subsize = *mrp++; toc->tracks[i].frames = *mrp++; toc->tracks[i].extraframes = *mrp++; + toc->tracks[i].pregap = 0; + toc->tracks[i].postgap = 0; + toc->tracks[i].pgtype = 0; + toc->tracks[i].pgsub = 0; + toc->tracks[i].pgdatasize = 0; + toc->tracks[i].pgsubsize = 0; } /* TODO: I don't know why sometimes the data is one endian and sometimes another */ @@ -888,13 +826,13 @@ chd_error cdrom_write_metadata(chd_file *chd, const cdrom_toc *toc) /* write the metadata */ for (i = 0; i < toc->numtrks; i++) { - char metadata[512]; - sprintf(metadata, CDROM_TRACK_METADATA2_FORMAT, i + 1, cdrom_get_type_string(toc->tracks[i].trktype), + astring metadata; + metadata.format(CDROM_TRACK_METADATA2_FORMAT, i + 1, cdrom_get_type_string(toc->tracks[i].trktype), cdrom_get_subtype_string(toc->tracks[i].subtype), toc->tracks[i].frames, toc->tracks[i].pregap, cdrom_get_type_string(toc->tracks[i].pgtype), cdrom_get_subtype_string(toc->tracks[i].pgsub), toc->tracks[i].postgap); - err = chd_set_metadata(chd, CDROM_TRACK_METADATA2_TAG, i, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); + err = chd->write_metadata(CDROM_TRACK_METADATA2_TAG, i, metadata); if (err != CHDERR_NONE) return err; } diff --git a/src/lib/util/cdrom.h b/src/lib/util/cdrom.h index 3ff7c9f063c..02f032a699c 100644 --- a/src/lib/util/cdrom.h +++ b/src/lib/util/cdrom.h @@ -51,6 +51,9 @@ CONSTANTS ***************************************************************************/ +// tracks are padded to a multiple of this many frames +const UINT32 CD_TRACK_PADDING = 4; + #define CD_MAX_TRACKS (99) /* AFAIK the theoretical limit */ #define CD_MAX_SECTOR_DATA (2352) #define CD_MAX_SUBCODE_DATA (96) @@ -90,8 +93,7 @@ enum typedef struct _cdrom_file cdrom_file; -typedef struct _cdrom_track_info cdrom_track_info; -struct _cdrom_track_info +struct cdrom_track_info { /* fields used by CHDMAN and in MAME */ UINT32 trktype; /* track type */ @@ -113,8 +115,7 @@ struct _cdrom_track_info }; -typedef struct _cdrom_toc cdrom_toc; -struct _cdrom_toc +struct cdrom_toc { UINT32 numtrks; /* number of tracks */ cdrom_track_info tracks[CD_MAX_TRACKS]; 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; } diff --git a/src/lib/util/chd.h b/src/lib/util/chd.h index 6cdb7463681..743d3774b93 100644 --- a/src/lib/util/chd.h +++ b/src/lib/util/chd.h @@ -43,16 +43,20 @@ #define __CHD_H__ #include "osdcore.h" +#include "coretmpl.h" +#include "astring.h" #include "bitmap.h" #include "corefile.h" -#include "avcomp.h" +#include "hashing.h" +#include "chdcodec.h" /*************************************************************************** Compressed Hunks of Data header format. All numbers are stored in - Motorola (big-endian) byte ordering. The header is 76 (V1) or 80 (V2) - bytes long. + Motorola (big-endian) byte ordering. + + ========================================================================= V1 header: @@ -70,6 +74,21 @@ [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file [ 76] (V1 header length) + Flags: + 0x00000001 - set if this drive has a parent + 0x00000002 - set if this drive allows writes + + Compression types: + CHDCOMPRESSION_NONE = 0 + CHDCOMPRESSION_ZLIB = 1 + + V1 map format: + + [ 0] UINT64 offset : 44; // starting offset within the file + [ 0] UINT64 length : 20; // length of data; if == hunksize, data is uncompressed + + ========================================================================= + V2 header: [ 0] char tag[8]; // 'MComprHD' @@ -86,6 +105,10 @@ [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file [ 76] UINT32 seclen; // number of bytes per sector [ 80] (V2 header length) + + Flags and map format are same as V1 + + ========================================================================= V3 header: @@ -103,6 +126,23 @@ [ 80] UINT8 sha1[20]; // SHA1 checksum of raw data [100] UINT8 parentsha1[20];// SHA1 checksum of parent file [120] (V3 header length) + + Flags are the same as V1 + + Compression types: + CHDCOMPRESSION_NONE = 0 + CHDCOMPRESSION_ZLIB = 1 + CHDCOMPRESSION_ZLIB_PLUS = 2 + + V3 map format: + + [ 0] UINT64 offset; // starting offset within the file + [ 8] UINT32 crc32; // 32-bit CRC of the uncompressed data + [ 12] UINT16 length_lo; // lower 16 bits of length + [ 14] UINT8 length_hi; // upper 8 bits of length + [ 15] UINT8 flags; // flags, indicating compression info + + ========================================================================= V4 header: @@ -119,90 +159,117 @@ [ 68] UINT8 parentsha1[20];// combined raw+meta SHA1 of parent [ 88] UINT8 rawsha1[20]; // raw data SHA1 [108] (V4 header length) - - Flags: - 0x00000001 - set if this drive has a parent - 0x00000002 - set if this drive allows writes + + Flags are the same as V1 + + Compression types: + CHDCOMPRESSION_NONE = 0 + CHDCOMPRESSION_ZLIB = 1 + CHDCOMPRESSION_ZLIB_PLUS = 2 + CHDCOMPRESSION_AV = 3 + + Map format is the same as V3 + + ========================================================================= + + V5 header: + + [ 0] char tag[8]; // 'MComprHD' + [ 8] UINT32 length; // length of header (including tag and length fields) + [ 12] UINT32 version; // drive format version + [ 16] UINT32 compressors[4];// which custom compressors are used? + [ 32] UINT64 logicalbytes; // logical size of the data (in bytes) + [ 40] UINT64 mapoffset; // offset to the map + [ 48] UINT64 metaoffset; // offset to the first blob of metadata + [ 56] UINT32 hunkbytes; // number of bytes per hunk (512k maximum) + [ 60] UINT32 unitbytes; // number of bytes per unit within each hunk + [ 64] UINT8 rawsha1[20]; // raw data SHA1 + [ 84] UINT8 sha1[20]; // combined raw+meta SHA1 + [104] UINT8 parentsha1[20];// combined raw+meta SHA1 of parent + [124] (V5 header length) + + If parentsha1 != 0, we have a parent (no need for flags) + If compressors[0] == 0, we are uncompressed (including maps) + + V5 uncompressed map format: + + [ 0] UINT32 offset; // starting offset / hunk size + + V5 compressed map format header: + + [ 0] UINT32 length; // length of compressed map + [ 4] UINT48 datastart; // offset of first block + [ 10] UINT16 crc; // crc-16 of the map + [ 12] UINT8 lengthbits; // bits used to encode complength + [ 13] UINT8 hunkbits; // bits used to encode self-refs + [ 14] UINT8 parentunitbits; // bits used to encode parent unit refs + [ 15] UINT8 reserved; // future use + [ 16] (compressed header length) + + Each compressed map entry, once expanded, looks like: + + [ 0] UINT8 compression; // compression type + [ 1] UINT24 complength; // compressed length + [ 4] UINT48 offset; // offset + [ 10] UINT16 crc; // crc-16 of the data ***************************************************************************/ -/*************************************************************************** - CONSTANTS -***************************************************************************/ +//************************************************************************** +// CONSTANTS +//************************************************************************** + +// pseudo-codecs returned by hunk_info +const chd_codec_type CHD_CODEC_SELF = 1; // copy of another hunk +const chd_codec_type CHD_CODEC_PARENT = 2; // copy of a parent's hunk +const chd_codec_type CHD_CODEC_MINI = 3; // legacy "mini" 8-byte repeat + +// core types +typedef UINT32 chd_metadata_tag; + +// metadata parameters +const chd_metadata_tag CHDMETATAG_WILDCARD = 0; +const UINT32 CHDMETAINDEX_APPEND = ~0; + +// metadata flags +const UINT8 CHD_MDFLAGS_CHECKSUM = 0x01; // indicates data is checksummed -/* header information */ -#define CHD_HEADER_VERSION 4 -#define CHD_V1_HEADER_SIZE 76 -#define CHD_V2_HEADER_SIZE 80 -#define CHD_V3_HEADER_SIZE 120 -#define CHD_V4_HEADER_SIZE 108 -#define CHD_MAX_HEADER_SIZE CHD_V4_HEADER_SIZE - -/* checksumming information */ -#define CHD_MD5_BYTES 16 -#define CHD_SHA1_BYTES 20 - -/* CHD global flags */ -#define CHDFLAGS_HAS_PARENT 0x00000001 -#define CHDFLAGS_IS_WRITEABLE 0x00000002 -#define CHDFLAGS_UNDEFINED 0xfffffffc - -/* compression types */ -#define CHDCOMPRESSION_NONE 0 -#define CHDCOMPRESSION_ZLIB 1 -#define CHDCOMPRESSION_ZLIB_PLUS 2 -#define CHDCOMPRESSION_AV 3 -#define CHDCOMPRESSION_ZLIB_PLUS_WITH_FLAC 4 - -/* A/V codec configuration parameters */ -#define AV_CODEC_COMPRESS_CONFIG 1 -#define AV_CODEC_DECOMPRESS_CONFIG 2 - -/* metadata parameters */ -#define CHDMETATAG_WILDCARD 0 -#define CHD_METAINDEX_APPEND ((UINT32)-1) - -/* metadata flags */ -#define CHD_MDFLAGS_CHECKSUM 0x01 /* indicates data is checksummed */ - -/* standard hard disk metadata */ -#define HARD_DISK_METADATA_TAG 0x47444444 /* 'GDDD' */ -#define HARD_DISK_METADATA_FORMAT "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d" - -/* hard disk identify information */ -#define HARD_DISK_IDENT_METADATA_TAG 0x49444e54 /* 'IDNT' */ - -/* hard disk key information */ -#define HARD_DISK_KEY_METADATA_TAG 0x4b455920 /* 'KEY ' */ - -/* pcmcia CIS information */ -#define PCMCIA_CIS_METADATA_TAG 0x43495320 /* 'CIS ' */ - -/* standard CD-ROM metadata */ -#define CDROM_OLD_METADATA_TAG 0x43484344 /* 'CHCD' */ -#define CDROM_TRACK_METADATA_TAG 0x43485452 /* 'CHTR' */ -#define CDROM_TRACK_METADATA_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d" -#define CDROM_TRACK_METADATA2_TAG 0x43485432 /* 'CHT2' */ -#define CDROM_TRACK_METADATA2_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d" - -/* standard A/V metadata */ -#define AV_METADATA_TAG 0x41564156 /* 'AVAV' */ -#define AV_METADATA_FORMAT "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d" - -/* A/V laserdisc frame metadata */ -#define AV_LD_METADATA_TAG 0x41564C44 /* 'AVLD' */ - -/* CHD open values */ -#define CHD_OPEN_READ 1 -#define CHD_OPEN_READWRITE 2 - -/* error types */ -enum _chd_error +// standard hard disk metadata +const chd_metadata_tag HARD_DISK_METADATA_TAG = CHD_MAKE_TAG('G','D','D','D'); +extern const char *HARD_DISK_METADATA_FORMAT; + +// hard disk identify information +const chd_metadata_tag HARD_DISK_IDENT_METADATA_TAG = CHD_MAKE_TAG('I','D','N','T'); + +// hard disk key information +const chd_metadata_tag HARD_DISK_KEY_METADATA_TAG = CHD_MAKE_TAG('K','E','Y',' '); + +// pcmcia CIS information +const chd_metadata_tag PCMCIA_CIS_METADATA_TAG = CHD_MAKE_TAG('C','I','S',' '); + +// standard CD-ROM metadata +const chd_metadata_tag CDROM_OLD_METADATA_TAG = CHD_MAKE_TAG('C','H','C','D'); +const chd_metadata_tag CDROM_TRACK_METADATA_TAG = CHD_MAKE_TAG('C','H','T','R'); +extern const char *CDROM_TRACK_METADATA_FORMAT; +const chd_metadata_tag CDROM_TRACK_METADATA2_TAG = CHD_MAKE_TAG('C','H','T','2'); +extern const char *CDROM_TRACK_METADATA2_FORMAT; + +// standard A/V metadata +const chd_metadata_tag AV_METADATA_TAG = CHD_MAKE_TAG('A','V','A','V'); +extern const char *AV_METADATA_FORMAT; + +// A/V laserdisc frame metadata +const chd_metadata_tag AV_LD_METADATA_TAG = CHD_MAKE_TAG('A','V','L','D'); + +// error types +enum chd_error { CHDERR_NONE, CHDERR_NO_INTERFACE, CHDERR_OUT_OF_MEMORY, + CHDERR_NOT_OPEN, + CHDERR_ALREADY_OPEN, CHDERR_INVALID_FILE, CHDERR_INVALID_PARAMETER, CHDERR_INVALID_DATA, @@ -226,166 +293,303 @@ enum _chd_error CHDERR_INVALID_METADATA, CHDERR_INVALID_STATE, CHDERR_OPERATION_PENDING, - CHDERR_NO_ASYNC_OPERATION, - CHDERR_UNSUPPORTED_FORMAT + CHDERR_UNSUPPORTED_FORMAT, + CHDERR_UNKNOWN_COMPRESSION, + CHDERR_WALKING_PARENT, + CHDERR_COMPRESSING }; -typedef enum _chd_error chd_error; -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** -/* opaque types */ -typedef struct _chd_file chd_file; +class chd_codec; -/* extract header structure (NOT the on-disk header structure) */ -typedef struct _chd_header chd_header; -struct _chd_header -{ - UINT32 length; /* length of header data */ - UINT32 version; /* drive format version */ - UINT32 flags; /* flags field */ - UINT32 compression; /* compression type */ - UINT32 hunkbytes; /* number of bytes per hunk */ - UINT32 totalhunks; /* total # of hunks represented */ - UINT64 logicalbytes; /* logical size of the data */ - UINT64 metaoffset; /* offset in file of first metadata */ - UINT8 md5[CHD_MD5_BYTES]; /* overall MD5 checksum */ - UINT8 parentmd5[CHD_MD5_BYTES]; /* overall MD5 checksum of parent */ - UINT8 sha1[CHD_SHA1_BYTES]; /* overall SHA1 checksum */ - UINT8 rawsha1[CHD_SHA1_BYTES]; /* SHA1 checksum of raw data */ - UINT8 parentsha1[CHD_SHA1_BYTES]; /* overall SHA1 checksum of parent */ - - UINT32 obsolete_cylinders; /* obsolete field -- do not use! */ - UINT32 obsolete_sectors; /* obsolete field -- do not use! */ - UINT32 obsolete_heads; /* obsolete field -- do not use! */ - UINT32 obsolete_hunksize; /* obsolete field -- do not use! */ -}; +// ======================> chd_file - -/* structure for returning information about a verification pass */ -typedef struct _chd_verify_result chd_verify_result; -struct _chd_verify_result +// core file class +class chd_file { - UINT8 md5[CHD_MD5_BYTES]; /* overall MD5 checksum */ - UINT8 sha1[CHD_SHA1_BYTES]; /* overall SHA1 checksum */ - UINT8 rawsha1[CHD_SHA1_BYTES]; /* SHA1 checksum of raw data */ - UINT8 metasha1[CHD_SHA1_BYTES]; /* SHA1 checksum of metadata */ + friend class chd_file_compressor; + friend class chd_verifier; + + // constants + static const UINT32 HEADER_VERSION = 5; + static const UINT32 V3_HEADER_SIZE = 120; + static const UINT32 V4_HEADER_SIZE = 108; + static const UINT32 V5_HEADER_SIZE = 124; + static const UINT32 MAX_HEADER_SIZE = V5_HEADER_SIZE; + +public: + // construction/destruction + chd_file(); + virtual ~chd_file(); + + // operators + operator core_file *() { return m_file; } + + // getters + bool opened() const { return (m_file != NULL); } + UINT32 version() const { return m_version; } + UINT64 logical_bytes() const { return m_logicalbytes; } + UINT32 hunk_bytes() const { return m_hunkbytes; } + UINT32 hunk_count() const { return m_hunkcount; } + UINT32 unit_bytes() const { return m_unitbytes; } + UINT64 unit_count() const { return m_unitcount; } + bool compressed() const { return (m_compression[0] != CHD_CODEC_NONE); } + chd_codec_type compression(int index) const { return m_compression[index]; } + chd_file *parent() const { return m_parent; } + sha1_t sha1(); + sha1_t raw_sha1(); + sha1_t parent_sha1(); + chd_error hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes); + + // setters + void set_raw_sha1(sha1_t rawdata); + void set_parent_sha1(sha1_t parent); + + // file create + chd_error create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]); + chd_error create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]); + chd_error create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent); + chd_error create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent); + + // file open + chd_error open(const char *filename, bool writeable = false, chd_file *parent = NULL); + chd_error open(core_file &file, bool writeable = false, chd_file *parent = NULL); + + // file close + void close(); + + // read/write + chd_error read_hunk(UINT32 hunknum, void *buffer); + chd_error write_hunk(UINT32 hunknum, const void *buffer); + chd_error read_units(UINT64 unitnum, void *buffer, UINT32 count = 1); + chd_error write_units(UINT64 unitnum, const void *buffer, UINT32 count = 1); + chd_error read_bytes(UINT64 offset, void *buffer, UINT32 bytes); + chd_error write_bytes(UINT64 offset, const void *buffer, UINT32 bytes); + + // metadata management + chd_error read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, astring &output); + chd_error read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output); + chd_error read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen); + chd_error read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output, chd_metadata_tag &resulttag, UINT8 &resultflags); + chd_error write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags = CHD_MDFLAGS_CHECKSUM); + chd_error write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const astring &input, UINT8 flags = CHD_MDFLAGS_CHECKSUM) { return write_metadata(metatag, metaindex, input.cstr(), input.len() + 1, flags = CHD_MDFLAGS_CHECKSUM); } + chd_error write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const dynamic_buffer &input, UINT8 flags = CHD_MDFLAGS_CHECKSUM) { return write_metadata(metatag, metaindex, input, input.count(), flags = CHD_MDFLAGS_CHECKSUM); } + chd_error delete_metadata(chd_metadata_tag metatag, UINT32 metaindex); + chd_error clone_all_metadata(chd_file &source); + + // hashing helper + sha1_t compute_overall_sha1(sha1_t rawsha1); + + // codec interfaces + chd_error codec_configure(chd_codec_type codec, int param, void *config); + + // static helpers + static const char *error_string(chd_error err); + +private: + struct metadata_entry; + struct metadata_hash; + + // inline helpers + UINT64 be_read(const UINT8 *base, int numbytes); + void be_write(UINT8 *base, UINT64 value, int numbytes); + sha1_t be_read_sha1(const UINT8 *base); + void be_write_sha1(UINT8 *base, sha1_t value); + void file_read(UINT64 offset, void *dest, UINT32 length); + void file_write(UINT64 offset, const void *source, UINT32 length); + UINT64 file_append(const void *source, UINT32 length, UINT32 alignment = 0); + UINT8 bits_for_value(UINT64 value); + + // internal helpers + UINT32 guess_unitbytes(); + void parse_v3_header(UINT8 *rawheader, sha1_t &parentsha1); + void parse_v4_header(UINT8 *rawheader, sha1_t &parentsha1); + void parse_v5_header(UINT8 *rawheader, sha1_t &parentsha1); + chd_error compress_v5_map(); + void decompress_v5_map(); + chd_error create_common(); + chd_error open_common(bool writeable); + void create_open_common(); + void verify_proper_compression_append(UINT32 hunknum); + void hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, crc16_t crc16); + void hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk); + void hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit); + bool metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume = false); + void metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset); + void metadata_update_hash(); + static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2); + + // file characteristics + core_file * m_file; // handle to the open core file + bool m_owns_file; // flag indicating if this file should be closed on chd_close() + bool m_allow_reads; // permit reads from this CHD? + bool m_allow_writes; // permit writes to this CHD? + + // core parameters from the header + UINT32 m_version; // version of the header + UINT64 m_logicalbytes; // logical size of the raw CHD data in bytes + UINT64 m_mapoffset; // offset of map + UINT64 m_metaoffset; // offset to first metadata bit + UINT32 m_hunkbytes; // size of each raw hunk in bytes + UINT32 m_hunkcount; // number of hunks represented + UINT32 m_unitbytes; // size of each unit in bytes + UINT64 m_unitcount; // number of units represented + chd_codec_type m_compression[4]; // array of compression types used + chd_file * m_parent; // pointer to parent file, or NULL if none + bool m_parent_missing; // are we missing our parent? + + // key offsets within the header + UINT64 m_mapoffset_offset; // offset of map offset field + UINT64 m_metaoffset_offset;// offset of metaoffset field + UINT64 m_sha1_offset; // offset of SHA1 field + UINT64 m_rawsha1_offset; // offset of raw SHA1 field + UINT64 m_parentsha1_offset;// offset of paren SHA1 field + + // map information + UINT32 m_mapentrybytes; // length of each entry in a map + dynamic_buffer m_rawmap; // raw map data + + // compression management + chd_decompressor * m_decompressor[4]; // array of decompression codecs + dynamic_buffer m_compressed; // temporary buffer for compressed data + + // caching + dynamic_buffer m_cache; // single-hunk cache for partial reads/writes + UINT32 m_cachehunk; // which hunk is in the cache? }; +// ======================> chd_file_compressor -/*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ - - -/* ----- CHD file management ----- */ - -/* create a new CHD file fitting the given description */ -chd_error chd_create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent); - -/* same as chd_create(), but accepts an already-opened core_file object */ -chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent); - -/* open an existing CHD file */ -chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd); - -/* same as chd_open(), but accepts an already-opened core_file object */ -chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd); - -/* close a CHD file */ -void chd_close(chd_file *chd); - -/* return the associated core_file */ -core_file *chd_core_file(chd_file *chd); - -/* return an error string for the given CHD error */ -const char *chd_error_string(chd_error err); - - - -/* ----- CHD header management ----- */ - -/* return a pointer to the extracted CHD header data */ -const chd_header *chd_get_header(chd_file *chd); - -/* set a modified header */ -chd_error chd_set_header(const char *filename, const chd_header *header); - -/* same as chd_set_header(), but accepts an already-opened core_file object */ -chd_error chd_set_header_file(core_file *file, const chd_header *header); - - - -/* ----- core data read/write ----- */ - -/* read one hunk from the CHD file */ -chd_error chd_read(chd_file *chd, UINT32 hunknum, void *buffer); - -/* read one hunk from the CHD file asynchronously */ -chd_error chd_read_async(chd_file *chd, UINT32 hunknum, void *buffer); - -/* write one hunk to a CHD file */ -chd_error chd_write(chd_file *chd, UINT32 hunknum, const void *buffer); - -/* write one hunk to a CHD file asynchronously */ -chd_error chd_write_async(chd_file *chd, UINT32 hunknum, const void *buffer); - -/* wait for a previously issued async read/write to complete and return the error */ -chd_error chd_async_complete(chd_file *chd); - - - -/* ----- metadata management ----- */ - -/* get indexed metadata of a particular sort */ -chd_error chd_get_metadata(chd_file *chd, UINT32 searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 *resultlen, UINT32 *resulttag, UINT8 *resultflags); - -/* set indexed metadata of a particular sort */ -chd_error chd_set_metadata(chd_file *chd, UINT32 metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags); - -/* clone all of the metadata from one CHD to another */ -chd_error chd_clone_metadata(chd_file *source, chd_file *dest); - - - -/* ----- compression management ----- */ - -/* begin compressing data to a CHD */ -chd_error chd_compress_begin(chd_file *chd); - -/* compress the next hunk of data */ -chd_error chd_compress_hunk(chd_file *chd, const void *data, double *curratio, int is_half_hunk = 0); - -/* finish compressing data to a CHD */ -chd_error chd_compress_finish(chd_file *chd, int write_protect); - - - -/* ----- verification management ----- */ - -/* begin verifying a CHD */ -chd_error chd_verify_begin(chd_file *chd); - -/* verify a single hunk of data */ -chd_error chd_verify_hunk(chd_file *chd); - -/* finish verifying a CHD, returning the computed MD5 and SHA1 */ -chd_error chd_verify_finish(chd_file *chd, chd_verify_result *result); - - - -/* ----- codec interfaces ----- */ - -/* set internal codec parameters */ -chd_error chd_codec_config(chd_file *chd, int param, void *config); - -/* return a string description of a codec */ -const char *chd_get_codec_name(UINT32 codec); +// class for creating a new compressed CHD +class chd_file_compressor : public chd_file +{ +public: + // construction/destruction + chd_file_compressor(); + virtual ~chd_file_compressor(); + + // compression management + void compress_begin(); + chd_error compress_continue(double &progress, double &ratio); + +protected: + // required override: read more data + virtual UINT32 read_data(void *dest, UINT64 offset, UINT32 length) = 0; + +private: + // hash map for looking up values + class hashmap + { + public: + // construction/destruction + hashmap(); + ~hashmap(); + + // operations + void reset(); + UINT64 find(crc16_t crc16, sha1_t sha1); + void add(UINT64 itemnum, crc16_t crc16, sha1_t sha1); + + // constants + static const UINT64 NOT_FOUND = ~UINT64(0); + private: + // internal entry + struct entry_t + { + entry_t * m_next; // next entry in list + UINT64 m_itemnum; // item number + sha1_t m_sha1; // SHA-1 of the block + }; + + // block of entries + struct entry_block + { + entry_block(entry_block *prev) + : m_next(prev), m_nextalloc(0) { } + + entry_block * m_next; // next block in list + UINT32 m_nextalloc; // next to be allocated + entry_t m_array[16384]; // array of entries + }; + + // internal state + entry_t * m_map[65536]; // map, hashed by CRC-16 + entry_block * m_block_list; // list of allocated blocks + }; + + // status of a given work item + enum work_status + { + WS_READY = 0, + WS_READING, + WS_QUEUED, + WS_COMPLETE + }; + + // a CRC-16/SHA-1 pair + struct hash_pair + { + crc16_t m_crc16; // calculated CRC-16 + sha1_t m_sha1; // calculated SHA-1 + }; + + // a single work item + struct work_item + { + osd_work_item * m_osd; // OSD work item running on this block + chd_file_compressor *m_compressor; // pointer back to the compressor + volatile work_status m_status; // current status of this item + UINT32 m_hunknum; // number of the hunk we're working on + UINT8 * m_data; // pointer to the data we are working on + UINT8 * m_compressed; // pointer to the compressed data + UINT32 m_complen; // compressed data length + INT8 m_compression; // type of compression used + chd_compressor_group *m_codecs; // codec instance + dynamic_array<hash_pair> m_hash; // array of hashes + }; + + // internal helpers + static void *async_walk_parent_static(void *param, int threadid); + void async_walk_parent(work_item &item); + static void *async_compress_hunk_static(void *param, int threadid); + void async_compress_hunk(work_item &item, int threadid); + static void *async_read_static(void *param, int threadid); + void async_read(); + + // current compression status + bool m_walking_parent; // are we building the parent map? + UINT64 m_total_in; // total bytes in + UINT64 m_total_out; // total bytes out + sha1_creator m_compsha1; // running SHA-1 on raw data + + // hash lookup maps + hashmap m_parent_map; // hash map for parent + hashmap m_current_map; // hash map for current + + // read I/O thread + osd_work_queue * m_read_queue; // work queue for reading + UINT64 m_read_queue_offset;// next offset to enqueue + UINT64 m_read_done_offset; // next offset that will complete + bool m_read_error; // error during reading? + + // work item thread + static const int WORK_BUFFER_HUNKS = 256; + osd_work_queue * m_work_queue; // queue for doing work on other threads + dynamic_buffer m_work_buffer; // buffer containing hunk data to work on + dynamic_buffer m_compressed_buffer;// buffer containing compressed data + work_item m_work_item[WORK_BUFFER_HUNKS]; // status of each hunk + chd_compressor_group * m_codecs[WORK_MAX_THREADS]; // codecs to use + + // output state + UINT32 m_write_hunk; // next hunk to write +}; -#endif /* __CHD_H__ */ +#endif // __CHD_H__ diff --git a/src/lib/util/chdcd.c b/src/lib/util/chdcd.c index 0d8c02077d7..afd577129c4 100644 --- a/src/lib/util/chdcd.c +++ b/src/lib/util/chdcd.c @@ -304,7 +304,7 @@ UINT64 read_uint64(FILE *infile) chdcd_parse_toc - parse a CDRWin format CUE file -------------------------------------------------*/ -chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo) +chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc &outtoc, chdcd_track_input_info &outinfo) { FILE *infile; unsigned char buffer[12]; @@ -322,8 +322,8 @@ chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc *outtoc, chdcd_track_ } /* clear structures */ - memset(outtoc, 0, sizeof(cdrom_toc)); - memset(outinfo, 0, sizeof(chdcd_track_input_info)); + memset(&outtoc, 0, sizeof(outtoc)); + outinfo.reset(); // seek to 12 bytes before the end fseek(infile, -12, SEEK_END); @@ -372,7 +372,7 @@ chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc *outtoc, chdcd_track_ // printf("TOC type: %08x. Start track %d End track: %d\n", toc_type, start, end); - outtoc->numtrks = (end-start) + 1; + outtoc.numtrks = (end-start) + 1; offset = 0; for (track = start; track <= end; track++) @@ -388,27 +388,26 @@ chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc *outtoc, chdcd_track_ index2 = read_uint64(infile); // printf("Track %d: sector size %d mode %x index0 %llx index1 %llx index2 %llx (pregap %d sectors, length %d sectors)\n", track, size, mode, index0, index1, index2, (UINT32)(index1-index0)/size, (UINT32)(index2-index1)/size); - strncpy(outinfo->fname[track-1], path.cstr(), 256); - strncat(outinfo->fname[track-1], tocfname, 256); - outinfo->offset[track-1] = offset + (UINT32)(index1-index0); - outinfo->idx0offs[track-1] = 0; - outinfo->idx1offs[track-1] = 0; + outinfo.track[track-1].fname.cpy(path.cstr()).cat(tocfname); + outinfo.track[track-1].offset = offset + (UINT32)(index1-index0); + outinfo.track[track-1].idx0offs = 0; + outinfo.track[track-1].idx1offs = 0; switch (mode>>24) { case 0x00: // 2048 byte data - outtoc->tracks[track-1].trktype = CD_TRACK_MODE1; - outinfo->swap[track-1] = 0; + outtoc.tracks[track-1].trktype = CD_TRACK_MODE1; + outinfo.track[track-1].swap = false; break; case 0x06: // 2352 byte mode 2 raw - outtoc->tracks[track-1].trktype = CD_TRACK_MODE2_RAW; - outinfo->swap[track-1] = 0; + outtoc.tracks[track-1].trktype = CD_TRACK_MODE2_RAW; + outinfo.track[track-1].swap = false; break; case 0x07: // 2352 byte audio - outtoc->tracks[track-1].trktype = CD_TRACK_AUDIO; - outinfo->swap[track-1] = 1; + outtoc.tracks[track-1].trktype = CD_TRACK_AUDIO; + outinfo.track[track-1].swap = true; break; default: @@ -416,18 +415,18 @@ chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc *outtoc, chdcd_track_ break; } - outtoc->tracks[track-1].datasize = size; + outtoc.tracks[track-1].datasize = size; - outtoc->tracks[track-1].subtype = CD_SUB_NONE; - outtoc->tracks[track-1].subsize = 0; + outtoc.tracks[track-1].subtype = CD_SUB_NONE; + outtoc.tracks[track-1].subsize = 0; - outtoc->tracks[track-1].pregap = (UINT32)(index1-index0)/size; - outtoc->tracks[track-1].frames = (UINT32)(index2-index1)/size; - outtoc->tracks[track-1].postgap = 0; - outtoc->tracks[track-1].pgtype = 0; - outtoc->tracks[track-1].pgsub = CD_SUB_NONE; - outtoc->tracks[track-1].pgdatasize = 0; - outtoc->tracks[track-1].pgsubsize = 0; + outtoc.tracks[track-1].pregap = (UINT32)(index1-index0)/size; + outtoc.tracks[track-1].frames = (UINT32)(index2-index1)/size; + outtoc.tracks[track-1].postgap = 0; + outtoc.tracks[track-1].pgtype = 0; + outtoc.tracks[track-1].pgsub = CD_SUB_NONE; + outtoc.tracks[track-1].pgdatasize = 0; + outtoc.tracks[track-1].pgsubsize = 0; offset += (UINT32)index2-index1; } @@ -452,7 +451,7 @@ chd_error chdcd_parse_nero(const char *tocfname, cdrom_toc *outtoc, chdcd_track_ chdcd_parse_gdi - parse a Sega GD-ROM rip -------------------------------------------------*/ -static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo) +static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc &outtoc, chdcd_track_input_info &outinfo) { FILE *infile; int i, numtracks; @@ -469,8 +468,8 @@ static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc *outtoc, chdcd_ } /* clear structures */ - memset(outtoc, 0, sizeof(cdrom_toc)); - memset(outinfo, 0, sizeof(chdcd_track_input_info)); + memset(&outtoc, 0, sizeof(outtoc)); + outinfo.reset(); fgets(linebuffer,511,infile); @@ -491,16 +490,16 @@ static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc *outtoc, chdcd_ trknum=atoi(tok)-1; - outinfo->swap[trknum]=0; - outinfo->offset[trknum]=0; + outinfo.track[trknum].swap=false; + outinfo.track[trknum].offset=0; - //outtoc->tracks[trknum].trktype = CD_TRACK_MODE1; - outtoc->tracks[trknum].datasize = 0; - outtoc->tracks[trknum].subtype = CD_SUB_NONE; - outtoc->tracks[trknum].subsize = 0; + //outtoc.tracks[trknum].trktype = CD_TRACK_MODE1; + outtoc.tracks[trknum].datasize = 0; + outtoc.tracks[trknum].subtype = CD_SUB_NONE; + outtoc.tracks[trknum].subsize = 0; tok=strtok(NULL," "); - outtoc->tracks[trknum].physframeofs=atoi(tok); + outtoc.tracks[trknum].physframeofs=atoi(tok); tok=strtok(NULL," "); trktype=atoi(tok); @@ -510,19 +509,19 @@ static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc *outtoc, chdcd_ if(trktype==4 && trksize==2352) { - outtoc->tracks[trknum].trktype=CD_TRACK_MODE1_RAW; - outtoc->tracks[trknum].datasize=2352; + outtoc.tracks[trknum].trktype=CD_TRACK_MODE1_RAW; + outtoc.tracks[trknum].datasize=2352; } if(trktype==4 && trksize==2048) { - outtoc->tracks[trknum].trktype=CD_TRACK_MODE1; - outtoc->tracks[trknum].datasize=2048; + outtoc.tracks[trknum].trktype=CD_TRACK_MODE1; + outtoc.tracks[trknum].datasize=2048; } if(trktype==0) { //assert(trksize==2352); - outtoc->tracks[trknum].trktype=CD_TRACK_AUDIO; - outtoc->tracks[trknum].datasize=2352; + outtoc.tracks[trknum].trktype=CD_TRACK_AUDIO; + outtoc.tracks[trknum].datasize=2352; } astring name; @@ -539,43 +538,42 @@ static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc *outtoc, chdcd_ } while(tok!=NULL && (strrchr(tok,'"')-tok !=(strlen(tok)-1))); name = name.delchr('"'); } - strncpy(outinfo->fname[trknum], path.cstr(), 256); - strncat(outinfo->fname[trknum], name, 256); + outinfo.track[trknum].fname.cpy(path.cstr()).cat(name); - sz=get_file_size(outinfo->fname[trknum]); + sz=get_file_size(outinfo.track[trknum].fname); - outtoc->tracks[trknum].frames=sz/trksize; - outtoc->tracks[trknum].extraframes=0; + outtoc.tracks[trknum].frames=sz/trksize; + outtoc.tracks[trknum].extraframes=0; if(trknum!=0) { - int dif=outtoc->tracks[trknum].physframeofs-(outtoc->tracks[trknum-1].frames+outtoc->tracks[trknum-1].physframeofs); - outtoc->tracks[trknum-1].frames+=dif; + int dif=outtoc.tracks[trknum].physframeofs-(outtoc.tracks[trknum-1].frames+outtoc.tracks[trknum-1].physframeofs); + outtoc.tracks[trknum-1].frames+=dif; } /* if(trknum!=0) { - outtoc->tracks[trknum-1].extraframes=outtoc->tracks[trknum].physframeofs-(outtoc->tracks[trknum-1].frames+outtoc->tracks[trknum-1].physframeofs); + outtoc.tracks[trknum-1].extraframes=outtoc.tracks[trknum].physframeofs-(outtoc.tracks[trknum-1].frames+outtoc.tracks[trknum-1].physframeofs); } */ - hunks = (outtoc->tracks[trknum].frames+CD_FRAMES_PER_HUNK - 1) / CD_FRAMES_PER_HUNK; - outtoc->tracks[trknum].extraframes = hunks * CD_FRAMES_PER_HUNK - outtoc->tracks[trknum].frames; + hunks = (outtoc.tracks[trknum].frames+CD_FRAMES_PER_HUNK - 1) / CD_FRAMES_PER_HUNK; + outtoc.tracks[trknum].extraframes = hunks * CD_FRAMES_PER_HUNK - outtoc.tracks[trknum].frames; - //chdpos+=outtoc->tracks[trknum].frames+outtoc->tracks[trknum].extraframes; + //chdpos+=outtoc.tracks[trknum].frames+outtoc.tracks[trknum].extraframes; } /* for(i=0;i<numtracks;++i) { - printf("%s %d %d %d\n",outinfo->fname[i],outtoc->tracks[i].frames,outtoc->tracks[i].extraframes,outtoc->tracks[i].physframeofs); + printf("%s %d %d %d\n",outinfo.track[i].fname,outtoc.tracks[i].frames,outtoc.tracks[i].extraframes,outtoc.tracks[i].physframeofs); } */ /* close the input TOC */ fclose(infile); /* store the number of tracks found */ - outtoc->numtrks = numtracks; + outtoc.numtrks = numtracks; return CHDERR_NONE; } @@ -584,7 +582,7 @@ static chd_error chdcd_parse_gdi(const char *tocfname, cdrom_toc *outtoc, chdcd_ chdcd_parse_toc - parse a CDRWin format CUE file -------------------------------------------------*/ -chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo) +chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc &outtoc, chdcd_track_input_info &outinfo) { FILE *infile; int i, trknum; @@ -601,8 +599,8 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i } /* clear structures */ - memset(outtoc, 0, sizeof(cdrom_toc)); - memset(outinfo, 0, sizeof(chdcd_track_input_info)); + memset(&outtoc, 0, sizeof(outtoc)); + outinfo.reset(); trknum = -1; wavoffs = wavlen = 0; @@ -633,11 +631,11 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i if (!strcmp(token, "BINARY")) { - outinfo->swap[trknum] = 0; + outinfo.track[trknum].swap = false; } else if (!strcmp(token, "MOTOROLA")) { - outinfo->swap[trknum] = 1; + outinfo.track[trknum].swap = true; } else if (!strcmp(token, "WAVE")) { @@ -672,28 +670,28 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i if (wavlen != 0) { - outtoc->tracks[trknum].trktype = CD_TRACK_AUDIO; - outtoc->tracks[trknum].frames = wavlen/2352; - outinfo->offset[trknum] = wavoffs; + outtoc.tracks[trknum].trktype = CD_TRACK_AUDIO; + outtoc.tracks[trknum].frames = wavlen/2352; + outinfo.track[trknum].offset = wavoffs; wavoffs = wavlen = 0; } else { - outtoc->tracks[trknum].trktype = CD_TRACK_MODE1; - outtoc->tracks[trknum].datasize = 0; - outinfo->offset[trknum] = 0; + outtoc.tracks[trknum].trktype = CD_TRACK_MODE1; + outtoc.tracks[trknum].datasize = 0; + outinfo.track[trknum].offset = 0; } - outtoc->tracks[trknum].subtype = CD_SUB_NONE; - outtoc->tracks[trknum].subsize = 0; - outtoc->tracks[trknum].pregap = 0; - outinfo->idx0offs[trknum] = -1; - outinfo->idx1offs[trknum] = 0; + outtoc.tracks[trknum].subtype = CD_SUB_NONE; + outtoc.tracks[trknum].subsize = 0; + outtoc.tracks[trknum].pregap = 0; + outinfo.track[trknum].idx0offs = -1; + outinfo.track[trknum].idx1offs = 0; - strncpy(outinfo->fname[trknum], lastfname, 256); // default filename to the last one -// printf("trk %d: fname %s offset %d\n", trknum, &outinfo->fname[trknum][0], outinfo->offset[trknum]); + outinfo.track[trknum].fname.cpy(lastfname); // default filename to the last one +// printf("trk %d: fname %s offset %d\n", trknum, outinfo.track[trknum].fname.cstr(), outinfo.track[trknum].offset); - cdrom_convert_type_string_to_track_info(token, &outtoc->tracks[trknum]); - if (outtoc->tracks[trknum].datasize == 0) + cdrom_convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); + if (outtoc.tracks[trknum].datasize == 0) { printf("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); return CHDERR_FILE_NOT_FOUND; @@ -702,7 +700,7 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i /* next (optional) token on the line is the subcode type */ TOKENIZE - cdrom_convert_subtype_string_to_track_info(token, &outtoc->tracks[trknum]); + cdrom_convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); } else if (!strcmp(token, "INDEX")) /* only in bin/cue files */ { @@ -718,14 +716,14 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i if (idx == 0) { - outinfo->idx0offs[trknum] = frames; + outinfo.track[trknum].idx0offs = frames; } else if (idx == 1) { - outinfo->idx1offs[trknum] = frames; - if ((outtoc->tracks[trknum].pregap == 0) && (outinfo->idx0offs[trknum] != -1)) + outinfo.track[trknum].idx1offs = frames; + if ((outtoc.tracks[trknum].pregap == 0) && (outinfo.track[trknum].idx0offs != -1)) { - outtoc->tracks[trknum].pregap = frames - outinfo->idx0offs[trknum]; + outtoc.tracks[trknum].pregap = frames - outinfo.track[trknum].idx0offs; } } } @@ -737,7 +735,7 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i TOKENIZE frames = msf_to_frames( token ); - outtoc->tracks[trknum].pregap = frames; + outtoc.tracks[trknum].pregap = frames; } else if (!strcmp(token, "POSTGAP")) { @@ -747,7 +745,7 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i TOKENIZE frames = msf_to_frames( token ); - outtoc->tracks[trknum].postgap = frames; + outtoc.tracks[trknum].postgap = frames; } } } @@ -756,67 +754,67 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i fclose(infile); /* store the number of tracks found */ - outtoc->numtrks = trknum + 1; + outtoc.numtrks = trknum + 1; /* now go over the files again and set the lengths */ - for (trknum = 0; trknum < outtoc->numtrks; trknum++) + for (trknum = 0; trknum < outtoc.numtrks; trknum++) { UINT64 tlen = 0; // this is true for cue/bin and cue/iso, and we need it for cue/wav since .WAV is little-endian - if (outtoc->tracks[trknum].trktype == CD_TRACK_AUDIO) + if (outtoc.tracks[trknum].trktype == CD_TRACK_AUDIO) { - outinfo->swap[trknum] = 1; + outinfo.track[trknum].swap = true; } // don't do this for .WAV tracks, we already have their length and offset filled out - if (outinfo->offset[trknum] == 0) + if (outinfo.track[trknum].offset == 0) { // is this the last track? - if (trknum == (outtoc->numtrks-1)) + if (trknum == (outtoc.numtrks-1)) { /* if we have the same filename as the last track, do it that way */ - if (!strcmp(&outinfo->fname[trknum][0], &outinfo->fname[trknum-1][0])) + if (outinfo.track[trknum].fname == outinfo.track[trknum-1].fname) { - tlen = get_file_size(outinfo->fname[trknum]); + tlen = get_file_size(outinfo.track[trknum].fname); if (tlen == 0) { - printf("ERROR: couldn't find bin file [%s]\n", outinfo->fname[trknum-1]); + printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.cstr()); return CHDERR_FILE_NOT_FOUND; } - outinfo->offset[trknum] = outinfo->offset[trknum-1] + outtoc->tracks[trknum-1].frames * (outtoc->tracks[trknum-1].datasize + outtoc->tracks[trknum-1].subsize); - outtoc->tracks[trknum].frames = (tlen - outinfo->offset[trknum]) / (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize); + outinfo.track[trknum].offset = outinfo.track[trknum-1].offset + outtoc.tracks[trknum-1].frames * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize); + outtoc.tracks[trknum].frames = (tlen - outinfo.track[trknum].offset) / (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); } else /* data files are different */ { - tlen = get_file_size(outinfo->fname[trknum]); + tlen = get_file_size(outinfo.track[trknum].fname); if (tlen == 0) { - printf("ERROR: couldn't find bin file [%s]\n", outinfo->fname[trknum-1]); + printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.cstr()); return CHDERR_FILE_NOT_FOUND; } - tlen /= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize); - outtoc->tracks[trknum].frames = tlen; - outinfo->offset[trknum] = 0; + tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + outtoc.tracks[trknum].frames = tlen; + outinfo.track[trknum].offset = 0; } } else { /* if we have the same filename as the next track, do it that way */ - if (!strcmp(&outinfo->fname[trknum][0], &outinfo->fname[trknum+1][0])) + if (outinfo.track[trknum].fname == outinfo.track[trknum+1].fname) { - outtoc->tracks[trknum].frames = outinfo->idx1offs[trknum+1] - outinfo->idx1offs[trknum]; + outtoc.tracks[trknum].frames = outinfo.track[trknum+1].idx1offs - outinfo.track[trknum].idx1offs; if (trknum == 0) // track 0 offset is 0 { - outinfo->offset[trknum] = 0; + outinfo.track[trknum].offset = 0; } else { - outinfo->offset[trknum] = outinfo->offset[trknum-1] + outtoc->tracks[trknum-1].frames * (outtoc->tracks[trknum-1].datasize + outtoc->tracks[trknum-1].subsize); + outinfo.track[trknum].offset = outinfo.track[trknum-1].offset + outtoc.tracks[trknum-1].frames * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize); } - if (!outtoc->tracks[trknum].frames) + if (!outtoc.tracks[trknum].frames) { printf("ERROR: unable to determine size of track %d, missing INDEX 01 markers?\n", trknum+1); return CHDERR_FILE_NOT_FOUND; @@ -824,19 +822,19 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i } else /* data files are different */ { - tlen = get_file_size(outinfo->fname[trknum]); + tlen = get_file_size(outinfo.track[trknum].fname); if (tlen == 0) { - printf("ERROR: couldn't find bin file [%s]\n", outinfo->fname[trknum]); + printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname.cstr()); return CHDERR_FILE_NOT_FOUND; } - tlen /= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize); - outtoc->tracks[trknum].frames = tlen; - outinfo->offset[trknum] = 0; + tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + outtoc.tracks[trknum].frames = tlen; + outinfo.track[trknum].offset = 0; } } } - printf("trk %d: %d frames @ offset %d\n", trknum+1, outtoc->tracks[trknum].frames, outinfo->offset[trknum]); + printf("trk %d: %d frames @ offset %d\n", trknum+1, outtoc.tracks[trknum].frames, outinfo.track[trknum].offset); } return CHDERR_NONE; @@ -846,7 +844,7 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i chdcd_parse_toc - parse a CDRDAO format TOC file -------------------------------------------------*/ -chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo) +chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc &outtoc, chdcd_track_input_info &outinfo) { FILE *infile; int i, trknum; @@ -885,8 +883,8 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i } /* clear structures */ - memset(outtoc, 0, sizeof(cdrom_toc)); - memset(outinfo, 0, sizeof(chdcd_track_input_info)); + memset(&outtoc, 0, sizeof(outtoc)); + outinfo.reset(); trknum = -1; @@ -910,8 +908,7 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i TOKENIZE /* keep the filename */ - strncpy(outinfo->fname[trknum], path.cstr(), 256); - strncat(outinfo->fname[trknum], token, 256); + outinfo.track[trknum].fname.cpy(path.cstr()).cat(token); /* get either the offset or the length */ TOKENIZE @@ -920,11 +917,11 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i { TOKENIZE - outinfo->swap[trknum] = 1; + outinfo.track[trknum].swap = true; } else { - outinfo->swap[trknum] = 0; + outinfo.track[trknum].swap = false; } if (token[0] == '#') @@ -937,14 +934,14 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i /* convert the time to an offset */ f = msf_to_frames( token ); - f *= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize); + f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); } else { f = 0; } - outinfo->offset[trknum] = f; + outinfo.track[trknum].offset = f; TOKENIZE @@ -958,19 +955,19 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i if (isdigit((UINT8)token[0])) { // it was an offset. - f *= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize); + f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outinfo->offset[trknum] += f; + outinfo.track[trknum].offset += f; // this is the length. f = msf_to_frames( token ); } } - else if( trknum == 0 && outinfo->offset[trknum] != 0 ) + else if( trknum == 0 && outinfo.track[trknum].offset != 0 ) { /* the 1st track might have a length with no offset */ - f = outinfo->offset[trknum] / (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize); - outinfo->offset[trknum] = 0; + f = outinfo.track[trknum].offset / (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + outinfo.track[trknum].offset = 0; } else { @@ -978,7 +975,7 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i f = 0; } - outtoc->tracks[trknum].frames = f; + outtoc.tracks[trknum].frames = f; } else if (!strcmp(token, "TRACK")) { @@ -987,13 +984,13 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i /* next token on the line is the track type */ TOKENIZE - outtoc->tracks[trknum].trktype = CD_TRACK_MODE1; - outtoc->tracks[trknum].datasize = 0; - outtoc->tracks[trknum].subtype = CD_SUB_NONE; - outtoc->tracks[trknum].subsize = 0; + outtoc.tracks[trknum].trktype = CD_TRACK_MODE1; + outtoc.tracks[trknum].datasize = 0; + outtoc.tracks[trknum].subtype = CD_SUB_NONE; + outtoc.tracks[trknum].subsize = 0; - cdrom_convert_type_string_to_track_info(token, &outtoc->tracks[trknum]); - if (outtoc->tracks[trknum].datasize == 0) + cdrom_convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); + if (outtoc.tracks[trknum].datasize == 0) { printf("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); return CHDERR_FILE_NOT_FOUND; @@ -1002,7 +999,7 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i /* next (optional) token on the line is the subcode type */ TOKENIZE - cdrom_convert_subtype_string_to_track_info(token, &outtoc->tracks[trknum]); + cdrom_convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); } else if (!strcmp(token, "START")) { @@ -1012,7 +1009,7 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i TOKENIZE frames = msf_to_frames( token ); - outtoc->tracks[trknum].pregap = frames; + outtoc.tracks[trknum].pregap = frames; } } } @@ -1021,7 +1018,7 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_i fclose(infile); /* store the number of tracks found */ - outtoc->numtrks = trknum + 1; + outtoc.numtrks = trknum + 1; return CHDERR_NONE; } diff --git a/src/lib/util/chdcd.h b/src/lib/util/chdcd.h index 3228828a160..df799371603 100644 --- a/src/lib/util/chdcd.h +++ b/src/lib/util/chdcd.h @@ -14,17 +14,26 @@ #include "cdrom.h" -typedef struct _chdcd_track_input_info chdcd_track_input_info; -struct _chdcd_track_input_info /* used only at compression time */ +struct chdcd_track_input_entry { - char fname[CD_MAX_TRACKS][256]; /* filename for each track */ - UINT32 offset[CD_MAX_TRACKS]; /* offset in the data file for each track */ - int swap[CD_MAX_TRACKS]; /* data needs to be byte swapped */ - UINT32 idx0offs[CD_MAX_TRACKS]; - UINT32 idx1offs[CD_MAX_TRACKS]; + chdcd_track_input_entry() { reset(); } + void reset() { fname.reset(); offset = idx0offs = idx1offs = 0; swap = false; } + + astring fname; // filename for each track + UINT32 offset; // offset in the data file for each track + bool swap; // data needs to be byte swapped + UINT32 idx0offs; + UINT32 idx1offs; +}; + +struct chdcd_track_input_info +{ + void reset() { for (int i = 0; i < CD_MAX_TRACKS; i++) track[i].reset(); } + + chdcd_track_input_entry track[CD_MAX_TRACKS]; }; -chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo); +chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc &outtoc, chdcd_track_input_info &outinfo); #endif /* __CHDCD_H__ */ diff --git a/src/lib/util/chdcodec.c b/src/lib/util/chdcodec.c new file mode 100644 index 00000000000..b0649cbd29b --- /dev/null +++ b/src/lib/util/chdcodec.c @@ -0,0 +1,1325 @@ +/*************************************************************************** + + chdcodec.c + + Codecs used by the CHD format + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#include "chd.h" +#include "hashing.h" +#include "avhuff.h" +#include "flac.h" +#include <zlib.h> +#include "lib7z/lzmaenc.h" +#include "lib7z/lzmadec.h" +#include <new> + +// function that should exist but doesn't in the official release +extern "C" SRes LzmaDec_Allocate_MAME(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc); + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> chd_zlib_allocator + +// allocation helper clas for zlib +class chd_zlib_allocator +{ +public: + // construction/destruction + chd_zlib_allocator(); + ~chd_zlib_allocator(); + + // installation + void install(z_stream &stream); + +private: + // internal helpers + static voidpf fast_alloc(voidpf opaque, uInt items, uInt size); + static void fast_free(voidpf opaque, voidpf address); + + static const int MAX_ZLIB_ALLOCS = 64; + UINT32 * m_allocptr[MAX_ZLIB_ALLOCS]; +}; + + +// ======================> chd_zlib_compressor + +// ZLIB compressor +class chd_zlib_compressor : public chd_compressor +{ +public: + // construction/destruction + chd_zlib_compressor(chd_file &chd, bool lossy); + ~chd_zlib_compressor(); + + // core functionality + virtual UINT32 compress(const UINT8 *src, UINT32 srclen, UINT8 *dest); + +private: + // internal state + z_stream m_deflater; + chd_zlib_allocator m_allocator; +}; + + +// ======================> chd_zlib_decompressor + +// ZLIB decompressor +class chd_zlib_decompressor : public chd_decompressor +{ +public: + // construction/destruction + chd_zlib_decompressor(chd_file &chd, bool lossy); + ~chd_zlib_decompressor(); + + // core functionality + virtual void decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen); + +private: + // internal state + z_stream m_inflater; + chd_zlib_allocator m_allocator; +}; + + +// ======================> chd_lzma_allocator + +// allocation helper clas for zlib +class chd_lzma_allocator : public ISzAlloc +{ +public: + // construction/destruction + chd_lzma_allocator(); + ~chd_lzma_allocator(); + +private: + // internal helpers + static void *fast_alloc(void *p, size_t size); + static void fast_free(void *p, void *address); + + static const int MAX_LZMA_ALLOCS = 64; + UINT32 * m_allocptr[MAX_LZMA_ALLOCS]; +}; + + +// ======================> chd_lzma_compressor + +// LZMA compressor +class chd_lzma_compressor : public chd_compressor +{ +public: + // construction/destruction + chd_lzma_compressor(chd_file &chd, bool lossy); + ~chd_lzma_compressor(); + + // core functionality + virtual UINT32 compress(const UINT8 *src, UINT32 srclen, UINT8 *dest); + + // helpers + static void configure_properties(CLzmaEncProps &props, chd_file &chd); + +private: + // internal state + CLzmaEncProps m_props; + chd_lzma_allocator m_allocator; +}; + + +// ======================> chd_lzma_decompressor + +// LZMA decompressor +class chd_lzma_decompressor : public chd_decompressor +{ +public: + // construction/destruction + chd_lzma_decompressor(chd_file &chd, bool lossy); + ~chd_lzma_decompressor(); + + // core functionality + virtual void decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen); + +private: + // internal state + CLzmaProps m_props; + CLzmaDec m_decoder; + chd_lzma_allocator m_allocator; +}; + + +// ======================> chd_huffman_compressor + +// Huffman compressor +class chd_huffman_compressor : public chd_compressor +{ +public: + // construction/destruction + chd_huffman_compressor(chd_file &chd, bool lossy); + + // core functionality + virtual UINT32 compress(const UINT8 *src, UINT32 srclen, UINT8 *dest); + +private: + // internal state + huffman_8bit_encoder m_encoder; +}; + + +// ======================> chd_huffman_decompressor + +// Huffman decompressor +class chd_huffman_decompressor : public chd_decompressor +{ +public: + // construction/destruction + chd_huffman_decompressor(chd_file &chd, bool lossy); + + // core functionality + virtual void decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen); + +private: + // internal state + huffman_8bit_decoder m_decoder; +}; + + +// ======================> chd_flac_compressor + +// FLAC compressor +class chd_flac_compressor : public chd_compressor +{ +public: + // construction/destruction + chd_flac_compressor(chd_file &chd, bool lossy, bool bigendian); + + // core functionality + virtual UINT32 compress(const UINT8 *src, UINT32 srclen, UINT8 *dest); + +private: + // internal state + bool m_swap_endian; + flac_encoder m_encoder; +}; + +// big-endian variant +class chd_flac_compressor_be : public chd_flac_compressor +{ +public: + // construction/destruction + chd_flac_compressor_be(chd_file &chd, bool lossy) + : chd_flac_compressor(chd, lossy, true) { } +}; + +// little-endian variant +class chd_flac_compressor_le : public chd_flac_compressor +{ +public: + // construction/destruction + chd_flac_compressor_le(chd_file &chd, bool lossy) + : chd_flac_compressor(chd, lossy, false) { } +}; + + +// ======================> chd_flac_decompressor + +// FLAC decompressor +class chd_flac_decompressor : public chd_decompressor +{ +public: + // construction/destruction + chd_flac_decompressor(chd_file &chd, bool lossy, bool bigendian); + + // core functionality + virtual void decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen); + +private: + // internal state + bool m_swap_endian; + flac_decoder m_decoder; +}; + +// big-endian variant +class chd_flac_decompressor_be : public chd_flac_decompressor +{ +public: + // construction/destruction + chd_flac_decompressor_be(chd_file &chd, bool lossy) + : chd_flac_decompressor(chd, lossy, true) { } +}; + +// little-endian variant +class chd_flac_decompressor_le : public chd_flac_decompressor +{ +public: + // construction/destruction + chd_flac_decompressor_le(chd_file &chd, bool lossy) + : chd_flac_decompressor(chd, lossy, false) { } +}; + + +// ======================> chd_avhuff_compressor + +// A/V compressor +class chd_avhuff_compressor : public chd_compressor +{ +public: + // construction/destruction + chd_avhuff_compressor(chd_file &chd, bool lossy); + + // core functionality + virtual UINT32 compress(const UINT8 *src, UINT32 srclen, UINT8 *dest); + +private: + // internal helpers + void postinit(); + + // internal state + avhuff_encoder m_encoder; + bool m_postinit; +}; + + +// ======================> chd_avhuff_decompressor + +// A/V decompressor +class chd_avhuff_decompressor : public chd_decompressor +{ +public: + // construction/destruction + chd_avhuff_decompressor(chd_file &chd, bool lossy); + + // core functionality + virtual void decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen); + virtual void configure(int param, void *config); + +private: + // internal state + avhuff_decoder m_decoder; +}; + + + +//************************************************************************** +// CODEC LIST +//************************************************************************** + +// static list of available known codecs +const chd_codec_list::codec_entry chd_codec_list::s_codec_list[] = +{ + { CHD_CODEC_ZLIB, false, "Deflate", &chd_codec_list::construct_compressor<chd_zlib_compressor>, &chd_codec_list::construct_decompressor<chd_zlib_decompressor> }, + { CHD_CODEC_LZMA, false, "LZMA", &chd_codec_list::construct_compressor<chd_lzma_compressor>, &chd_codec_list::construct_decompressor<chd_lzma_decompressor> }, + { CHD_CODEC_HUFFMAN, false, "Huffman", &chd_codec_list::construct_compressor<chd_huffman_compressor>, &chd_codec_list::construct_decompressor<chd_huffman_decompressor> }, + { CHD_CODEC_FLAC_BE, false, "FLAC, big-endian", &chd_codec_list::construct_compressor<chd_flac_compressor_be>, &chd_codec_list::construct_decompressor<chd_flac_decompressor_be> }, + { CHD_CODEC_FLAC_LE, false, "FLAC, little-endian", &chd_codec_list::construct_compressor<chd_flac_compressor_le>, &chd_codec_list::construct_decompressor<chd_flac_decompressor_le> }, + { CHD_CODEC_AVHUFF, false, "A/V Huffman", &chd_codec_list::construct_compressor<chd_avhuff_compressor>, &chd_codec_list::construct_decompressor<chd_avhuff_decompressor> }, +}; + + + +//************************************************************************** +// CHD CODEC +//************************************************************************** + +//------------------------------------------------- +// chd_codec - constructor +//------------------------------------------------- + +chd_codec::chd_codec(chd_file &file, bool lossy) + : m_chd(file), + m_lossy(lossy) +{ +} + + +//------------------------------------------------- +// ~chd_codec - destructor +//------------------------------------------------- + +chd_codec::~chd_codec() +{ +} + + +//------------------------------------------------- +// configure - configuration +//------------------------------------------------- + +void chd_codec::configure(int param, void *config) +{ + // if not overridden, it is always a failure + throw CHDERR_INVALID_PARAMETER; +} + + + +//************************************************************************** +// CHD COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_compressor - constructor +//------------------------------------------------- + +chd_compressor::chd_compressor(chd_file &file, bool lossy) + : chd_codec(file, lossy) +{ +} + + + +//************************************************************************** +// CHD DECOMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_decompressor - constructor +//------------------------------------------------- + +chd_decompressor::chd_decompressor(chd_file &file, bool lossy) + : chd_codec(file, lossy) +{ +} + + + +//************************************************************************** +// CHD CODEC LIST +//************************************************************************** + +//------------------------------------------------- +// new_compressor - create a new compressor +// instance of the given type +//------------------------------------------------- + +chd_compressor *chd_codec_list::new_compressor(chd_codec_type type, chd_file &file) +{ + // find in the list and construct the class + const codec_entry *entry = find_in_list(type); + return (entry == NULL) ? NULL : (*entry->m_construct_compressor)(file, entry->m_lossy); +} + + +//------------------------------------------------- +// new_compressor - create a new decompressor +// instance of the given type +//------------------------------------------------- + +chd_decompressor *chd_codec_list::new_decompressor(chd_codec_type type, chd_file &file) +{ + // find in the list and construct the class + const codec_entry *entry = find_in_list(type); + return (entry == NULL) ? NULL : (*entry->m_construct_decompressor)(file, entry->m_lossy); +} + + +//------------------------------------------------- +// codec_name - return the name of the given +// codec +//------------------------------------------------- + +const char *chd_codec_list::codec_name(chd_codec_type type) +{ + // find in the list and construct the class + const codec_entry *entry = find_in_list(type); + return (entry == NULL) ? NULL : entry->m_name; +} + + +//------------------------------------------------- +// find_in_list - create a new compressor +// instance of the given type +//------------------------------------------------- + +const chd_codec_list::codec_entry *chd_codec_list::find_in_list(chd_codec_type type) +{ + // find in the list and construct the class + for (int listnum = 0; listnum < ARRAY_LENGTH(s_codec_list); listnum++) + if (s_codec_list[listnum].m_type == type) + return &s_codec_list[listnum]; + return NULL; +} + + + +//************************************************************************** +// CODEC INSTANCE +//************************************************************************** + +//------------------------------------------------- +// chd_compressor_group - constructor +//------------------------------------------------- + +chd_compressor_group::chd_compressor_group(chd_file &file, UINT32 compressor_list[4]) + : m_hunkbytes(file.hunk_bytes()), + m_compress_test(m_hunkbytes) +#if CHDCODEC_VERIFY_COMPRESSION + ,m_decompressed(m_hunkbytes) +#endif +{ + // verify the compression types and initialize the codecs + for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compressor); codecnum++) + { + m_compressor[codecnum] = NULL; + if (compressor_list[codecnum] != CHD_CODEC_NONE) + { + m_compressor[codecnum] = chd_codec_list::new_compressor(compressor_list[codecnum], file); + if (m_compressor[codecnum] == NULL) + throw CHDERR_UNKNOWN_COMPRESSION; +#if CHDCODEC_VERIFY_COMPRESSION + m_decompressor[codecnum] = chd_codec_list::new_decompressor(compressor_list[codecnum], file); + if (m_decompressor[codecnum] == NULL) + throw CHDERR_UNKNOWN_COMPRESSION; +#endif + } + } +} + + +//------------------------------------------------- +// ~chd_compressor_group - destructor +//------------------------------------------------- + +chd_compressor_group::~chd_compressor_group() +{ + // delete the codecs and the test buffer + for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compressor); codecnum++) + delete m_compressor[codecnum]; +} + + +//------------------------------------------------- +// find_best_compressor - iterate over all codecs +// to determine which one produces the best +// compression for this hunk +//------------------------------------------------- + +INT8 chd_compressor_group::find_best_compressor(const UINT8 *src, UINT8 *compressed, UINT32 &complen) +{ + // determine best compression technique + complen = m_hunkbytes; + INT8 compression = -1; + for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compressor); codecnum++) + if (m_compressor[codecnum] != NULL) + { + // attempt to compress, swallowing errors + try + { + // if this is the best one, copy the data into the permanent buffer + UINT32 compbytes = m_compressor[codecnum]->compress(src, m_hunkbytes, m_compress_test); +#if CHDCODEC_VERIFY_COMPRESSION + try + { + memset(m_decompressed, 0, m_hunkbytes); + m_decompressor[codecnum]->decompress(m_compress_test, compbytes, m_decompressed, m_hunkbytes); + } + catch (...) + { + } + + if (memcmp(src, m_decompressed, m_hunkbytes) != 0) + { + compbytes = m_compressor[codecnum]->compress(src, m_hunkbytes, m_compress_test); + try + { + m_decompressor[codecnum]->decompress(m_compress_test, compbytes, m_decompressed, m_hunkbytes); + } + catch (...) + { + memset(m_decompressed, 0, m_hunkbytes); + } + } +printf(" codec%d=%d bytes \n", codecnum, compbytes); +#endif + if (compbytes < complen) + { + compression = codecnum; + complen = compbytes; + memcpy(compressed, m_compress_test, compbytes); + } + } + catch (...) { } + } + + // if the best is none, copy it over + if (compression == -1) + memcpy(compressed, src, m_hunkbytes); + return compression; +} + + + +//************************************************************************** +// ZLIB ALLOCATOR HELPER +//************************************************************************** + +//------------------------------------------------- +// chd_zlib_allocator - constructor +//------------------------------------------------- + +chd_zlib_allocator::chd_zlib_allocator() +{ + // reset pointer list + memset(m_allocptr, 0, sizeof(m_allocptr)); +} + + +//------------------------------------------------- +// ~chd_zlib_allocator - constructor +//------------------------------------------------- + +chd_zlib_allocator::~chd_zlib_allocator() +{ + // free our memory + for (int memindex = 0; memindex < ARRAY_LENGTH(m_allocptr); memindex++) + delete[] m_allocptr[memindex]; +} + + +//------------------------------------------------- +// install - configure the allocators for a +// stream +//------------------------------------------------- + +void chd_zlib_allocator::install(z_stream &stream) +{ + stream.zalloc = &chd_zlib_allocator::fast_alloc; + stream.zfree = &chd_zlib_allocator::fast_free; + stream.opaque = this; +} + + +//------------------------------------------------- +// zlib_fast_alloc - fast malloc for ZLIB, which +// allocates and frees memory frequently +//------------------------------------------------- + +voidpf chd_zlib_allocator::fast_alloc(voidpf opaque, uInt items, uInt size) +{ + chd_zlib_allocator *codec = reinterpret_cast<chd_zlib_allocator *>(opaque); + + // compute the size, rounding to the nearest 1k + size = (size * items + 0x3ff) & ~0x3ff; + + // reuse a hunk if we can + for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++) + { + UINT32 *ptr = codec->m_allocptr[scan]; + if (ptr != NULL && 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 and put it into the list + UINT32 *ptr = reinterpret_cast<UINT32 *>(new UINT8[size + sizeof(UINT32)]); + for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++) + if (codec->m_allocptr[scan] == NULL) + { + codec->m_allocptr[scan] = ptr; + break; + } + + // set the low bit of the size so we don't match next time + *ptr = size | 1; + return ptr + 1; +} + + +//------------------------------------------------- +// zlib_fast_free - fast free for ZLIB, which +// allocates and frees memory frequently +//------------------------------------------------- + +void chd_zlib_allocator::fast_free(voidpf opaque, voidpf address) +{ + chd_zlib_allocator *codec = reinterpret_cast<chd_zlib_allocator *>(opaque); + + // find the hunk + UINT32 *ptr = reinterpret_cast<UINT32 *>(address) - 1; + for (int scan = 0; scan < MAX_ZLIB_ALLOCS; scan++) + if (ptr == codec->m_allocptr[scan]) + { + // clear the low bit of the size to allow matches + *ptr &= ~1; + return; + } +} + + + +//************************************************************************** +// ZLIB COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_zlib_compressor - constructor +//------------------------------------------------- + +chd_zlib_compressor::chd_zlib_compressor(chd_file &chd, bool lossy) + : chd_compressor(chd, lossy) +{ + // initialize the deflater + m_deflater.next_in = (Bytef *)this; // bogus, but that's ok + m_deflater.avail_in = 0; + m_allocator.install(m_deflater); + int zerr = deflateInit2(&m_deflater, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + + // convert errors + if (zerr == Z_MEM_ERROR) + throw std::bad_alloc(); + else if (zerr != Z_OK) + throw CHDERR_CODEC_ERROR; +} + + +//------------------------------------------------- +// ~chd_zlib_compressor - destructor +//------------------------------------------------- + +chd_zlib_compressor::~chd_zlib_compressor() +{ + deflateEnd(&m_deflater); +} + + +//------------------------------------------------- +// compress - compress data using the ZLIB codec +//------------------------------------------------- + +UINT32 chd_zlib_compressor::compress(const UINT8 *src, UINT32 srclen, UINT8 *dest) +{ + // reset the decompressor + m_deflater.next_in = const_cast<Bytef *>(src); + m_deflater.avail_in = srclen; + m_deflater.total_in = 0; + m_deflater.next_out = dest; + m_deflater.avail_out = srclen; + m_deflater.total_out = 0; + int zerr = deflateReset(&m_deflater); + if (zerr != Z_OK) + throw CHDERR_COMPRESSION_ERROR; + + // do it + zerr = deflate(&m_deflater, Z_FINISH); + + // if we ended up with more data than we started with, return an error + if (zerr != Z_STREAM_END || m_deflater.total_out >= srclen) + throw CHDERR_COMPRESSION_ERROR; + + // otherwise, return the length + return m_deflater.total_out; +} + + + +//************************************************************************** +// ZLIB DECOMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_zlib_decompressor - constructor +//------------------------------------------------- + +chd_zlib_decompressor::chd_zlib_decompressor(chd_file &chd, bool lossy) + : chd_decompressor(chd, lossy) +{ + // init the inflater first + m_inflater.next_in = (Bytef *)this; // bogus, but that's ok + m_inflater.avail_in = 0; + m_allocator.install(m_inflater); + int zerr = inflateInit2(&m_inflater, -MAX_WBITS); + + // convert errors + if (zerr == Z_MEM_ERROR) + throw std::bad_alloc(); + else if (zerr != Z_OK) + throw CHDERR_CODEC_ERROR; +} + + +//------------------------------------------------- +// ~chd_zlib_decompressor - destructor +//------------------------------------------------- + +chd_zlib_decompressor::~chd_zlib_decompressor() +{ + inflateEnd(&m_inflater); +} + + +//------------------------------------------------- +// decompress - decompress data using the ZLIB +// codec +//------------------------------------------------- + +void chd_zlib_decompressor::decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen) +{ + // reset the decompressor + m_inflater.next_in = const_cast<Bytef *>(src); + m_inflater.avail_in = complen; + m_inflater.total_in = 0; + m_inflater.next_out = dest; + m_inflater.avail_out = destlen; + m_inflater.total_out = 0; + int zerr = inflateReset(&m_inflater); + if (zerr != Z_OK) + throw CHDERR_DECOMPRESSION_ERROR; + + // do it + zerr = inflate(&m_inflater, Z_FINISH); + if (m_inflater.total_out != destlen) + throw CHDERR_DECOMPRESSION_ERROR; +} + + + +//************************************************************************** +// LZMA ALLOCATOR HELPER +//************************************************************************** + +//------------------------------------------------- +// chd_lzma_allocator - constructor +//------------------------------------------------- + +chd_lzma_allocator::chd_lzma_allocator() +{ + // reset pointer list + memset(m_allocptr, 0, sizeof(m_allocptr)); + + // set our pointers + Alloc = &chd_lzma_allocator::fast_alloc; + Free = &chd_lzma_allocator::fast_free; +} + + +//------------------------------------------------- +// ~chd_lzma_allocator - constructor +//------------------------------------------------- + +chd_lzma_allocator::~chd_lzma_allocator() +{ + // free our memory + for (int memindex = 0; memindex < ARRAY_LENGTH(m_allocptr); memindex++) + delete[] m_allocptr[memindex]; +} + + +//------------------------------------------------- +// lzma_fast_alloc - fast malloc for lzma, which +// allocates and frees memory frequently +//------------------------------------------------- + +void *chd_lzma_allocator::fast_alloc(void *p, size_t size) +{ + chd_lzma_allocator *codec = reinterpret_cast<chd_lzma_allocator *>(p); + + // compute the size, rounding to the nearest 1k + size = (size + 0x3ff) & ~0x3ff; + + // reuse a hunk if we can + for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++) + { + UINT32 *ptr = codec->m_allocptr[scan]; + if (ptr != NULL && 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 and put it into the list + UINT32 *ptr = reinterpret_cast<UINT32 *>(new UINT8[size + sizeof(UINT32)]); + for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++) + if (codec->m_allocptr[scan] == NULL) + { + codec->m_allocptr[scan] = ptr; + break; + } + + // set the low bit of the size so we don't match next time + *ptr = size | 1; + return ptr + 1; +} + + +//------------------------------------------------- +// lzma_fast_free - fast free for lzma, which +// allocates and frees memory frequently +//------------------------------------------------- + +void chd_lzma_allocator::fast_free(void *p, void *address) +{ + if (address == NULL) + return; + + chd_lzma_allocator *codec = reinterpret_cast<chd_lzma_allocator *>(p); + + // find the hunk + UINT32 *ptr = reinterpret_cast<UINT32 *>(address) - 1; + for (int scan = 0; scan < MAX_LZMA_ALLOCS; scan++) + if (ptr == codec->m_allocptr[scan]) + { + // clear the low bit of the size to allow matches + *ptr &= ~1; + return; + } +} + + + +//************************************************************************** +// LZMA COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_lzma_compressor - constructor +//------------------------------------------------- + +chd_lzma_compressor::chd_lzma_compressor(chd_file &chd, bool lossy) + : chd_compressor(chd, lossy) +{ + // initialize the properties + configure_properties(m_props, chd); +} + + +//------------------------------------------------- +// ~chd_lzma_compressor - destructor +//------------------------------------------------- + +chd_lzma_compressor::~chd_lzma_compressor() +{ +} + + +//------------------------------------------------- +// compress - compress data using the LZMA codec +//------------------------------------------------- + +UINT32 chd_lzma_compressor::compress(const UINT8 *src, UINT32 srclen, UINT8 *dest) +{ + // allocate the encoder + CLzmaEncHandle encoder = LzmaEnc_Create(&m_allocator); + if (encoder == NULL) + throw CHDERR_COMPRESSION_ERROR; + + try + { + // configure the encoder + SRes res = LzmaEnc_SetProps(encoder, &m_props); + if (res != SZ_OK) + throw CHDERR_COMPRESSION_ERROR; + + // run it + UINT32 complen = srclen; + res = LzmaEnc_MemEncode(encoder, dest, &complen, src, srclen, 0, NULL, &m_allocator, &m_allocator); + if (res != SZ_OK) + throw CHDERR_COMPRESSION_ERROR; + + // clean up + LzmaEnc_Destroy(encoder, &m_allocator, &m_allocator); + return complen; + } + catch (...) + { + // destroy before re-throwing + LzmaEnc_Destroy(encoder, &m_allocator, &m_allocator); + throw; + } +} + + +//------------------------------------------------- +// configure_properties - configure the LZMA +// codec +//------------------------------------------------- + +void chd_lzma_compressor::configure_properties(CLzmaEncProps &props, chd_file &chd) +{ + LzmaEncProps_Init(&props); + props.level = 9; + props.reduceSize = chd.hunk_bytes(); + LzmaEncProps_Normalize(&props); +} + + + +//************************************************************************** +// LZMA DECOMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_lzma_decompressor - constructor +//------------------------------------------------- + +chd_lzma_decompressor::chd_lzma_decompressor(chd_file &chd, bool lossy) + : chd_decompressor(chd, lossy) +{ + // construct the decoder + LzmaDec_Construct(&m_decoder); + + // configure the properties like the compressor did + CLzmaEncProps encoder_props; + chd_lzma_compressor::configure_properties(encoder_props, chd); + + // convert to decoder properties + CLzmaProps decoder_props; + decoder_props.lc = encoder_props.lc; + decoder_props.lp = encoder_props.lp; + decoder_props.pb = encoder_props.pb; + decoder_props.dicSize = encoder_props.dictSize; + + // do memory allocations + SRes res = LzmaDec_Allocate_MAME(&m_decoder, &decoder_props, &m_allocator); + if (res != SZ_OK) + throw CHDERR_DECOMPRESSION_ERROR; +} + + +//------------------------------------------------- +// ~chd_lzma_decompressor - destructor +//------------------------------------------------- + +chd_lzma_decompressor::~chd_lzma_decompressor() +{ + // free memory + LzmaDec_Free(&m_decoder, &m_allocator); +} + + +//------------------------------------------------- +// decompress - decompress data using the LZMA +// codec +//------------------------------------------------- + +void chd_lzma_decompressor::decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen) +{ + // initialize + LzmaDec_Init(&m_decoder); + + // decode + UINT32 consumedlen = complen; + UINT32 decodedlen = destlen; + ELzmaStatus status; + SRes res = LzmaDec_DecodeToBuf(&m_decoder, dest, &decodedlen, src, &consumedlen, LZMA_FINISH_END, &status); + if ((res != SZ_OK && res != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) || consumedlen != complen || decodedlen != destlen) + throw CHDERR_DECOMPRESSION_ERROR; +} + + + +//************************************************************************** +// HUFFMAN COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_huffman_compressor - constructor +//------------------------------------------------- + +chd_huffman_compressor::chd_huffman_compressor(chd_file &chd, bool lossy) + : chd_compressor(chd, lossy) +{ +} + + +//------------------------------------------------- +// compress - compress data using the Huffman +// codec +//------------------------------------------------- + +UINT32 chd_huffman_compressor::compress(const UINT8 *src, UINT32 srclen, UINT8 *dest) +{ + UINT32 complen; + if (m_encoder.encode(src, srclen, dest, srclen, complen) != HUFFERR_NONE) + throw CHDERR_COMPRESSION_ERROR; + return complen; +} + + + +//************************************************************************** +// HUFFMAN DECOMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_huffman_decompressor - constructor +//------------------------------------------------- + +chd_huffman_decompressor::chd_huffman_decompressor(chd_file &chd, bool lossy) + : chd_decompressor(chd, lossy) +{ +} + + +//------------------------------------------------- +// decompress - decompress data using the Huffman +// codec +//------------------------------------------------- + +void chd_huffman_decompressor::decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen) +{ + if (m_decoder.decode(src, complen, dest, destlen) != HUFFERR_NONE) + throw CHDERR_COMPRESSION_ERROR; +} + + + +//************************************************************************** +// FLAC COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_flac_compressor - constructor +//------------------------------------------------- + +chd_flac_compressor::chd_flac_compressor(chd_file &chd, bool lossy, bool bigendian) + : chd_compressor(chd, lossy) +{ + // determine whether we want native or swapped samples + UINT16 native_endian = 0; + *reinterpret_cast<UINT8 *>(&native_endian) = 1; + if (native_endian == 1) + m_swap_endian = bigendian; + else + m_swap_endian = !bigendian; + + // configure the encoder + m_encoder.set_sample_rate(44100); + m_encoder.set_num_channels(2); + m_encoder.set_block_size(chd.hunk_bytes()); + m_encoder.set_strip_metadata(true); +} + + +//------------------------------------------------- +// compress - compress data using the FLAC codec +//------------------------------------------------- + +UINT32 chd_flac_compressor::compress(const UINT8 *src, UINT32 srclen, UINT8 *dest) +{ + // reset and encode + m_encoder.reset(dest, chd().hunk_bytes()); + if (!m_encoder.encode_interleaved(reinterpret_cast<const INT16 *>(src), srclen / 4, m_swap_endian)) + throw CHDERR_COMPRESSION_ERROR; + + // finish up + UINT32 complen = m_encoder.finish(); + if (complen >= chd().hunk_bytes()) + throw CHDERR_COMPRESSION_ERROR; + return complen; +} + + + +//************************************************************************** +// FLAC COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_flac_decompressor - constructor +//------------------------------------------------- + +chd_flac_decompressor::chd_flac_decompressor(chd_file &chd, bool lossy, bool bigendian) + : chd_decompressor(chd, lossy) +{ + // determine whether we want native or swapped samples + UINT16 native_endian = 0; + *reinterpret_cast<UINT8 *>(&native_endian) = 1; + if (native_endian == 1) + m_swap_endian = bigendian; + else + m_swap_endian = !bigendian; +} + + +//------------------------------------------------- +// decompress - decompress data using the FLAC +// codec +//------------------------------------------------- + +void chd_flac_decompressor::decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen) +{ + // reset and decode + if (!m_decoder.reset(44100, 2, chd().hunk_bytes() / 4, src, complen)) + throw CHDERR_DECOMPRESSION_ERROR; + if (!m_decoder.decode_interleaved(reinterpret_cast<INT16 *>(dest), destlen / 4, m_swap_endian)) + throw CHDERR_DECOMPRESSION_ERROR; + + // finish up + m_decoder.finish(); +} + + + +//************************************************************************** +// AVHUFF COMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_avhuff_compressor - constructor +//------------------------------------------------- + +chd_avhuff_compressor::chd_avhuff_compressor(chd_file &chd, bool lossy) + : chd_compressor(chd, lossy), + m_postinit(false) +{ + try + { + // attempt to do a post-init now + postinit(); + } + catch (chd_error &) + { + // if we're creating a new CHD, it won't work but that's ok + } +} + + +//------------------------------------------------- +// compress - compress data using the A/V codec +//------------------------------------------------- + +UINT32 chd_avhuff_compressor::compress(const UINT8 *src, UINT32 srclen, UINT8 *dest) +{ + // if we haven't yet set up the avhuff code, do it now + if (!m_postinit) + postinit(); + + // make sure short frames are padded with 0 + if (src != NULL) + { + int size = avhuff_encoder::raw_data_size(src); + while (size < srclen) + if (src[size++] != 0) + throw CHDERR_INVALID_DATA; + } + + // encode the audio and video + UINT32 complen; + avhuff_error averr = m_encoder.encode_data(src, dest, complen); + if (averr != AVHERR_NONE || complen > srclen) + throw CHDERR_COMPRESSION_ERROR; + return complen; +} + + +//------------------------------------------------- +// postinit - actual initialization of avhuff +// happens here, on the first attempt to compress +// or decompress data +//------------------------------------------------- + +void chd_avhuff_compressor::postinit() +{ + // get the metadata + astring metadata; + chd_error err = chd().read_metadata(AV_METADATA_TAG, 0, metadata); + if (err != CHDERR_NONE) + throw err; + + // extract the info + int fps, fpsfrac, width, height, interlaced, channels, rate; + if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) + throw CHDERR_INVALID_METADATA; + + // compute the bytes per frame + UINT32 fps_times_1million = fps * 1000000 + fpsfrac; + UINT32 max_samples_per_frame = (UINT64(rate) * 1000000 + fps_times_1million - 1) / fps_times_1million; + UINT32 bytes_per_frame = 12 + channels * max_samples_per_frame * 2 + width * height * 2; + if (bytes_per_frame > chd().hunk_bytes()) + throw CHDERR_INVALID_METADATA; + + // done with post-init + m_postinit = true; +} + + + +//************************************************************************** +// AVHUFF DECOMPRESSOR +//************************************************************************** + +//------------------------------------------------- +// chd_avhuff_decompressor - constructor +//------------------------------------------------- + +chd_avhuff_decompressor::chd_avhuff_decompressor(chd_file &chd, bool lossy) + : chd_decompressor(chd, lossy) +{ +} + + +//------------------------------------------------- +// decompress - decompress data using the A/V +// codec +//------------------------------------------------- + +void chd_avhuff_decompressor::decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen) +{ + // decode the audio and video + avhuff_error averr = m_decoder.decode_data(src, complen, dest); + if (averr != AVHERR_NONE) + throw CHDERR_DECOMPRESSION_ERROR; + + // pad short frames with 0 + if (dest != NULL) + { + int size = avhuff_encoder::raw_data_size(dest); + if (size < destlen) + memset(dest + size, 0, destlen - size); + } +} + + +//------------------------------------------------- +// config - codec-specific configuration for the +// A/V codec +//------------------------------------------------- + +void chd_avhuff_decompressor::configure(int param, void *config) +{ + // if we're getting the decompression configuration, apply it now + if (param == AVHUFF_CODEC_DECOMPRESS_CONFIG) + m_decoder.configure(*reinterpret_cast<avhuff_decompress_config *>(config)); + + // anything else is invalid + else + throw CHDERR_INVALID_PARAMETER; +} diff --git a/src/lib/util/chdcodec.h b/src/lib/util/chdcodec.h new file mode 100644 index 00000000000..fd47e811ccb --- /dev/null +++ b/src/lib/util/chdcodec.h @@ -0,0 +1,212 @@ +/*************************************************************************** + + chdcodec.h + + Codecs used by the CHD format + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#pragma once + +#ifndef __CHDCODEC_H__ +#define __CHDCODEC_H__ + +#include "osdcore.h" + + +#define CHDCODEC_VERIFY_COMPRESSION 0 + + +//************************************************************************** +// MACROS +//************************************************************************** + +#define CHD_MAKE_TAG(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) + + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// forward references +class chd_file; + +// base types +typedef UINT32 chd_codec_type; + + +// ======================> chd_codec + +// common base class for all compressors and decompressors +class chd_codec +{ +protected: + // can't create these directly + chd_codec(chd_file &file, bool lossy); + +public: + // allow public deletion + virtual ~chd_codec(); + + // accessors + chd_file &chd() const { return m_chd; } + bool lossy() const { return m_lossy; } + + // implementation + virtual void configure(int param, void *config); + +private: + // internal state + chd_file & m_chd; + bool m_lossy; +}; + + +// ======================> chd_compressor + +// base class for all compressors +class chd_compressor : public chd_codec +{ +protected: + // can't create these directly + chd_compressor(chd_file &file, bool lossy); + +public: + // implementation + virtual UINT32 compress(const UINT8 *src, UINT32 srclen, UINT8 *dest) = 0; +}; + + +// ======================> chd_decompressor + +// base class for all decompressors +class chd_decompressor : public chd_codec +{ +protected: + // can't create these directly + chd_decompressor(chd_file &file, bool lossy); + +public: + // implementation + virtual void decompress(const UINT8 *src, UINT32 complen, UINT8 *dest, UINT32 destlen) = 0; +}; + + +// ======================> chd_codec_list + +// wrapper to get at the list of codecs +class chd_codec_list +{ +public: + // create compressors or decompressors + static chd_compressor *new_compressor(chd_codec_type type, chd_file &file); + static chd_decompressor *new_decompressor(chd_codec_type type, chd_file &file); + + // utilities + static bool codec_exists(chd_codec_type type) { return (find_in_list(type) != NULL); } + static const char *codec_name(chd_codec_type type); + +private: + // an entry in the list + struct codec_entry + { + chd_codec_type m_type; + bool m_lossy; + const char * m_name; + chd_compressor * (*m_construct_compressor)(chd_file &, bool); + chd_decompressor * (*m_construct_decompressor)(chd_file &, bool); + }; + + // internal helper functions + static const codec_entry *find_in_list(chd_codec_type type); + + template<class _CompressorClass> + static chd_compressor *construct_compressor(chd_file &chd, bool lossy) { return new _CompressorClass(chd, lossy); } + + template<class _DecompressorClass> + static chd_decompressor *construct_decompressor(chd_file &chd, bool lossy) { return new _DecompressorClass(chd, lossy); } + + // the static list + static const codec_entry s_codec_list[]; +}; + + +// ======================> chd_compressor_group + +// helper class that wraps several compressors +class chd_compressor_group +{ +public: + // construction/destruction + chd_compressor_group(chd_file &file, chd_codec_type compressor_list[4]); + ~chd_compressor_group(); + + // find the best compressor + INT8 find_best_compressor(const UINT8 *src, UINT8 *compressed, UINT32 &complen); + +private: + // internal state + UINT32 m_hunkbytes; // number of bytes in a hunk + chd_compressor * m_compressor[4]; // array of active codecs + dynamic_buffer m_compress_test; // test buffer for compression +#if CHDCODEC_VERIFY_COMPRESSION + chd_decompressor * m_decompressor[4]; // array of active codecs + dynamic_buffer m_decompressed; // verification buffer +#endif +}; + + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +// currently-defined codecs +const chd_codec_type CHD_CODEC_NONE = 0; +const chd_codec_type CHD_CODEC_ZLIB = CHD_MAKE_TAG('z','l','i','b'); +const chd_codec_type CHD_CODEC_LZMA = CHD_MAKE_TAG('l','z','m','a'); +const chd_codec_type CHD_CODEC_HUFFMAN = CHD_MAKE_TAG('h','u','f','f'); +const chd_codec_type CHD_CODEC_FLAC_BE = CHD_MAKE_TAG('f','l','c','b'); +const chd_codec_type CHD_CODEC_FLAC_LE = CHD_MAKE_TAG('f','l','c','l'); +const chd_codec_type CHD_CODEC_AVHUFF = CHD_MAKE_TAG('a','v','h','u'); + +// A/V codec configuration parameters +enum +{ + AVHUFF_CODEC_DECOMPRESS_CONFIG = 1 +}; + + +#endif // __CHDCODEC_H__ diff --git a/src/lib/util/corefile.c b/src/lib/util/corefile.c index 4d760f95d24..0a907aeabc4 100644 --- a/src/lib/util/corefile.c +++ b/src/lib/util/corefile.c @@ -757,6 +757,41 @@ file_error core_fload(const char *filename, void **data, UINT32 *length) return FILERR_NONE; } +file_error core_fload(const char *filename, dynamic_buffer &data) +{ + core_file *file = NULL; + file_error err; + UINT64 size; + + /* attempt to open the file */ + err = core_fopen(filename, OPEN_FLAG_READ, &file); + if (err != FILERR_NONE) + return err; + + /* get the size */ + size = core_fsize(file); + if ((UINT32)size != size) + { + core_fclose(file); + return FILERR_OUT_OF_MEMORY; + } + + /* allocate memory */ + data.resize(size); + + /* read the data */ + if (core_fread(file, data, size) != size) + { + core_fclose(file); + data.reset(); + return FILERR_FAILURE; + } + + /* close the file and return data */ + core_fclose(file); + return FILERR_NONE; +} + /*************************************************************************** diff --git a/src/lib/util/corefile.h b/src/lib/util/corefile.h index ff88efa004a..182d8c76ad9 100644 --- a/src/lib/util/corefile.h +++ b/src/lib/util/corefile.h @@ -45,6 +45,7 @@ #include <stdarg.h> #include "osdcore.h" #include "astring.h" +#include "coretmpl.h" @@ -129,6 +130,7 @@ const void *core_fbuffer(core_file *file); /* open a file with the specified filename, read it into memory, and return a pointer */ file_error core_fload(const char *filename, void **data, UINT32 *length); +file_error core_fload(const char *filename, dynamic_buffer &data); diff --git a/src/lib/util/coretmpl.h b/src/lib/util/coretmpl.h new file mode 100644 index 00000000000..59e012fe753 --- /dev/null +++ b/src/lib/util/coretmpl.h @@ -0,0 +1,109 @@ +/*************************************************************************** + + coretmpl.h + + Core templates for basic non-string types. + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#pragma once + +#ifndef __CORETMPL_H__ +#define __CORETMPL_H__ + +#include <assert.h> +#include "osdcore.h" + + +// ======================> dynamic_array + +// an array that is dynamically sized and can optionally auto-expand +template<class _ElementType> +class dynamic_array +{ +private: + // we don't support deep copying + dynamic_array(const dynamic_array &); + dynamic_array &operator=(const dynamic_array &); + +public: + // construction/destruction + dynamic_array(int initial = 0) + : m_array(NULL), + m_count(0), + m_allocated(0) { if (initial != 0) expand_internal(initial); m_count = initial; } + virtual ~dynamic_array() { reset(); } + + // operators + operator _ElementType *() { return &m_array[0]; } + operator const _ElementType *() const { return &m_array[0]; } + _ElementType operator[](int index) const { assert(index < m_count); return m_array[index]; } + _ElementType &operator[](int index) { assert(index < m_count); return m_array[index]; } + + // simple getters + int count() const { return m_count; } + + // helpers + void append(const _ElementType &element) { if (m_count == m_allocated) expand_internal((m_allocated == 0) ? 16 : (m_allocated << 1), true); m_array[m_count++] = element; } + void reset() { delete[] m_array; m_array = NULL; m_count = m_allocated = 0; } + void resize(int count, bool keepdata = false) { if (count > m_allocated) expand_internal(count, keepdata); m_count = count; } + +private: + // internal helpers + void expand_internal(int count, bool keepdata = true) + { + // allocate a new array, copy the old one, and proceed + m_allocated = count; + _ElementType *newarray = new _ElementType[m_allocated]; + if (keepdata) + for (int index = 0; index < m_count; index++) + newarray[index] = m_array[index]; + delete[] m_array; + m_array = newarray; + } + + // internal state + _ElementType * m_array; // allocated array + int m_count; // number of objects accessed in the list + int m_allocated; // amount of space allocated for the array +}; + + +// ======================> dynamic_buffer + +typedef dynamic_array<UINT8> dynamic_buffer; + + + +#endif diff --git a/src/lib/util/flac.c b/src/lib/util/flac.c new file mode 100644 index 00000000000..aaefe301779 --- /dev/null +++ b/src/lib/util/flac.c @@ -0,0 +1,598 @@ +/*************************************************************************** + + flac.c + + FLAC compression wrappers + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#include "flac.h" +#include <assert.h> +#include <new> + + +//************************************************************************** +// FLAC ENCODER +//************************************************************************** + +//------------------------------------------------- +// flac_encoder - constructors +//------------------------------------------------- + +flac_encoder::flac_encoder() +{ + init_common(); +} + + +flac_encoder::flac_encoder(void *buffer, UINT32 buflength) +{ + init_common(); + reset(buffer, buflength); +} + + +flac_encoder::flac_encoder(core_file &file) +{ + init_common(); + reset(file); +} + + +//------------------------------------------------- +// ~flac_encoder - destructor +//------------------------------------------------- + +flac_encoder::~flac_encoder() +{ + // delete the encoder + FLAC__stream_encoder_delete(m_encoder); +} + + +//------------------------------------------------- +// reset - reset state with the original +// parameters +//------------------------------------------------- + +bool flac_encoder::reset() +{ + // configure the output + m_compressed_offset = 0; + m_ignore_bytes = m_strip_metadata ? 4 : 0; + m_found_audio = !m_strip_metadata; + + // configure the encoder in a standard way + // note we do this on each reset; if we don't, results are NOT consistent! + FLAC__stream_encoder_set_verify(m_encoder, false); +// FLAC__stream_encoder_set_do_md5(m_encoder, false); + FLAC__stream_encoder_set_compression_level(m_encoder, 8); + FLAC__stream_encoder_set_channels(m_encoder, m_channels); + FLAC__stream_encoder_set_bits_per_sample(m_encoder, 16); + FLAC__stream_encoder_set_sample_rate(m_encoder, m_sample_rate); + FLAC__stream_encoder_set_total_samples_estimate(m_encoder, 0); + FLAC__stream_encoder_set_streamable_subset(m_encoder, false); + FLAC__stream_encoder_set_blocksize(m_encoder, m_block_size); + + // re-start processing + return (FLAC__stream_encoder_init_stream(m_encoder, write_callback_static, NULL, NULL, NULL, this) == FLAC__STREAM_ENCODER_INIT_STATUS_OK); +} + + +//------------------------------------------------- +// reset - reset state with new memory parameters +//------------------------------------------------- + +bool flac_encoder::reset(void *buffer, UINT32 buflength) +{ + // configure the output + m_compressed_start = reinterpret_cast<FLAC__byte *>(buffer); + m_compressed_length = buflength; + m_file = NULL; + return reset(); +} + + +//------------------------------------------------- +// reset - reset state with new file parameters +//------------------------------------------------- + +bool flac_encoder::reset(core_file &file) +{ + // configure the output + m_compressed_start = NULL; + m_compressed_length = 0; + m_file = &file; + return reset(); +} + + +//------------------------------------------------- +// encode_interleaved - encode a buffer with +// interleaved samples +//------------------------------------------------- + +bool flac_encoder::encode_interleaved(const INT16 *samples, UINT32 samples_per_channel, bool swap_endian) +{ + int shift = swap_endian ? 8 : 0; + + // loop over source samples + int num_channels = FLAC__stream_encoder_get_channels(m_encoder); + UINT32 srcindex = 0; + while (samples_per_channel != 0) + { + // process in batches of 2k samples + FLAC__int32 converted_buffer[2048]; + FLAC__int32 *dest = converted_buffer; + UINT32 cur_samples = MIN(ARRAY_LENGTH(converted_buffer) / num_channels, samples_per_channel); + + // convert a buffers' worth + for (UINT32 sampnum = 0; sampnum < cur_samples; sampnum++) + for (int channel = 0; channel < num_channels; channel++, srcindex++) + *dest++ = INT16((UINT16(samples[srcindex]) << shift) | (UINT16(samples[srcindex]) >> shift)); + + // process this batch + if (!FLAC__stream_encoder_process_interleaved(m_encoder, converted_buffer, cur_samples)) + return false; + samples_per_channel -= cur_samples; + } + return true; +} + + +//------------------------------------------------- +// encode - encode a buffer with individual +// sample streams +//------------------------------------------------- + +bool flac_encoder::encode(INT16 *const *samples, UINT32 samples_per_channel, bool swap_endian) +{ + int shift = swap_endian ? 8 : 0; + + // loop over source samples + int num_channels = FLAC__stream_encoder_get_channels(m_encoder); + UINT32 srcindex = 0; + while (samples_per_channel != 0) + { + // process in batches of 2k samples + FLAC__int32 converted_buffer[2048]; + FLAC__int32 *dest = converted_buffer; + UINT32 cur_samples = MIN(ARRAY_LENGTH(converted_buffer) / num_channels, samples_per_channel); + + // convert a buffers' worth + for (UINT32 sampnum = 0; sampnum < cur_samples; sampnum++, srcindex++) + for (int channel = 0; channel < num_channels; channel++) + *dest++ = INT16((UINT16(samples[channel][srcindex]) << shift) | (UINT16(samples[channel][srcindex]) >> shift)); + + // process this batch + if (!FLAC__stream_encoder_process_interleaved(m_encoder, converted_buffer, cur_samples)) + return false; + samples_per_channel -= cur_samples; + } + return true; +} + + +//------------------------------------------------- +// finish - complete encoding and flush the +// stream +//------------------------------------------------- + +UINT32 flac_encoder::finish() +{ + // process the data and return the amount written + FLAC__stream_encoder_finish(m_encoder); + return (m_file != NULL) ? core_ftell(m_file) : m_compressed_offset; +} + + +//------------------------------------------------- +// init_common - common initialization +//------------------------------------------------- + +void flac_encoder::init_common() +{ + // allocate the encoder + m_encoder = FLAC__stream_encoder_new(); + if (m_encoder == NULL) + throw std::bad_alloc(); + + // initialize default state + m_file = NULL; + m_compressed_offset = 0; + m_compressed_start = NULL; + m_compressed_length = 0; + m_sample_rate = 44100; + m_channels = 2; + m_block_size = 0; + m_strip_metadata = false; + m_ignore_bytes = 0; + m_found_audio = false; +} + + +//------------------------------------------------- +// write_callback - handle writes to the +// output stream +//------------------------------------------------- + +FLAC__StreamEncoderWriteStatus flac_encoder::write_callback_static(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) +{ + return reinterpret_cast<flac_encoder *>(client_data)->write_callback(buffer, bytes, samples, current_frame); +} + +FLAC__StreamEncoderWriteStatus flac_encoder::write_callback(const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame) +{ + // loop over output data + size_t offset = 0; + while (offset < bytes) + { + // if we're ignoring, continue to do so + if (m_ignore_bytes != 0) + { + int ignore = MIN(bytes - offset, m_ignore_bytes); + offset += ignore; + m_ignore_bytes -= ignore; + } + + // if we haven't hit the end of metadata, process a new piece + else if (!m_found_audio) + { + assert(bytes - offset >= 4); + m_found_audio = ((buffer[offset] & 0x80) != 0); + m_ignore_bytes = (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | buffer[offset + 3]; + offset += 4; + } + + // otherwise process as audio data and copy to the output + else + { + int count = bytes - offset; + if (m_file != NULL) + core_fwrite(m_file, buffer, count); + else + { + if (m_compressed_offset + count <= m_compressed_length) + memcpy(m_compressed_start + m_compressed_offset, buffer, count); + m_compressed_offset += count; + } + offset += count; + } + } + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + + + +//************************************************************************** +// FLAC DECODER +//************************************************************************** + +//------------------------------------------------- +// flac_decoder - constructor +//------------------------------------------------- + +flac_decoder::flac_decoder() + : m_decoder(FLAC__stream_decoder_new()), + m_file(NULL), + m_compressed_offset(0), + m_compressed_start(NULL), + m_compressed_length(0), + m_compressed2_start(NULL), + m_compressed2_length(0) +{ +} + + +//------------------------------------------------- +// flac_decoder - constructor +//------------------------------------------------- + +flac_decoder::flac_decoder(const void *buffer, UINT32 length, const void *buffer2, UINT32 length2) + : m_decoder(FLAC__stream_decoder_new()), + m_file(NULL), + m_compressed_offset(0), + m_compressed_start(reinterpret_cast<const FLAC__byte *>(buffer)), + m_compressed_length(length), + m_compressed2_start(reinterpret_cast<const FLAC__byte *>(buffer2)), + m_compressed2_length(length2) +{ + reset(); +} + + +//------------------------------------------------- +// flac_decoder - constructor +//------------------------------------------------- + +flac_decoder::flac_decoder(core_file &file) + : m_decoder(FLAC__stream_decoder_new()), + m_file(&file), + m_compressed_offset(0), + m_compressed_start(NULL), + m_compressed_length(0), + m_compressed2_start(NULL), + m_compressed2_length(0) +{ + reset(); +} + + +//------------------------------------------------- +// flac_decoder - destructor +//------------------------------------------------- + +flac_decoder::~flac_decoder() +{ + FLAC__stream_decoder_delete(m_decoder); +} + + +//------------------------------------------------- +// reset - reset state with the original +// parameters +//------------------------------------------------- + +bool flac_decoder::reset() +{ + m_compressed_offset = 0; + if (FLAC__stream_decoder_init_stream(m_decoder, &flac_decoder::read_callback_static, NULL, NULL, NULL, NULL, &flac_decoder::write_callback_static, NULL, &flac_decoder::error_callback_static, this) != FLAC__STREAM_DECODER_INIT_STATUS_OK) + return false; + return FLAC__stream_decoder_process_until_end_of_metadata(m_decoder); +} + + +//------------------------------------------------- +// reset - reset state with new memory parameters +//------------------------------------------------- + +bool flac_decoder::reset(const void *buffer, UINT32 length, const void *buffer2, UINT32 length2) +{ + m_file = NULL; + m_compressed_start = reinterpret_cast<const FLAC__byte *>(buffer); + m_compressed_length = length; + m_compressed2_start = reinterpret_cast<const FLAC__byte *>(buffer2); + m_compressed2_length = length2; + return reset(); +} + + +//------------------------------------------------- +// reset - reset state with new memory parameters +// and a custom-generated header +//------------------------------------------------- + +bool flac_decoder::reset(UINT32 sample_rate, UINT8 num_channels, UINT32 block_size, const void *buffer, UINT32 length) +{ + // modify the template header with our parameters + static const UINT8 s_header_template[0x2a] = + { + 0x66, 0x4C, 0x61, 0x43, // +00: 'fLaC' stream header + 0x80, // +04: metadata block type 0 (STREAMINFO), + // flagged as last block + 0x00, 0x00, 0x22, // +05: metadata block length = 0x22 + 0x00, 0x00, // +08: minimum block size + 0x00, 0x00, // +0A: maximum block size + 0x00, 0x00, 0x00, // +0C: minimum frame size (0 == unknown) + 0x00, 0x00, 0x00, // +0F: maximum frame size (0 == unknown) + 0x0A, 0xC4, 0x42, 0xF0, 0x00, 0x00, 0x00, 0x00, // +12: sample rate (0x0ac44 == 44100), + // numchannels (2), sample bits (16), + // samples in stream (0 == unknown) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // +1A: MD5 signature (0 == none) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // + // +2A: start of stream data + }; + memcpy(m_custom_header, s_header_template, sizeof(s_header_template)); + m_custom_header[0x08] = m_custom_header[0x0a] = block_size >> 8; + m_custom_header[0x09] = m_custom_header[0x0b] = block_size & 0xff; + m_custom_header[0x12] = sample_rate >> 12; + m_custom_header[0x13] = sample_rate >> 4; + m_custom_header[0x14] = (sample_rate << 4) | ((num_channels - 1) << 1); + + // configure the header ahead of the provided buffer + m_file = NULL; + m_compressed_start = reinterpret_cast<const FLAC__byte *>(m_custom_header); + m_compressed_length = sizeof(m_custom_header); + m_compressed2_start = reinterpret_cast<const FLAC__byte *>(buffer); + m_compressed2_length = length; + return reset(); +} + + +//------------------------------------------------- +// reset - reset state with new file parameter +//------------------------------------------------- + +bool flac_decoder::reset(core_file &file) +{ + m_file = &file; + m_compressed_start = NULL; + m_compressed_length = 0; + m_compressed2_start = NULL; + m_compressed2_length = 0; + return reset(); +} + + +//------------------------------------------------- +// decode_interleaved - decode to an interleaved +// sound stream +//------------------------------------------------- + +bool flac_decoder::decode_interleaved(INT16 *samples, UINT32 num_samples, bool swap_endian) +{ + // configure the uncompressed buffer + memset(m_uncompressed_start, 0, sizeof(m_uncompressed_start)); + m_uncompressed_start[0] = samples; + m_uncompressed_offset = 0; + m_uncompressed_length = num_samples; + m_uncompressed_swap = swap_endian; + + // loop until we get everything we want + while (m_uncompressed_offset < m_uncompressed_length) + if (!FLAC__stream_decoder_process_single(m_decoder)) + return false; + return true; +} + + +//------------------------------------------------- +// decode - decode to an multiple independent +// data streams +//------------------------------------------------- + +bool flac_decoder::decode(INT16 **samples, UINT32 num_samples, bool swap_endian) +{ + // make sure we don't have too many channels + int chans = channels(); + if (chans > ARRAY_LENGTH(m_uncompressed_start)) + return false; + + // configure the uncompressed buffer + memset(m_uncompressed_start, 0, sizeof(m_uncompressed_start)); + for (int curchan = 0; curchan < chans; curchan++) + m_uncompressed_start[curchan] = samples[curchan]; + m_uncompressed_offset = 0; + m_uncompressed_length = num_samples; + m_uncompressed_swap = swap_endian; + + // loop until we get everything we want + while (m_uncompressed_offset < m_uncompressed_length) + if (!FLAC__stream_decoder_process_single(m_decoder)) + return false; + return true; +} + + +//------------------------------------------------- +// finish - finish up the decode +//------------------------------------------------- + +void flac_decoder::finish() +{ + FLAC__stream_decoder_finish(m_decoder); +} + + +//------------------------------------------------- +// read_callback - handle reads from the input +// stream +//------------------------------------------------- + +FLAC__StreamDecoderReadStatus flac_decoder::read_callback_static(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) +{ + return reinterpret_cast<flac_decoder *>(client_data)->read_callback(buffer, bytes); +} + +FLAC__StreamDecoderReadStatus flac_decoder::read_callback(FLAC__byte buffer[], size_t *bytes) +{ + UINT32 expected = *bytes; + + // if a file, just read + if (m_file != NULL) + *bytes = core_fread(m_file, buffer, expected); + + // otherwise, copy from memory + else + { + // copy from primary buffer first + UINT32 outputpos = 0; + if (outputpos < *bytes && m_compressed_offset < m_compressed_length) + { + UINT32 bytes_to_copy = MIN(*bytes - outputpos, m_compressed_length - m_compressed_offset); + memcpy(&buffer[outputpos], m_compressed_start + m_compressed_offset, bytes_to_copy); + outputpos += bytes_to_copy; + m_compressed_offset += bytes_to_copy; + } + + // once we're out of that, copy from the secondary buffer + if (outputpos < *bytes && m_compressed_offset < m_compressed_length + m_compressed2_length) + { + UINT32 bytes_to_copy = MIN(*bytes - outputpos, m_compressed2_length - (m_compressed_offset - m_compressed_length)); + memcpy(&buffer[outputpos], m_compressed2_start + m_compressed_offset - m_compressed_length, bytes_to_copy); + outputpos += bytes_to_copy; + m_compressed_offset += bytes_to_copy; + } + *bytes = outputpos; + } + + // return based on whether we ran out of data + return (*bytes < expected) ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + + +//------------------------------------------------- +// write_callback - handle writes to the output +// stream +//------------------------------------------------- + +FLAC__StreamDecoderWriteStatus flac_decoder::write_callback_static(const FLAC__StreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +{ + return reinterpret_cast<flac_decoder *>(client_data)->write_callback(frame, buffer); +} + +FLAC__StreamDecoderWriteStatus flac_decoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) +{ + assert(frame->header.channels == channels()); + + // interleaved case + int shift = m_uncompressed_swap ? 8 : 0; + int blocksize = frame->header.blocksize; + if (m_uncompressed_start[1] == NULL) + { + INT16 *dest = m_uncompressed_start[0] + m_uncompressed_offset * frame->header.channels; + for (int sampnum = 0; sampnum < blocksize && m_uncompressed_offset < m_uncompressed_length; sampnum++, m_uncompressed_offset++) + for (int chan = 0; chan < frame->header.channels; chan++) + *dest++ = INT16((UINT16(buffer[chan][sampnum]) << shift) | (UINT16(buffer[chan][sampnum]) >> shift)); + } + + // non-interleaved case + else + { + for (int sampnum = 0; sampnum < blocksize && m_uncompressed_offset < m_uncompressed_length; sampnum++, m_uncompressed_offset++) + for (int chan = 0; chan < frame->header.channels; chan++) + if (m_uncompressed_start[chan] != NULL) + m_uncompressed_start[chan][m_uncompressed_offset] = INT16((UINT16(buffer[chan][sampnum]) << shift) | (UINT16(buffer[chan][sampnum]) >> shift)); + } + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + + +//------------------------------------------------- +// error_callback - handle errors (ignore them) +//------------------------------------------------- + +void flac_decoder::error_callback_static(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) +{ +} diff --git a/src/lib/util/flac.h b/src/lib/util/flac.h new file mode 100644 index 00000000000..da37b49b75b --- /dev/null +++ b/src/lib/util/flac.h @@ -0,0 +1,167 @@ +/*************************************************************************** + + flac.h + + FLAC compression wrappers + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#pragma once + +#ifndef __FLAC_H__ +#define __FLAC_H__ + +#include "osdcore.h" +#include "corefile.h" +#include "../../lib/libflac/include/flac/all.h" + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> flac_encoder + +class flac_encoder +{ +public: + // construction/destruction + flac_encoder(); + flac_encoder(void *buffer, UINT32 buflength); + flac_encoder(core_file &file); + ~flac_encoder(); + + // configuration + void set_sample_rate(UINT32 sample_rate) { m_sample_rate = sample_rate; } + void set_num_channels(UINT8 num_channels) { m_channels = num_channels; } + void set_block_size(UINT32 block_size) { m_block_size = block_size; } + void set_strip_metadata(bool strip) { m_strip_metadata = strip; } + + // getters (valid after reset) + FLAC__StreamEncoderState state() const { return FLAC__stream_encoder_get_state(m_encoder); } + const char *state_string() const { return FLAC__stream_encoder_get_resolved_state_string(m_encoder); } + + // reset + bool reset(); + bool reset(void *buffer, UINT32 buflength); + bool reset(core_file &file); + + // encode a buffer + bool encode_interleaved(const INT16 *samples, UINT32 samples_per_channel, bool swap_endian = false); + bool encode(INT16 *const *samples, UINT32 samples_per_channel, bool swap_endian = false); + + // finish up + UINT32 finish(); + +private: + // internal helpers + void init_common(); + static FLAC__StreamEncoderWriteStatus write_callback_static(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data); + FLAC__StreamEncoderWriteStatus write_callback(const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame); + + // internal state + FLAC__StreamEncoder * m_encoder; // actual encoder + core_file * m_file; // output file + UINT32 m_compressed_offset; // current offset with the compressed stream + FLAC__byte * m_compressed_start; // start of compressed data + UINT32 m_compressed_length; // length of the compressed stream + + // parameters + UINT32 m_sample_rate; // sample rate + UINT8 m_channels; // number of channels + UINT32 m_block_size; // block size + + // header stripping + bool m_strip_metadata; // strip the the metadata? + UINT32 m_ignore_bytes; // how many bytes to ignore when writing + bool m_found_audio; // have we hit the audio yet? +}; + + +// ======================> flac_decoder + +class flac_decoder +{ +public: + // construction/destruction + flac_decoder(); + flac_decoder(const void *buffer, UINT32 length, const void *buffer2 = NULL, UINT32 length2 = 0); + flac_decoder(core_file &file); + ~flac_decoder(); + + // getters (valid after reset) + UINT32 sample_rate() const { return FLAC__stream_decoder_get_sample_rate(m_decoder); } + UINT8 channels() const { return FLAC__stream_decoder_get_channels(m_decoder); } + UINT32 block_size() const { return FLAC__stream_decoder_get_blocksize(m_decoder); } + FLAC__StreamDecoderState state() const { return FLAC__stream_decoder_get_state(m_decoder); } + const char *state_string() const { return FLAC__stream_decoder_get_resolved_state_string(m_decoder); } + + // reset + bool reset(); + bool reset(const void *buffer, UINT32 length, const void *buffer2 = NULL, UINT32 length2 = 0); + bool reset(UINT32 sample_rate, UINT8 num_channels, UINT32 block_size, const void *buffer, UINT32 length); + bool reset(core_file &file); + + // decode to a buffer; num_samples must be a multiple of the block size + bool decode_interleaved(INT16 *samples, UINT32 num_samples, bool swap_endian = false); + bool decode(INT16 **samples, UINT32 num_samples, bool swap_endian = false); + + // finish up + void finish(); + +private: + // internal helpers + static FLAC__StreamDecoderReadStatus read_callback_static(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data); + FLAC__StreamDecoderReadStatus read_callback(FLAC__byte buffer[], size_t *bytes); + static FLAC__StreamDecoderWriteStatus write_callback_static(const FLAC__StreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data); + FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + static void error_callback_static(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data); + + // output state + FLAC__StreamDecoder * m_decoder; // actual encoder + core_file * m_file; // output file + UINT32 m_compressed_offset; // current offset in compressed data + const FLAC__byte * m_compressed_start; // start of compressed data + UINT32 m_compressed_length; // length of compressed data + const FLAC__byte * m_compressed2_start; // start of compressed data + UINT32 m_compressed2_length; // length of compressed data + INT16 * m_uncompressed_start[8];// pointer to start of uncompressed data (up to 8 streams) + UINT32 m_uncompressed_offset; // current position in uncompressed data + UINT32 m_uncompressed_length; // length of uncompressed data + bool m_uncompressed_swap; // swap uncompressed sample data + UINT8 m_custom_header[0x2a]; // custom header +}; + + +#endif // __FLAC_H__ diff --git a/src/lib/util/harddisk.c b/src/lib/util/harddisk.c index ffae861a6d4..64af494405a 100644 --- a/src/lib/util/harddisk.c +++ b/src/lib/util/harddisk.c @@ -46,13 +46,10 @@ TYPE DEFINITIONS ***************************************************************************/ -struct _hard_disk_file +struct hard_disk_file { chd_file * chd; /* CHD file */ hard_disk_info info; /* hard disk info */ - UINT32 hunksectors; /* sectors per hunk */ - UINT32 cachehunk; /* which hunk is cached */ - UINT8 * cache; /* cache of the current hunk */ }; @@ -70,7 +67,7 @@ hard_disk_file *hard_disk_open(chd_file *chd) { int cylinders, heads, sectors, sectorbytes; hard_disk_file *file; - char metadata[256]; + astring metadata; chd_error err; /* punt if no CHD */ @@ -78,7 +75,7 @@ hard_disk_file *hard_disk_open(chd_file *chd) return NULL; /* read the hard disk metadata */ - err = chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); + err = chd->read_metadata(HARD_DISK_METADATA_TAG, 0, metadata); if (err != CHDERR_NONE) return NULL; @@ -97,17 +94,6 @@ hard_disk_file *hard_disk_open(chd_file *chd) file->info.heads = heads; file->info.sectors = sectors; file->info.sectorbytes = sectorbytes; - file->hunksectors = chd_get_header(chd)->hunkbytes / file->info.sectorbytes; - file->cachehunk = -1; - - /* allocate a cache */ - file->cache = (UINT8 *)malloc(chd_get_header(chd)->hunkbytes); - if (file->cache == NULL) - { - free(file); - return NULL; - } - return file; } @@ -118,9 +104,6 @@ hard_disk_file *hard_disk_open(chd_file *chd) void hard_disk_close(hard_disk_file *file) { - /* free the cache */ - if (file->cache != NULL) - free(file->cache); free(file); } @@ -154,21 +137,8 @@ hard_disk_info *hard_disk_get_info(hard_disk_file *file) UINT32 hard_disk_read(hard_disk_file *file, UINT32 lbasector, void *buffer) { - UINT32 hunknum = lbasector / file->hunksectors; - UINT32 sectoroffs = lbasector % file->hunksectors; - - /* if we haven't cached this hunk, read it now */ - if (file->cachehunk != hunknum) - { - chd_error err = chd_read(file->chd, hunknum, file->cache); - if (err != CHDERR_NONE) - return 0; - file->cachehunk = hunknum; - } - - /* copy out the requested sector */ - memcpy(buffer, &file->cache[sectoroffs * file->info.sectorbytes], file->info.sectorbytes); - return 1; + chd_error err = file->chd->read_units(lbasector, buffer); + return (err == CHDERR_NONE); } @@ -179,23 +149,6 @@ UINT32 hard_disk_read(hard_disk_file *file, UINT32 lbasector, void *buffer) UINT32 hard_disk_write(hard_disk_file *file, UINT32 lbasector, const void *buffer) { - UINT32 hunknum = lbasector / file->hunksectors; - UINT32 sectoroffs = lbasector % file->hunksectors; - chd_error err; - - /* if we haven't cached this hunk, read it now */ - if (file->cachehunk != hunknum) - { - err = chd_read(file->chd, hunknum, file->cache); - if (err != CHDERR_NONE) - return 0; - file->cachehunk = hunknum; - } - - /* copy in the requested data */ - memcpy(&file->cache[sectoroffs * file->info.sectorbytes], buffer, file->info.sectorbytes); - - /* write it back out */ - err = chd_write(file->chd, hunknum, file->cache); - return (err == CHDERR_NONE) ? 1 : 0; + chd_error err = file->chd->write_units(lbasector, buffer); + return (err == CHDERR_NONE); } diff --git a/src/lib/util/harddisk.h b/src/lib/util/harddisk.h index 9f7c5f402e1..09351be9072 100644 --- a/src/lib/util/harddisk.h +++ b/src/lib/util/harddisk.h @@ -50,10 +50,9 @@ TYPE DEFINITIONS ***************************************************************************/ -typedef struct _hard_disk_file hard_disk_file; +struct hard_disk_file; -typedef struct _hard_disk_info hard_disk_info; -struct _hard_disk_info +struct hard_disk_info { UINT32 cylinders; UINT32 heads; diff --git a/src/lib/util/hashing.c b/src/lib/util/hashing.c new file mode 100644 index 00000000000..97afb184156 --- /dev/null +++ b/src/lib/util/hashing.c @@ -0,0 +1,282 @@ +/*************************************************************************** + + hashing.c + + Hashing helper classes. + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#include "hashing.h" + + +//************************************************************************** +// CONSTANTS +//************************************************************************** + +const crc16_t crc16_t::null = { 0 }; +const crc32_t crc32_t::null = { 0 }; +const md5_t md5_t::null = { { 0 } }; +const sha1_t sha1_t::null = { { 0 } }; + + + +//************************************************************************** +// INLINE FUNCTIONS +//************************************************************************** + +//------------------------------------------------- +// char_to_hex - return the hex value of a +// character +//------------------------------------------------- + +inline int char_to_hex(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + if (c >= 'A' && c <= 'F') + return 10 + c - 'a'; + return -1; +} + + + +//************************************************************************** +// SHA-1 HELPERS +//************************************************************************** + +//------------------------------------------------- +// from_string - convert from a string +//------------------------------------------------- + +bool sha1_t::from_string(const char *string) +{ + // must be at least long enough to hold everything + if (strlen(string) < 2 * sizeof(m_raw)) + return false; + + // iterate through our raw buffer + for (int bytenum = 0; bytenum < sizeof(m_raw); bytenum++) + { + int upper = char_to_hex(*string++); + int lower = char_to_hex(*string++); + if (upper == -1 || lower == -1) + return false; + m_raw[bytenum] = (upper << 4) | lower; + } + return true; +} + + +//------------------------------------------------- +// as_string - convert to a string +//------------------------------------------------- + +const char *sha1_t::as_string(astring &buffer) +{ + buffer.reset(); + for (int i = 0; i < ARRAY_LENGTH(m_raw); i++) + buffer.catformat("%02x", m_raw[i]); + return buffer; +} + + +//************************************************************************** +// MD-5 HELPERS +//************************************************************************** + +//------------------------------------------------- +// from_string - convert from a string +//------------------------------------------------- + +bool md5_t::from_string(const char *string) +{ + // must be at least long enough to hold everything + if (strlen(string) < 2 * sizeof(m_raw)) + return false; + + // iterate through our raw buffer + for (int bytenum = 0; bytenum < sizeof(m_raw); bytenum++) + { + int upper = char_to_hex(*string++); + int lower = char_to_hex(*string++); + if (upper == -1 || lower == -1) + return false; + m_raw[bytenum] = (upper << 4) | lower; + } + return true; +} + + +//------------------------------------------------- +// as_string - convert to a string +//------------------------------------------------- + +const char *md5_t::as_string(astring &buffer) +{ + buffer.reset(); + for (int i = 0; i < ARRAY_LENGTH(m_raw); i++) + buffer.catformat("%02x", m_raw[i]); + return buffer; +} + + + +//************************************************************************** +// CRC-32 HELPERS +//************************************************************************** + +//------------------------------------------------- +// from_string - convert from a string +//------------------------------------------------- + +bool crc32_t::from_string(const char *string) +{ + // must be at least long enough to hold everything + if (strlen(string) < 2 * sizeof(m_raw)) + return false; + + // iterate through our raw buffer + m_raw = 0; + for (int bytenum = 0; bytenum < sizeof(m_raw) * 2; bytenum++) + { + int nibble = char_to_hex(*string++); + if (nibble == -1) + return false; + m_raw = (m_raw << 4) | nibble; + } + return true; +} + + +//------------------------------------------------- +// as_string - convert to a string +//------------------------------------------------- + +const char *crc32_t::as_string(astring &buffer) +{ + return buffer.format("%08x", m_raw); +} + + + +//************************************************************************** +// CRC-16 HELPERS +//************************************************************************** + +//------------------------------------------------- +// from_string - convert from a string +//------------------------------------------------- + +bool crc16_t::from_string(const char *string) +{ + // must be at least long enough to hold everything + if (strlen(string) < 2 * sizeof(m_raw)) + return false; + + // iterate through our raw buffer + m_raw = 0; + for (int bytenum = 0; bytenum < sizeof(m_raw) * 2; bytenum++) + { + int nibble = char_to_hex(*string++); + if (nibble == -1) + return false; + m_raw = (m_raw << 4) | nibble; + } + return true; +} + + +//------------------------------------------------- +// as_string - convert to a string +//------------------------------------------------- + +const char *crc16_t::as_string(astring &buffer) +{ + return buffer.format("%04x", m_raw); +} + + +//------------------------------------------------- +// append - hash a block of data, appending to +// the currently-accumulated value +//------------------------------------------------- + +void crc16_creator::append(const void *data, UINT32 length) +{ + static const UINT16 s_table[256] = + { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 + }; + + const UINT8 *src = reinterpret_cast<const UINT8 *>(data); + + // fetch the current value into a local and rip through the source data + UINT16 crc = m_accum.m_raw; + while (length-- != 0) + crc = (crc << 8) ^ s_table[(crc >> 8) ^ *src++]; + m_accum.m_raw = crc; +} diff --git a/src/lib/util/hashing.h b/src/lib/util/hashing.h new file mode 100644 index 00000000000..8bb59b9c5cc --- /dev/null +++ b/src/lib/util/hashing.h @@ -0,0 +1,245 @@ +/*************************************************************************** + + hashing.h + + Hashing helper classes. + +**************************************************************************** + + Copyright Aaron Giles + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name 'MAME' nor the names of its contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + +***************************************************************************/ + +#pragma once + +#ifndef __HASHING_H__ +#define __HASHING_H__ + +#include "osdcore.h" +#include "astring.h" +#include "zlib.h" +#include "md5.h" +#include "sha1.h" + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + + +// ======================> SHA-1 + +// final digest +struct sha1_t +{ + bool operator==(const sha1_t &rhs) const { return memcmp(m_raw, rhs.m_raw, sizeof(m_raw)) == 0; } + bool operator!=(const sha1_t &rhs) const { return memcmp(m_raw, rhs.m_raw, sizeof(m_raw)) != 0; } + operator UINT8 *() { return m_raw; } + const char *as_string(astring &buffer); + bool from_string(const char *string); + UINT8 m_raw[20]; + static const sha1_t null; +}; + +// creation helper +class sha1_creator +{ +public: + // construction/destruction + sha1_creator() { reset(); } + + // reset + void reset() { sha1_init(&m_context); } + + // append data + void append(const void *data, UINT32 length) { sha1_update(&m_context, length, reinterpret_cast<const UINT8 *>(data)); } + + // finalize and compute the final digest + sha1_t finish() + { + sha1_t result; + sha1_final(&m_context); + sha1_digest(&m_context, sizeof(result.m_raw), result.m_raw); + return result; + } + + // static wrapper to just get the digest from a block + static sha1_t simple(const void *data, UINT32 length) + { + sha1_creator creator; + creator.append(data, length); + return creator.finish(); + } + +protected: + // internal state + struct sha1_ctx m_context; // internal context +}; + + + +// ======================> MD5 + +// final digest +struct md5_t +{ + bool operator==(const md5_t &rhs) const { return memcmp(m_raw, rhs.m_raw, sizeof(m_raw)) == 0; } + bool operator!=(const md5_t &rhs) const { return memcmp(m_raw, rhs.m_raw, sizeof(m_raw)) != 0; } + operator UINT8 *() { return m_raw; } + const char *as_string(astring &buffer); + bool from_string(const char *string); + UINT8 m_raw[16]; + static const md5_t null; +}; + +// creation helper +class md5_creator +{ +public: + // construction/destruction + md5_creator() { reset(); } + + // reset + void reset() { MD5Init(&m_context); } + + // append data + void append(const void *data, UINT32 length) { MD5Update(&m_context, reinterpret_cast<const unsigned char *>(data), length); } + + // finalize and compute the final digest + md5_t finish() + { + md5_t result; + MD5Final(result.m_raw, &m_context); + return result; + } + + // static wrapper to just get the digest from a block + static md5_t simple(const void *data, UINT32 length) + { + md5_creator creator; + creator.append(data, length); + return creator.finish(); + } + +protected: + // internal state + struct MD5Context m_context; // internal context +}; + + + +// ======================> CRC-32 + +// final digest +struct crc32_t +{ + bool operator==(const crc32_t &rhs) const { return m_raw == rhs.m_raw; } + operator UINT32() const { return m_raw; } + const char *as_string(astring &buffer); + bool from_string(const char *string); + UINT32 m_raw; + static const crc32_t null; +}; + +// creation helper +class crc32_creator +{ +public: + // construction/destruction + crc32_creator() { reset(); } + + // reset + void reset() { m_accum.m_raw = 0; } + + // append data + void append(const void *data, UINT32 length) { m_accum.m_raw = crc32(m_accum, reinterpret_cast<const Bytef *>(data), length); } + + // finalize and compute the final digest + crc32_t finish() { return m_accum; } + + // static wrapper to just get the digest from a block + static crc32_t simple(const void *data, UINT32 length) + { + crc32_creator creator; + creator.append(data, length); + return creator.finish(); + } + +protected: + // internal state + crc32_t m_accum; // internal accumulator +}; + + + +// ======================> CRC-16 + +// final digest +struct crc16_t +{ + bool operator==(const crc16_t &rhs) const { return m_raw == rhs.m_raw; } + operator UINT16() const { return m_raw; } + const char *as_string(astring &buffer); + bool from_string(const char *string); + UINT16 m_raw; + static const crc16_t null; +}; + +// creation helper +class crc16_creator +{ +public: + // construction/destruction + crc16_creator() { reset(); } + + // reset + void reset() { m_accum.m_raw = 0xffff; } + + // append data + void append(const void *data, UINT32 length); + + // finalize and compute the final digest + crc16_t finish() { return m_accum; } + + // static wrapper to just get the digest from a block + static crc16_t simple(const void *data, UINT32 length) + { + crc16_creator creator; + creator.append(data, length); + return creator.finish(); + } + +protected: + // internal state + crc16_t m_accum; // internal accumulator +}; + + +#endif // __HASHING_H__ diff --git a/src/lib/util/huffman.c b/src/lib/util/huffman.c index 3b9c4ea52b8..b672a7a0d89 100644 --- a/src/lib/util/huffman.c +++ b/src/lib/util/huffman.c @@ -2,7 +2,7 @@ huffman.c - Video compression and decompression helpers. + Static Huffman compression and decompression helpers. **************************************************************************** @@ -127,1569 +127,636 @@ #include <stdlib.h> +#include "coretmpl.h" #include "huffman.h" -/*************************************************************************** - CONSTANTS -***************************************************************************/ - -#define HUFFMAN_CODES 256 -#define HUFFMAN_DELTARLE_CODES (HUFFMAN_CODES + 16) - -#define MAX_HUFFMAN_CODES (HUFFMAN_DELTARLE_CODES) -#define MAX_HUFFMAN_NODES (MAX_HUFFMAN_CODES + MAX_HUFFMAN_CODES) +//************************************************************************** +// MACROS +//************************************************************************** +#define MAKE_LOOKUP(code,bits) (((code) << 5) | ((bits) & 0x1f)) -/*************************************************************************** - MACROS -***************************************************************************/ -#define MAKE_LOOKUP(code,bits) (((code) << 6) | ((bits) & 0x1f)) -#define LOOKUP_CODE(val) ((val) >> 6) -#define LOOKUP_BITS(val) ((val) & 0x1f) +//************************************************************************** +// IMPLEMENTATION +//************************************************************************** +//------------------------------------------------- +// huffman_context_base - create an encoding/ +// decoding context +//------------------------------------------------- - -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ - -typedef struct _bit_buffer bit_buffer; -struct _bit_buffer +huffman_context_base::huffman_context_base(int numcodes, int maxbits, lookup_value *lookup, UINT32 *histo, node_t *nodes) + : m_numcodes(numcodes), + m_maxbits(maxbits), + m_prevdata(0), + m_rleremaining(0), + m_lookup(lookup), + m_datahisto(histo), + m_huffnode(nodes) { - UINT32 buffer; /* current bit accumulator */ - int bits; /* number of bits in the accumulator */ - union - { - const UINT8 * read; /* read pointer */ - UINT8 * write; /* write pointer */ - } data; - UINT32 doffset; /* byte offset within the data */ - UINT32 dlength; /* length of the data */ - int overflow; /* flag: true if we read/wrote past the end */ -}; - - -typedef struct _huffman_node huffman_node; -struct _huffman_node -{ - huffman_node * parent; /* pointer to parent node */ - UINT32 count; /* number of hits on this node */ - UINT32 weight; /* assigned weight of this node */ - UINT32 bits; /* bits used to encode the node */ - UINT8 numbits; /* number of bits needed for this node */ -}; - - -struct _huffman_context -{ - UINT8 maxbits; /* maximum bits per code */ - UINT8 lookupdirty; /* TRUE if the lookup table is dirty */ - UINT8 prevdata; /* value of the previous data (for delta-RLE encoding) */ - UINT32 datahisto[MAX_HUFFMAN_CODES]; /* histogram of data values */ - int rleremaining; /* number of RLE bytes remaining (for delta-RLE encoding) */ - huffman_node huffnode[MAX_HUFFMAN_NODES]; /* array of nodes */ - huffman_lookup_value * lookup; /* pointer to the lookup table */ -}; - - - -/*************************************************************************** - PROTOTYPES -***************************************************************************/ - -static huffman_error huffman_deltarle_decode_data_interleaved_0102(huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength); - -static huffman_error import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength, UINT32 numcodes); -static huffman_error export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength, UINT32 numcodes); -static void write_rle_tree_bits(bit_buffer *bitbuf, int value, int repcount, int numbits); -static int CLIB_DECL tree_node_compare(const void *item1, const void *item2); -static huffman_error compute_optimal_tree(huffman_context *context, const UINT32 *datahisto, UINT32 numcodes); -static int huffman_build_tree(huffman_context *context, const UINT32 *datahisto, UINT32 totaldata, UINT32 totalweight, UINT32 numcodes); -static huffman_error assign_canonical_codes(huffman_context *context, UINT32 numcodes); -static huffman_error build_lookup_table(huffman_context *context, UINT32 numcodes); - - - -/*************************************************************************** - INLINE FUNCTIONS -***************************************************************************/ - -/*------------------------------------------------- - bit_buffer_write_init - initialize a bit - buffer for writing --------------------------------------------------*/ - -INLINE void bit_buffer_write_init(bit_buffer *bitbuf, UINT8 *data, UINT32 dlength) -{ - /* fill in the basic data structure */ - bitbuf->buffer = 0; - bitbuf->bits = 0; - bitbuf->data.write = data; - bitbuf->doffset = 0; - bitbuf->dlength = dlength; - bitbuf->overflow = FALSE; -} - - -/*------------------------------------------------- - bit_buffer_write - write 'numbits' to the - bit buffer, assuming that 'newbits' is right- - justified --------------------------------------------------*/ - -INLINE void bit_buffer_write(bit_buffer *bitbuf, UINT32 newbits, int numbits) -{ - /* flush the buffer if we're going to overflow it */ - if (bitbuf->bits + numbits > 32) - while (bitbuf->bits >= 8) - { - if (bitbuf->doffset < bitbuf->dlength) - bitbuf->data.write[bitbuf->doffset] = bitbuf->buffer >> 24; - else - bitbuf->overflow = TRUE; - bitbuf->doffset++; - bitbuf->buffer <<= 8; - bitbuf->bits -= 8; - } - - /* shift the bits to the top */ - newbits <<= 32 - numbits; - - /* now shift it down to account for the number of bits we already have and OR them in */ - bitbuf->buffer |= newbits >> bitbuf->bits; - bitbuf->bits += numbits; -} - - -/*------------------------------------------------- - bit_buffer_flush - flush any bits in the write - buffer and return the final data offset --------------------------------------------------*/ - -INLINE UINT32 bit_buffer_flush(bit_buffer *bitbuf) -{ - while (bitbuf->bits > 0) - { - if (bitbuf->doffset < bitbuf->dlength) - bitbuf->data.write[bitbuf->doffset] = bitbuf->buffer >> 24; - else - bitbuf->overflow = TRUE; - bitbuf->doffset++; - bitbuf->buffer <<= 8; - bitbuf->bits -= 8; - } - return bitbuf->doffset; -} - - -/*------------------------------------------------- - bit_buffer_read_init - initialize a bit - buffer for reading --------------------------------------------------*/ - -INLINE void bit_buffer_read_init(bit_buffer *bitbuf, const UINT8 *data, UINT32 dlength) -{ - /* fill in the basic data structure */ - bitbuf->buffer = 0; - bitbuf->bits = 0; - bitbuf->data.read = data; - bitbuf->doffset = 0; - bitbuf->dlength = dlength; - bitbuf->overflow = FALSE; -} - - -/*------------------------------------------------- - bit_buffer_read - read 'numbits' bits from - the buffer, returning them right-justified --------------------------------------------------*/ - -INLINE UINT32 bit_buffer_read(bit_buffer *bitbuf, int numbits) -{ - UINT32 result; - - /* fetch data if we need more */ - if (numbits > bitbuf->bits) - { - while (bitbuf->bits <= 24) - { - if (bitbuf->doffset < bitbuf->dlength) - bitbuf->buffer |= bitbuf->data.read[bitbuf->doffset] << (24 - bitbuf->bits); - bitbuf->doffset++; - bitbuf->bits += 8; - } - if (numbits > bitbuf->bits) - bitbuf->overflow = TRUE; - } - - /* return the data */ - result = bitbuf->buffer >> (32 - numbits); - bitbuf->buffer <<= numbits; - bitbuf->bits -= numbits; - return result; -} - - -/*------------------------------------------------- - bit_buffer_peek - peek ahead and return - 'numbits' bits from the buffer, returning - them right-justified --------------------------------------------------*/ - -INLINE UINT32 bit_buffer_peek(bit_buffer *bitbuf, int numbits) -{ - /* fetch data if we need more */ - if (numbits > bitbuf->bits) - { - while (bitbuf->bits <= 24) - { - if (bitbuf->doffset < bitbuf->dlength) - bitbuf->buffer |= bitbuf->data.read[bitbuf->doffset] << (24 - bitbuf->bits); - bitbuf->doffset++; - bitbuf->bits += 8; - } - if (numbits > bitbuf->bits) - bitbuf->overflow = TRUE; - } - - /* return the data */ - return bitbuf->buffer >> (32 - numbits); -} - - -/*------------------------------------------------- - bit_buffer_remove - remove 'numbits' bits - from the bit buffer; this presupposes that - at least 'numbits' are present --------------------------------------------------*/ - -INLINE void bit_buffer_remove(bit_buffer *bitbuf, int numbits) -{ - bitbuf->buffer <<= numbits; - bitbuf->bits -= numbits; -} - - -/*------------------------------------------------- - bit_buffer_read_offset - return the current - rounded byte reading offset --------------------------------------------------*/ - -INLINE UINT32 bit_buffer_read_offset(bit_buffer *bitbuf) -{ - UINT32 result = bitbuf->doffset; - int bits = bitbuf->bits; - while (bits >= 8) - { - result--; - bits -= 8; - } - return result; -} - - -/*------------------------------------------------- - code_to_rlecount - number of RLE repetitions - encoded in a given byte --------------------------------------------------*/ - -INLINE int code_to_rlecount(int code) -{ - if (code == 0x00) - return 1; - if (code <= 0x107) - return 8 + (code - 0x100); - return 16 << (code - 0x108); -} - - -/*------------------------------------------------- - rlecount_to_byte - return a byte encoding - the maximum RLE count less than or equal to - the provided amount --------------------------------------------------*/ - -INLINE int rlecount_to_code(int rlecount) -{ - if (rlecount >= 2048) - return 0x10f; - if (rlecount >= 1024) - return 0x10e; - if (rlecount >= 512) - return 0x10d; - if (rlecount >= 256) - return 0x10c; - if (rlecount >= 128) - return 0x10b; - if (rlecount >= 64) - return 0x10a; - if (rlecount >= 32) - return 0x109; - if (rlecount >= 16) - return 0x108; - if (rlecount >= 8) - return 0x100 + (rlecount - 8); - return 0x00; -} - - - -/*************************************************************************** - IMPLEMENTATION -***************************************************************************/ - -/*------------------------------------------------- - huffman_create_context - create an encoding/ - decoding context --------------------------------------------------*/ - -huffman_error huffman_create_context(huffman_context **context, int maxbits) -{ - /* limit to 24 bits */ + // limit to 24 bits if (maxbits > 24) - return HUFFERR_TOO_MANY_BITS; - - /* allocate a context */ - *context = (huffman_context *)malloc(sizeof(**context)); - if (*context == NULL) - return HUFFERR_OUT_OF_MEMORY; - - /* set the info */ - memset(*context, 0, sizeof(**context)); - (*context)->maxbits = maxbits; - (*context)->lookupdirty = TRUE; - - return HUFFERR_NONE; + throw HUFFERR_TOO_MANY_BITS; } -/*------------------------------------------------- - huffman_free_context - free an encoding/ - decoding context --------------------------------------------------*/ +//------------------------------------------------- +// import_tree_rle - import an RLE-encoded +// huffman tree from a source data stream +//------------------------------------------------- -void huffman_free_context(huffman_context *context) +huffman_error huffman_context_base::import_tree_rle(bitstream_in &bitbuf) { - if (context->lookup != NULL) - free(context->lookup); - free(context); -} - - -/*------------------------------------------------- - huffman_import_tree - import a huffman tree - from a source data stream --------------------------------------------------*/ - -huffman_error huffman_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength) -{ - return import_tree(context, source, slength, actlength, HUFFMAN_CODES); -} - - -/*------------------------------------------------- - huffman_export_tree - export a huffman tree - to a target data stream --------------------------------------------------*/ - -huffman_error huffman_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength) -{ - return export_tree(context, dest, dlength, actlength, HUFFMAN_CODES); -} - - -/*------------------------------------------------- - huffman_deltarle_import_tree - import a - huffman tree from a source data stream for - delta-RLE encoded data --------------------------------------------------*/ - -huffman_error huffman_deltarle_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength) -{ - return import_tree(context, source, slength, actlength, HUFFMAN_DELTARLE_CODES); -} - - -/*------------------------------------------------- - huffman__deltarle_export_tree - export a - huffman tree to a target data stream for - delta-RLE encoded data --------------------------------------------------*/ - -huffman_error huffman_deltarle_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength) -{ - return export_tree(context, dest, dlength, actlength, HUFFMAN_DELTARLE_CODES); -} - - -/*------------------------------------------------- - huffman_compute_tree - compute an optimal - huffman tree for the given source data --------------------------------------------------*/ - -huffman_error huffman_compute_tree(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) -{ - return huffman_compute_tree_interleaved(1, &context, source, swidth, sheight, sstride, sxor); -} - -huffman_error huffman_compute_tree_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) -{ - UINT32 sx, sy, ctxnum; - huffman_error error; - - /* initialize all nodes */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) - { - huffman_context *context = contexts[ctxnum]; - memset(context->datahisto, 0, sizeof(context->datahisto)); - } - - /* iterate over "height" */ - for (sy = 0; sy < sheight; sy++) - { - /* iterate over "width" */ - for (sx = 0; sx < swidth; ) - { - /* iterate over contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) - { - huffman_context *context = contexts[ctxnum]; - context->datahisto[source[sx ^ sxor]]++; - } - } - - /* advance to the next row */ - source += sstride; - } - - /* compute optimal trees for each */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) - { - huffman_context *context = contexts[ctxnum]; - error = compute_optimal_tree(context, context->datahisto, HUFFMAN_CODES); - if (error != HUFFERR_NONE) - return error; - } - return HUFFERR_NONE; -} - - -/*------------------------------------------------- - huffman_deltarle_compute_tree - compute an - optimal huffman tree for the given source - data, with pre-encoding as delta-RLE --------------------------------------------------*/ - -huffman_error huffman_deltarle_compute_tree(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) -{ - return huffman_deltarle_compute_tree_interleaved(1, &context, source, swidth, sheight, sstride, sxor); -} - -huffman_error huffman_deltarle_compute_tree_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) -{ - UINT32 sx, sy, ctxnum; - huffman_error error; + // bits per entry depends on the maxbits + int numbits; + if (m_maxbits >= 16) + numbits = 5; + else if (m_maxbits >= 8) + numbits = 4; + else + numbits = 3; - /* initialize all nodes */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) + // loop until we read all the nodes + int curnode; + for (curnode = 0; curnode < m_numcodes; ) { - huffman_context *context = contexts[ctxnum]; - memset(context->datahisto, 0, sizeof(context->datahisto)); - context->prevdata = 0; - } + // a non-one value is just raw + int nodebits = bitbuf.read(numbits); + if (nodebits != 1) + m_huffnode[curnode++].m_numbits = nodebits; - /* iterate over "height" */ - for (sy = 0; sy < sheight; sy++) - { - /* reset RLE counts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) + // a one value is an escape code + else { - huffman_context *context = contexts[ctxnum]; - context->rleremaining = 0; - } + // a double 1 is just a single 1 + nodebits = bitbuf.read(numbits); + if (nodebits == 1) + m_huffnode[curnode++].m_numbits = nodebits; - /* iterate over "width" */ - for (sx = 0; sx < swidth; ) - { - /* iterate over contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) + // otherwise, we need one for value for the repeat count + else { - huffman_context *context = contexts[ctxnum]; - UINT8 newdata, delta; - - /* if still counting RLE, do nothing */ - if (context->rleremaining != 0) - { - context->rleremaining--; - continue; - } - - /* fetch new data and compute the delta */ - newdata = source[sx ^ sxor]; - delta = newdata - context->prevdata; - context->prevdata = newdata; - - /* 0 deltas scan forward for a count */ - if (delta == 0) - { - int zerocount = 1; - int rlecode; - UINT32 scan; - - /* count the number of consecutive values */ - for (scan = sx + 1; scan < swidth; scan++) - if (contexts[scan % numcontexts] == context) - { - if (newdata == source[scan ^ sxor]) - zerocount++; - else - break; - } - - /* if we hit the end of row, maximize the count */ - if (scan >= swidth && zerocount >= 8) - zerocount = 100000; - - /* encode the maximal count we can */ - rlecode = rlecount_to_code(zerocount); - context->datahisto[rlecode]++; - - /* set up the remaining count */ - context->rleremaining = code_to_rlecount(rlecode) - 1; - } - else - { - /* encode the actual delta */ - context->datahisto[delta]++; - } + int repcount = bitbuf.read(numbits) + 3; + while (repcount--) + m_huffnode[curnode++].m_numbits = nodebits; } } - - /* advance to the next row */ - source += sstride; } - /* compute optimal trees for each */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) - { - huffman_context *context = contexts[ctxnum]; - error = compute_optimal_tree(context, context->datahisto, HUFFMAN_DELTARLE_CODES); - if (error != HUFFERR_NONE) - return error; - } - return HUFFERR_NONE; -} - - -/*------------------------------------------------- - huffman_encode_data - encode data using the - given tree --------------------------------------------------*/ - -huffman_error huffman_encode_data(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) -{ - return huffman_encode_data_interleaved(1, &context, source, swidth, sheight, sstride, sxor, dest, dlength, actlength); -} - -huffman_error huffman_encode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) -{ - UINT32 sx, sy, ctxnum; - bit_buffer bitbuf; - - /* initialize the output buffer */ - bit_buffer_write_init(&bitbuf, dest, dlength); - - /* iterate over "height" */ - for (sy = 0; sy < sheight; sy++) - { - /* iterate over "width" */ - for (sx = 0; sx < swidth; ) - { - /* iterate over contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) - { - huffman_context *context = contexts[ctxnum]; - huffman_node *node = &context->huffnode[source[sx ^ sxor]]; - bit_buffer_write(&bitbuf, node->bits, node->numbits); - } - } + // make sure we ended up with the right number + if (curnode != m_numcodes) + return HUFFERR_INVALID_DATA; - /* advance to the next row */ - source += sstride; - } + // assign canonical codes for all nodes based on their code lengths + huffman_error error = assign_canonical_codes(); + if (error != HUFFERR_NONE) + return error; + + // build the lookup table + build_lookup_table(); - /* flush and return a status */ - *actlength = bit_buffer_flush(&bitbuf); - return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; + // determine final input length and report errors + return bitbuf.overflow() ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; } -/*------------------------------------------------- - huffman_deltarle_encode_data - encode data - using the given tree with delta-RLE - pre-encoding --------------------------------------------------*/ +//------------------------------------------------- +// export_tree_rle - export a huffman tree to an +// RLE target data stream +//------------------------------------------------- -huffman_error huffman_deltarle_encode_data(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) +huffman_error huffman_context_base::export_tree_rle(bitstream_out &bitbuf) { - return huffman_deltarle_encode_data_interleaved(1, &context, source, swidth, sheight, sstride, sxor, dest, dlength, actlength); -} - -huffman_error huffman_deltarle_encode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) -{ - UINT32 sx, sy, ctxnum; - bit_buffer bitbuf; - - /* initialize the output buffer */ - bit_buffer_write_init(&bitbuf, dest, dlength); - - /* initialize the contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) - { - huffman_context *context = contexts[ctxnum]; - context->prevdata = 0; - } - - /* iterate over "height" */ - for (sy = 0; sy < sheight; sy++) - { - /* reset RLE counts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) - { - huffman_context *context = contexts[ctxnum]; - context->rleremaining = 0; - } - - /* iterate over "width" */ - for (sx = 0; sx < swidth; ) - { - /* iterate over contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) - { - huffman_context *context = contexts[ctxnum]; - UINT8 newdata, delta; - huffman_node *node; - - /* if still counting RLE, do nothing */ - if (context->rleremaining != 0) - { - context->rleremaining--; - continue; - } - - /* fetch new data and compute the delta */ - newdata = source[sx ^ sxor]; - delta = newdata - context->prevdata; - context->prevdata = newdata; - - /* 0 deltas scan forward for a count */ - if (delta == 0) - { - int zerocount = 1; - int rlecode; - UINT32 scan; - - /* count the number of consecutive values */ - for (scan = sx + 1; scan < swidth; scan++) - if (contexts[scan % numcontexts] == context) - { - if (newdata == source[scan ^ sxor]) - zerocount++; - else - break; - } - - /* if we hit the end of row, maximize the count */ - if (scan >= swidth && zerocount >= 8) - zerocount = 100000; - - /* encode the maximal count we can */ - rlecode = rlecount_to_code(zerocount); - node = &context->huffnode[rlecode]; - bit_buffer_write(&bitbuf, node->bits, node->numbits); - - /* set up the remaining count */ - context->rleremaining = code_to_rlecount(rlecode) - 1; - } - else - { - /* encode the actual delta */ - node = &context->huffnode[delta]; - bit_buffer_write(&bitbuf, node->bits, node->numbits); - } - } - } - - /* advance to the next row */ - source += sstride; - } - - /* flush and return a status */ - *actlength = bit_buffer_flush(&bitbuf); - return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; -} - - -/*------------------------------------------------- - huffman_decode_data - decode data using the - given tree --------------------------------------------------*/ + // bits per entry depends on the maxbits + int numbits; + if (m_maxbits >= 16) + numbits = 5; + else if (m_maxbits >= 8) + numbits = 4; + else + numbits = 3; -huffman_error huffman_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) -{ - const huffman_lookup_value *table; - int maxbits = context->maxbits; - huffman_error error; - bit_buffer bitbuf; - UINT32 dx, dy; - - /* regenerate the lookup table if necessary */ - if (context->lookupdirty) + // RLE encode the lengths + int lastval = ~0; + int repcount = 0; + for (int curcode = 0; curcode < m_numcodes; curcode++) { - error = build_lookup_table(context, HUFFMAN_CODES); - if (error != HUFFERR_NONE) - return error; - } - table = context->lookup; - - /* initialize our bit buffer */ - bit_buffer_read_init(&bitbuf, source, slength); + // if we match the previous value, just bump the repcount + int newval = m_huffnode[curcode].m_numbits; + if (newval == lastval) + repcount++; - /* iterate over "height" */ - for (dy = 0; dy < dheight; dy++) - { - /* iterate over "width" */ - for (dx = 0; dx < dwidth; dx++) + // otherwise, we need to flush the previous repeats + else { - huffman_lookup_value lookup; - UINT32 bits; - - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = table[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* store the upper byte */ - dest[dx ^ dxor] = LOOKUP_CODE(lookup); + if (repcount != 0) + write_rle_tree_bits(bitbuf, lastval, repcount, numbits); + lastval = newval; + repcount = 1; } - - /* advance to the next row */ - dest += dstride; } - /* determine the actual length and indicate overflow */ - *actlength = bit_buffer_read_offset(&bitbuf); - return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; + // flush the last value + write_rle_tree_bits(bitbuf, lastval, repcount, numbits); + return bitbuf.overflow() ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; } -/*------------------------------------------------- - huffman_decode_data_interleaved - decode - interleaved data using multiple contexts --------------------------------------------------*/ +//------------------------------------------------- +// import_tree_huffman - import a huffman-encoded +// huffman tree from a source data stream +//------------------------------------------------- -huffman_error huffman_decode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) +huffman_error huffman_context_base::import_tree_huffman(bitstream_in &bitbuf) { - UINT32 dx, dy, ctxnum; - huffman_error error; - bit_buffer bitbuf; - - /* regenerate the lookup tables if necessary */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) + // start by parsing the lengths for the small tree + huffman_decoder<24, 6> smallhuff; + smallhuff.m_huffnode[0].m_numbits = bitbuf.read(3); + int start = bitbuf.read(3) + 1; + int count = 0; + for (int index = 1; index < 24; index++) { - huffman_context *context = contexts[ctxnum]; - if (context->lookupdirty) + if (index < start || count == 7) + smallhuff.m_huffnode[index].m_numbits = 0; + else { - error = build_lookup_table(context, HUFFMAN_CODES); - if (error != HUFFERR_NONE) - return error; + count = bitbuf.read(3); + smallhuff.m_huffnode[index].m_numbits = (count == 7) ? 0 : count; } } - - /* initialize our bit buffer */ - bit_buffer_read_init(&bitbuf, source, slength); - - /* iterate over "height" */ - for (dy = 0; dy < dheight; dy++) - { - /* iterate over "width" */ - for (dx = 0; dx < dwidth; ) + + // then regenerate the tree + huffman_error error = smallhuff.assign_canonical_codes(); + if (error != HUFFERR_NONE) + return error; + smallhuff.build_lookup_table(); + + // determine the maximum length of an RLE count + UINT32 temp = m_numcodes - 9; + UINT8 rlefullbits = 0; + while (temp != 0) + temp >>= 1, rlefullbits++; + + // now process the rest of the data + int last = 0; + int curcode; + for (curcode = 0; curcode < m_numcodes; ) + { + int value = smallhuff.decode_one(bitbuf); + if (value != 0) + m_huffnode[curcode++].m_numbits = last = value - 1; + else { - /* iterate over contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, dx++) - { - huffman_context *context = contexts[ctxnum]; - huffman_lookup_value lookup; - UINT32 bits; - - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, context->maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = context->lookup[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* store the upper byte */ - dest[dx ^ dxor] = LOOKUP_CODE(lookup); - } + int count = bitbuf.read(3) + 2; + if (count == 7+2) + count += bitbuf.read(rlefullbits); + for ( ; count != 0 && curcode < m_numcodes; count--) + m_huffnode[curcode++].m_numbits = last; } - - /* advance to the next row */ - dest += dstride; - } - - /* determine the actual length and indicate overflow */ - *actlength = bit_buffer_read_offset(&bitbuf); - return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; -} - - -/*------------------------------------------------- - huffman_deltarle_decode_data - decode data - using the given tree with delta-RLE - post-decoding --------------------------------------------------*/ - -huffman_error huffman_deltarle_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) -{ - const huffman_lookup_value *table; - int maxbits = context->maxbits; - UINT32 rleremaining = 0; - huffman_error error; - UINT8 prevdata = 0; - bit_buffer bitbuf; - UINT32 dx, dy; - - /* regenerate the lookup table if necessary */ - if (context->lookupdirty) - { - error = build_lookup_table(context, HUFFMAN_DELTARLE_CODES); - if (error != HUFFERR_NONE) - return error; } - table = context->lookup; - - /* initialize our bit buffer */ - bit_buffer_read_init(&bitbuf, source, slength); - - /* iterate over "height" */ - for (dy = 0; dy < dheight; dy++) - { - /* reset RLE counts */ - rleremaining = 0; - - /* iterate over "width" */ - for (dx = 0; dx < dwidth; dx++) - { - huffman_lookup_value lookup; - UINT32 bits; - int data; - /* if we have RLE remaining, just store that */ - if (rleremaining != 0) - { - rleremaining--; - dest[dx ^ dxor] = prevdata; - continue; - } - - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = table[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* compute the data and handle RLE decoding */ - data = LOOKUP_CODE(lookup); - - /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ - if (data < 0x100) - prevdata += (UINT8)data; - else - rleremaining = code_to_rlecount(data) - 1; + // make sure we ended up with the right number + if (curcode != m_numcodes) + return HUFFERR_INVALID_DATA; - /* store the updated data value */ - dest[dx ^ dxor] = prevdata; - } + // assign canonical codes for all nodes based on their code lengths + error = assign_canonical_codes(); + if (error != HUFFERR_NONE) + return error; - /* advance to the next row */ - dest += dstride; - } + // build the lookup table + build_lookup_table(); - /* determine the actual length and indicate overflow */ - *actlength = bit_buffer_read_offset(&bitbuf); - return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; + // determine final input length and report errors + return bitbuf.overflow() ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; } -/*------------------------------------------------- - huffman_deltarle_decode_data_interleaved - - decode data using multiple contexts and - delta-RLE post-decoding --------------------------------------------------*/ +//------------------------------------------------- +// export_tree_huffman - export a huffman tree to +// a huffman target data stream +//------------------------------------------------- -huffman_error huffman_deltarle_decode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) +huffman_error huffman_context_base::export_tree_huffman(bitstream_out &bitbuf) { - UINT32 dx, dy, ctxnum; - huffman_error error; - bit_buffer bitbuf; - - /* fast case the A/V Y/Cb/Y/Cr case */ - if (numcontexts == 4 && contexts[0] == contexts[2] && contexts[0] != contexts[1] && contexts[1] != contexts[3] && - contexts[0]->maxbits == contexts[1]->maxbits && contexts[0]->maxbits == contexts[3]->maxbits) - return huffman_deltarle_decode_data_interleaved_0102(contexts, source, slength, dest, dwidth, dheight, dstride, dxor, actlength); + // first RLE compress the lengths of all the nodes + dynamic_array<UINT8> rle_data(m_numcodes); + UINT8 *dest = rle_data; + dynamic_array<UINT16> rle_lengths(m_numcodes/3); + UINT16 *lengths = rle_lengths; + int last = ~0; + int repcount = 0; + + // use a small huffman context to create a tree (ignoring RLE lengths) + huffman_encoder<24, 6> smallhuff; - /* regenerate the lookup tables if necessary */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) + // RLE-compress the lengths + for (int curcode = 0; curcode < m_numcodes; curcode++) { - huffman_context *context = contexts[ctxnum]; - if (context->lookupdirty) + // if this is the end of a repeat, flush any accumulation + int newval = m_huffnode[curcode].m_numbits; + if (newval != last && repcount > 0) { - error = build_lookup_table(context, HUFFMAN_DELTARLE_CODES); - if (error != HUFFERR_NONE) - return error; + if (repcount == 1) + smallhuff.histo_one(*dest++ = last + 1); + else + smallhuff.histo_one(*dest++ = 0), *lengths++ = repcount - 2; } - context->prevdata = 0; - } - - /* initialize our bit buffer */ - bit_buffer_read_init(&bitbuf, source, slength); - - /* iterate over "height" */ - for (dy = 0; dy < dheight; dy++) - { - /* reset RLE counts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) - { - huffman_context *context = contexts[ctxnum]; - context->rleremaining = 0; - } - - /* iterate over "width" */ - for (dx = 0; dx < dwidth; ) + + // if same as last, just track repeats + if (newval == last) + repcount++; + + // otherwise, write it and start a new run + else { - /* iterate over contexts */ - for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, dx++) - { - huffman_context *context = contexts[ctxnum]; - huffman_lookup_value lookup; - UINT32 bits; - int data; - - /* if we have RLE remaining, just store that */ - if (context->rleremaining != 0) - { - context->rleremaining--; - dest[dx ^ dxor] = context->prevdata; - continue; - } - - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, context->maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = context->lookup[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* compute the data and handle RLE decoding */ - data = LOOKUP_CODE(lookup); - - /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ - if (data < 0x100) - context->prevdata += (UINT8)data; - else - context->rleremaining = code_to_rlecount(data) - 1; - - /* store the updated data value */ - dest[dx ^ dxor] = context->prevdata; - } + smallhuff.histo_one(*dest++ = newval + 1); + last = newval; + repcount = 0; } - - /* advance to the next row */ - dest += dstride; } - /* determine the actual length and indicate overflow */ - *actlength = bit_buffer_read_offset(&bitbuf); - return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; -} - - -/*------------------------------------------------- - huffman_deltarle_decode_data_interleaved_0102 - - decode data using 3 unique contexts in - 0/1/0/2 order (used for Y/Cb/Y/Cr encoding) --------------------------------------------------*/ - -static huffman_error huffman_deltarle_decode_data_interleaved_0102(huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) -{ - const huffman_lookup_value *table02, *table1, *table3; - int rleremaining02, rleremaining1, rleremaining3; - UINT8 prevdata02 = 0, prevdata1 = 0, prevdata3 = 0; - int maxbits = contexts[0]->maxbits; - huffman_error error; - bit_buffer bitbuf; - UINT32 dx, dy; - - /* regenerate the lookup tables if necessary */ - if (contexts[0]->lookupdirty) - { - error = build_lookup_table(contexts[0], HUFFMAN_DELTARLE_CODES); - if (error != HUFFERR_NONE) - return error; - } - if (contexts[1]->lookupdirty) + // flush any final RLE counts + if (repcount > 0) { - error = build_lookup_table(contexts[1], HUFFMAN_DELTARLE_CODES); - if (error != HUFFERR_NONE) - return error; + if (repcount == 1) + smallhuff.histo_one(*dest++ = last + 1); + else + smallhuff.histo_one(*dest++ = 0), *lengths++ = repcount - 2; } - if (contexts[3]->lookupdirty) - { - error = build_lookup_table(contexts[3], HUFFMAN_DELTARLE_CODES); - if (error != HUFFERR_NONE) - return error; - } - - /* cache the tables locally */ - table02 = contexts[0]->lookup; - table1 = contexts[1]->lookup; - table3 = contexts[3]->lookup; - /* initialize our bit buffer */ - bit_buffer_read_init(&bitbuf, source, slength); + // compute an optimal tree + smallhuff.compute_tree_from_histo(); - /* iterate over "height" */ - for (dy = 0; dy < dheight; dy++) - { - /* reset RLE counts */ - rleremaining02 = rleremaining1 = rleremaining3 = 0; - - /* iterate over "width" */ - for (dx = 0; dx < dwidth; dx += 4) + // determine the first and last non-zero nodes + int first_non_zero = 31, last_non_zero = 0; + for (int index = 1; index < smallhuff.m_numcodes; index++) + if (smallhuff.m_huffnode[index].m_numbits != 0) { - huffman_lookup_value lookup; - UINT32 bits; - int data; - - /* ----- offset 0 ----- */ - - /* if we have RLE remaining, just store that */ - if (rleremaining02 != 0) - rleremaining02--; - else - { - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = table02[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* compute the data and handle RLE decoding */ - data = LOOKUP_CODE(lookup); - - /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ - if (data < 0x100) - prevdata02 += (UINT8)data; - else - rleremaining02 = code_to_rlecount(data) - 1; - } - - /* store the updated data value */ - dest[(dx + 0) ^ dxor] = prevdata02; - - /* ----- offset 1 ----- */ - - /* if we have RLE remaining, just store that */ - if (rleremaining1 != 0) - rleremaining1--; - else - { - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = table1[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* compute the data and handle RLE decoding */ - data = LOOKUP_CODE(lookup); - - /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ - if (data < 0x100) - prevdata1 += (UINT8)data; - else - rleremaining1 = code_to_rlecount(data) - 1; - } - - /* store the updated data value */ - dest[(dx + 1) ^ dxor] = prevdata1; - - /* ----- offset 2 (same as 0) ----- */ - - /* if we have RLE remaining, just store that */ - if (rleremaining02 != 0) - rleremaining02--; - else - { - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = table02[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* compute the data and handle RLE decoding */ - data = LOOKUP_CODE(lookup); - - /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ - if (data < 0x100) - prevdata02 += (UINT8)data; - else - rleremaining02 = code_to_rlecount(data) - 1; - } - - /* store the updated data value */ - dest[(dx + 2) ^ dxor] = prevdata02; - - /* ----- offset 3 ----- */ + if (first_non_zero == 31) + first_non_zero = index; + last_non_zero = index; + } - /* if we have RLE remaining, just store that */ - if (rleremaining3 != 0) - rleremaining3--; + // clamp first non-zero to be 8 at a maximum + first_non_zero = MIN(first_non_zero, 8); + + // output the lengths of the each small tree node, starting with the RLE + // token (0), followed by the first_non_zero value, followed by the data + // terminated by a 7 + bitbuf.write(smallhuff.m_huffnode[0].m_numbits, 3); + bitbuf.write(first_non_zero - 1, 3); + for (int index = first_non_zero; index <= last_non_zero; index++) + bitbuf.write(smallhuff.m_huffnode[index].m_numbits, 3); + bitbuf.write(7, 3); + + // determine the maximum length of an RLE count + UINT32 temp = m_numcodes - 9; + UINT8 rlefullbits = 0; + while (temp != 0) + temp >>= 1, rlefullbits++; + + // now encode the RLE data + lengths = rle_lengths; + for (UINT8 *src = rle_data; src < dest; src++) + { + // encode the data + UINT8 data = *src; + smallhuff.encode_one(bitbuf, data); + + // if this is an RLE token, encode the length following + if (data == 0) + { + int count = *lengths++; + if (count < 7) + bitbuf.write(count, 3); else - { - /* peek ahead to get maxbits worth of data */ - bits = bit_buffer_peek(&bitbuf, maxbits); - - /* look it up, then remove the actual number of bits for this code */ - lookup = table3[bits]; - bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); - - /* compute the data and handle RLE decoding */ - data = LOOKUP_CODE(lookup); - - /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ - if (data < 0x100) - prevdata3 += (UINT8)data; - else - rleremaining3 = code_to_rlecount(data) - 1; - } - - /* store the updated data value */ - dest[(dx + 3) ^ dxor] = prevdata3; + bitbuf.write(7, 3), bitbuf.write(count - 7, rlefullbits); } - - /* advance to the next row */ - dest += dstride; } - - /* determine the actual length and indicate overflow */ - *actlength = bit_buffer_read_offset(&bitbuf); - return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; + + // flush the final buffer + return bitbuf.overflow() ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; } +//------------------------------------------------- +// compute_tree_from_histo - common backend for +// computing a tree based on the data histogram +//------------------------------------------------- -/*************************************************************************** - INTERNAL FUNCTIONS -***************************************************************************/ - -/*------------------------------------------------- - import_tree - import a huffman tree from a - source data stream --------------------------------------------------*/ - -static huffman_error import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength, UINT32 numcodes) +huffman_error huffman_context_base::compute_tree_from_histo() { - huffman_error error; - bit_buffer bitbuf; - int curnode; - int numbits; - - /* initialize the input buffer */ - bit_buffer_read_init(&bitbuf, source, slength); - - /* bits per entry depends on the maxbits */ - if (context->maxbits >= 16) - numbits = 5; - else if (context->maxbits >= 8) - numbits = 4; - else - numbits = 3; + // compute the number of data items in the histogram + UINT32 sdatacount = 0; + for (int i = 0; i < m_numcodes; i++) + sdatacount += m_datahisto[i]; - /* loop until we read all the nodes */ - for (curnode = 0; curnode < numcodes; ) + // binary search to achieve the optimum encoding + UINT32 lowerweight = 0; + UINT32 upperweight = sdatacount * 2; + while (1) { - int nodebits = bit_buffer_read(&bitbuf, numbits); - - /* a non-one value is just raw */ - if (nodebits != 1) - context->huffnode[curnode++].numbits = nodebits; + // build a tree using the current weight + UINT32 curweight = (upperweight + lowerweight) / 2; + int curmaxbits = build_tree(sdatacount, curweight); - /* a one value is an escape code */ - else + // apply binary search here + if (curmaxbits <= m_maxbits) { - nodebits = bit_buffer_read(&bitbuf, numbits); - - /* a double 1 is just a single 1 */ - if (nodebits == 1) - context->huffnode[curnode++].numbits = nodebits; + lowerweight = curweight; - /* otherwise, we need one for value for the repeat count */ - else - { - int repcount = bit_buffer_read(&bitbuf, numbits) + 3; - while (repcount--) - context->huffnode[curnode++].numbits = nodebits; - } + // early out if it worked with the raw weights, or if we're done searching + if (curweight == sdatacount || (upperweight - lowerweight) <= 1) + break; } + else + upperweight = curweight; } - /* assign canonical codes for all nodes based on their code lengths */ - error = assign_canonical_codes(context, numcodes); - if (error != HUFFERR_NONE) - return error; - - /* make sure we ended up with the right number */ - if (curnode != numcodes) - return HUFFERR_INVALID_DATA; - - *actlength = bit_buffer_read_offset(&bitbuf); - return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; + // assign canonical codes for all nodes based on their code lengths + return assign_canonical_codes(); } -/*------------------------------------------------- - export_tree - export a huffman tree to a - target data stream --------------------------------------------------*/ - -static huffman_error export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength, UINT32 numcodes) -{ - bit_buffer bitbuf; - int repcount; - int lastval; - int numbits; - int i; - - /* initialize the output buffer */ - bit_buffer_write_init(&bitbuf, dest, dlength); - - /* bits per entry depends on the maxbits */ - if (context->maxbits >= 16) - numbits = 5; - else if (context->maxbits >= 8) - numbits = 4; - else - numbits = 3; - - /* RLE encode the lengths */ - lastval = ~0; - repcount = 0; - for (i = 0; i < numcodes; i++) - { - int newval = context->huffnode[i].numbits; - - /* if we match the previous value, just bump the repcount */ - if (newval == lastval) - repcount++; - - /* otherwise, we need to flush the previous repeats */ - else - { - if (repcount != 0) - write_rle_tree_bits(&bitbuf, lastval, repcount, numbits); - lastval = newval; - repcount = 1; - } - } - - /* flush the last value */ - write_rle_tree_bits(&bitbuf, lastval, repcount, numbits); - *actlength = bit_buffer_flush(&bitbuf); - return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; -} +//************************************************************************** +// INTERNAL FUNCTIONS +//************************************************************************** -/*------------------------------------------------- - write_rle_tree_bits - write an RLE encoded - set of data to a target stream --------------------------------------------------*/ +//------------------------------------------------- +// write_rle_tree_bits - write an RLE encoded +// set of data to a target stream +//------------------------------------------------- -static void write_rle_tree_bits(bit_buffer *bitbuf, int value, int repcount, int numbits) +void huffman_context_base::write_rle_tree_bits(bitstream_out &bitbuf, int value, int repcount, int numbits) { - /* loop until we have output all of the repeats */ + // loop until we have output all of the repeats while (repcount > 0) { - /* if we have a 1, write it twice as it is an escape code */ + // if we have a 1, write it twice as it is an escape code if (value == 1) { - bit_buffer_write(bitbuf, 1, numbits); - bit_buffer_write(bitbuf, 1, numbits); + bitbuf.write(1, numbits); + bitbuf.write(1, numbits); repcount--; } - /* if we have two or fewer in a row, write them raw */ + // if we have two or fewer in a row, write them raw else if (repcount <= 2) { - bit_buffer_write(bitbuf, value, numbits); + bitbuf.write(value, numbits); repcount--; } - /* otherwise, write a triple using 1 as the escape code */ + // otherwise, write a triple using 1 as the escape code else { int cur_reps = MIN(repcount - 3, (1 << numbits) - 1); - bit_buffer_write(bitbuf, 1, numbits); - bit_buffer_write(bitbuf, value, numbits); - bit_buffer_write(bitbuf, cur_reps, numbits); + bitbuf.write(1, numbits); + bitbuf.write(value, numbits); + bitbuf.write(cur_reps, numbits); repcount -= cur_reps + 3; } } } -/*------------------------------------------------- - tree_node_compare - compare two tree nodes - by weight --------------------------------------------------*/ - -static int CLIB_DECL tree_node_compare(const void *item1, const void *item2) -{ - const huffman_node *node1 = *(const huffman_node **)item1; - const huffman_node *node2 = *(const huffman_node **)item2; - return node2->weight - node1->weight; -} - - -/*------------------------------------------------- - compute_optimal_tree - common backend for - computing a tree based on the data histogram --------------------------------------------------*/ +//------------------------------------------------- +// tree_node_compare - compare two tree nodes +// by weight +//------------------------------------------------- -static huffman_error compute_optimal_tree(huffman_context *context, const UINT32 *datahisto, UINT32 numcodes) +int CLIB_DECL huffman_context_base::tree_node_compare(const void *item1, const void *item2) { - UINT32 lowerweight, upperweight; - UINT32 sdatacount; - int i; - - /* compute the number of data items in the histogram */ - sdatacount = 0; - for (i = 0; i < numcodes; i++) - sdatacount += datahisto[i]; - - /* binary search to achieve the optimum encoding */ - lowerweight = 0; - upperweight = sdatacount * 2; - while (TRUE) - { - UINT32 curweight = (upperweight + lowerweight) / 2; - int curmaxbits; - - /* build a tree using the current weight */ - curmaxbits = huffman_build_tree(context, datahisto, sdatacount, curweight, numcodes); - - /* apply binary search here */ - if (curmaxbits <= context->maxbits) - { - lowerweight = curweight; - - /* early out if it worked with the raw weights, or if we're done searching */ - if (curweight == sdatacount || (upperweight - lowerweight) <= 1) - break; - } - else - upperweight = curweight; - } - - /* assign canonical codes for all nodes based on their code lengths */ - return assign_canonical_codes(context, numcodes); + const node_t *node1 = *(const node_t **)item1; + const node_t *node2 = *(const node_t **)item2; + return node2->m_weight - node1->m_weight; } -/*------------------------------------------------- - huffman_build_tree - build a huffman tree - based on the data distribution --------------------------------------------------*/ +//------------------------------------------------- +// build_tree - build a huffman tree based on the +// data distribution +//------------------------------------------------- -static int huffman_build_tree(huffman_context *context, const UINT32 *datahisto, UINT32 totaldata, UINT32 totalweight, UINT32 numcodes) +int huffman_context_base::build_tree(UINT32 totaldata, UINT32 totalweight) { - huffman_node *list[MAX_HUFFMAN_CODES]; - int listitems; - int nextalloc; - int maxbits; - int i; - - /* make a list of all non-zero nodes */ - listitems = 0; - memset(context->huffnode, 0, numcodes * sizeof(context->huffnode[0])); - for (i = 0; i < numcodes; i++) - if (datahisto[i] != 0) + // make a list of all non-zero nodes + dynamic_array<node_t *> list(m_numcodes * 2); + int listitems = 0; + memset(m_huffnode, 0, m_numcodes * sizeof(m_huffnode[0])); + for (int curcode = 0; curcode < m_numcodes; curcode++) + if (m_datahisto[curcode] != 0) { - list[listitems++] = &context->huffnode[i]; - context->huffnode[i].count = datahisto[i]; + list[listitems++] = &m_huffnode[curcode]; + m_huffnode[curcode].m_count = m_datahisto[curcode]; - /* scale the weight by the current effective length, ensuring we don't go to 0 */ - context->huffnode[i].weight = (UINT64)datahisto[i] * (UINT64)totalweight / (UINT64)totaldata; - if (context->huffnode[i].weight == 0) - context->huffnode[i].weight = 1; + // scale the weight by the current effective length, ensuring we don't go to 0 + m_huffnode[curcode].m_weight = UINT64(m_datahisto[curcode]) * UINT64(totalweight) / UINT64(totaldata); + if (m_huffnode[curcode].m_weight == 0) + m_huffnode[curcode].m_weight = 1; } - /* sort the list by weight, largest weight first */ + // sort the list by weight, largest weight first qsort(list, listitems, sizeof(list[0]), tree_node_compare); - /* now build the tree */ - nextalloc = MAX_HUFFMAN_CODES; + // now build the tree + int nextalloc = m_numcodes; while (listitems > 1) { - huffman_node *node0, *node1, *newnode; - - /* remove lowest two items */ - node1 = list[--listitems]; - node0 = list[--listitems]; + // remove lowest two items + node_t &node1 = *list[--listitems]; + node_t &node0 = *list[--listitems]; - /* create new node */ - newnode = &context->huffnode[nextalloc++]; - newnode->parent = NULL; - node0->parent = node1->parent = newnode; - newnode->weight = node0->weight + node1->weight; + // create new node + node_t &newnode = m_huffnode[nextalloc++]; + newnode.m_parent = NULL; + node0.m_parent = node1.m_parent = &newnode; + newnode.m_weight = node0.m_weight + node1.m_weight; - /* insert into list at appropriate location */ - for (i = 0; i < listitems; i++) - if (newnode->weight > list[i]->weight) + // insert into list at appropriate location + int curitem; + for (curitem = 0; curitem < listitems; curitem++) + if (newnode.m_weight > list[curitem]->m_weight) { - memmove(&list[i+1], &list[i], (listitems - i) * sizeof(list[0])); + memmove(&list[curitem+1], &list[curitem], (listitems - curitem) * sizeof(list[0])); break; } - list[i] = newnode; + list[curitem] = &newnode; listitems++; } - /* compute the number of bits in each code, and fill in another histogram */ - maxbits = 0; - for (i = 0; i < numcodes; i++) + // compute the number of bits in each code, and fill in another histogram + int maxbits = 0; + for (int curcode = 0; curcode < m_numcodes; curcode++) { - huffman_node *node = &context->huffnode[i]; - node->numbits = 0; + node_t &node = m_huffnode[curcode]; + node.m_numbits = 0; - /* if we have a non-zero weight, compute the number of bits */ - if (node->weight > 0) + // if we have a non-zero weight, compute the number of bits + if (node.m_weight > 0) { - huffman_node *curnode; - - /* determine the number of bits for this node */ - for (curnode = node; curnode->parent != NULL; curnode = curnode->parent) - node->numbits++; - if (node->numbits == 0) - node->numbits = 1; - - /* keep track of the max */ - maxbits = MAX(maxbits, node->numbits); + // determine the number of bits for this node + for (node_t *curnode = &node; curnode->m_parent != NULL; curnode = curnode->m_parent) + node.m_numbits++; + if (node.m_numbits == 0) + node.m_numbits = 1; + + // keep track of the max + maxbits = MAX(maxbits, node.m_numbits); } } - return maxbits; } -/*------------------------------------------------- - assign_canonical_codes - assign - canonical codes to all the nodes based on the - number of bits in each --------------------------------------------------*/ +//------------------------------------------------- +// assign_canonical_codes - assign canonical codes +// to all the nodes based on the number of bits +// in each +//------------------------------------------------- -static huffman_error assign_canonical_codes(huffman_context *context, UINT32 numcodes) +huffman_error huffman_context_base::assign_canonical_codes() { - UINT32 bithisto[33]; - int curstart; - int i; - - /* build up a histogram of bit lengths */ - memset(bithisto, 0, sizeof(bithisto)); - for (i = 0; i < numcodes; i++) + // build up a histogram of bit lengths + UINT32 bithisto[33] = { 0 }; + for (int curcode = 0; curcode < m_numcodes; curcode++) { - huffman_node *node = &context->huffnode[i]; - if (node->numbits > context->maxbits) + node_t &node = m_huffnode[curcode]; + if (node.m_numbits > m_maxbits) return HUFFERR_INTERNAL_INCONSISTENCY; - if (node->numbits <= 32) - bithisto[node->numbits]++; + if (node.m_numbits <= 32) + bithisto[node.m_numbits]++; } - /* for each code length, determine the starting code number */ - curstart = 0; - for (i = 32; i > 0; i--) + // for each code length, determine the starting code number + UINT32 curstart = 0; + for (int codelen = 32; codelen > 0; codelen--) { - UINT32 nextstart = (curstart + bithisto[i]) >> 1; - if (i != 1 && nextstart * 2 != (curstart + bithisto[i])) + UINT32 nextstart = (curstart + bithisto[codelen]) >> 1; + if (codelen != 1 && nextstart * 2 != (curstart + bithisto[codelen])) return HUFFERR_INTERNAL_INCONSISTENCY; - bithisto[i] = curstart; + bithisto[codelen] = curstart; curstart = nextstart; } - /* now assign canonical codes */ - for (i = 0; i < numcodes; i++) + // now assign canonical codes + for (int curcode = 0; curcode < m_numcodes; curcode++) { - huffman_node *node = &context->huffnode[i]; - if (node->numbits > 0) - node->bits = bithisto[node->numbits]++; + node_t &node = m_huffnode[curcode]; + if (node.m_numbits > 0) + node.m_bits = bithisto[node.m_numbits]++; } - - /* if there was a decoding table, get rid of it now */ - context->lookupdirty = TRUE; return HUFFERR_NONE; } -/*------------------------------------------------- - build_lookup_table - build a lookup - table for fast decoding --------------------------------------------------*/ +//------------------------------------------------- +// build_lookup_table - build a lookup table for +// fast decoding +//------------------------------------------------- -static huffman_error build_lookup_table(huffman_context *context, UINT32 numcodes) +void huffman_context_base::build_lookup_table() { - int i; - - /* allocate a table if needed */ - if (context->lookup == NULL) - context->lookup = (huffman_lookup_value *)malloc((UINT32)sizeof(context->lookup[0]) * (UINT32)(1 << context->maxbits)); - if (context->lookup == NULL) - return HUFFERR_OUT_OF_MEMORY; - - /* now build */ - for (i = 0; i < numcodes; i++) + // iterate over all codes + for (int curcode = 0; curcode < m_numcodes; curcode++) { - huffman_node *node = &context->huffnode[i]; - if (node->numbits > 0) + // process all nodes which have non-zero bits + node_t &node = m_huffnode[curcode]; + if (node.m_numbits > 0) { - huffman_lookup_value *dest, *destend; + // set up the entry + lookup_value value = MAKE_LOOKUP(curcode, node.m_numbits); - /* left justify this node's bit values to max bits */ - int shift = context->maxbits - node->numbits; - UINT32 start = node->bits << shift; - UINT32 end = ((node->bits + 1) << shift) - 1; - huffman_lookup_value value; - - /* set up the entry */ - value = (i << 6) | node->numbits; - - /* fill all matching entries */ - dest = &context->lookup[start]; - destend = &context->lookup[end]; + // fill all matching entries + int shift = m_maxbits - node.m_numbits; + lookup_value *dest = &m_lookup[node.m_bits << shift]; + lookup_value *destend = &m_lookup[((node.m_bits + 1) << shift) - 1]; while (dest <= destend) *dest++ = value; } } +} - /* no longer dirty */ - context->lookupdirty = FALSE; - return HUFFERR_NONE; + + +//************************************************************************** +// 8-BIT ENCODER +//************************************************************************** + +//------------------------------------------------- +// huffman_8bit_encoder - constructor +//------------------------------------------------- + +huffman_8bit_encoder::huffman_8bit_encoder() +{ +} + + +//------------------------------------------------- +// encode - encode a full buffer +//------------------------------------------------- + +huffman_error huffman_8bit_encoder::encode(const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 &complength) +{ + // first compute the histogram + histo_reset(); + for (UINT32 cur = 0; cur < slength; cur++) + histo_one(source[cur]); + + // then compute the tree + huffman_error err = compute_tree_from_histo(); + if (err != HUFFERR_NONE) + return err; + + // export the tree + bitstream_out bitbuf(dest, dlength); + err = export_tree_huffman(bitbuf); + if (err != HUFFERR_NONE) + return err; + + // then encode the data + for (UINT32 cur = 0; cur < slength; cur++) + encode_one(bitbuf, source[cur]); + complength = bitbuf.flush(); + return bitbuf.overflow() ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + + +//************************************************************************** +// 8-BIT DECODER +//************************************************************************** + +//------------------------------------------------- +// huffman_8bit_decoder - constructor +//------------------------------------------------- + +huffman_8bit_decoder::huffman_8bit_decoder() +{ +} + + +//------------------------------------------------- +// decode - decode a full buffer +//------------------------------------------------- + +huffman_error huffman_8bit_decoder::decode(const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength) +{ + // first import the tree + bitstream_in bitbuf(source, slength); + huffman_error err = import_tree_huffman(bitbuf); + if (err != HUFFERR_NONE) + return err; + + // then decode the data + for (UINT32 cur = 0; cur < dlength; cur++) + dest[cur] = decode_one(bitbuf); + bitbuf.flush(); + return bitbuf.overflow() ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; } diff --git a/src/lib/util/huffman.h b/src/lib/util/huffman.h index f512975c77f..10dc301e7df 100644 --- a/src/lib/util/huffman.h +++ b/src/lib/util/huffman.h @@ -2,7 +2,7 @@ huffman.h - Huffman compression routines. + Static Huffman compression and decompression helpers. **************************************************************************** @@ -37,19 +37,22 @@ ***************************************************************************/ +#pragma once + #ifndef __HUFFMAN_H__ +#define __HUFFMAN_H__ #include "osdcore.h" +#include "bitstream.h" -/*************************************************************************** - CONSTANTS -***************************************************************************/ +//************************************************************************** +// CONSTANTS +//************************************************************************** -enum _huffman_error +enum huffman_error { HUFFERR_NONE = 0, - HUFFERR_OUT_OF_MEMORY, HUFFERR_TOO_MANY_BITS, HUFFERR_INVALID_DATA, HUFFERR_INPUT_BUFFER_TOO_SMALL, @@ -57,45 +60,193 @@ enum _huffman_error HUFFERR_INTERNAL_INCONSISTENCY, HUFFERR_TOO_MANY_CONTEXTS }; -typedef enum _huffman_error huffman_error; -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** -typedef UINT16 huffman_lookup_value; +// ======================> huffman_context_base -typedef struct _huffman_context huffman_context; +// base class for encoding and decoding +class huffman_context_base +{ +protected: + typedef UINT16 lookup_value; + + // a node in the huffman tree + struct node_t + { + node_t * m_parent; // pointer to parent node + UINT32 m_count; // number of hits on this node + UINT32 m_weight; // assigned weight of this node + UINT32 m_bits; // bits used to encode the node + UINT8 m_numbits; // number of bits needed for this node + }; + + // construction/destruction + huffman_context_base(int numcodes, int maxbits, lookup_value *lookup, UINT32 *histo, node_t *nodes); + + // tree creation + huffman_error compute_tree_from_histo(); + + // static tree import; huffman is notably more efficient + huffman_error import_tree_rle(bitstream_in &bitbuf); + huffman_error import_tree_huffman(bitstream_in &bitbuf); + + // static tree export + huffman_error export_tree_rle(bitstream_out &bitbuf); + huffman_error export_tree_huffman(bitstream_out &bitbuf); + + // internal helpers + void write_rle_tree_bits(bitstream_out &bitbuf, int value, int repcount, int numbits); + static int CLIB_DECL tree_node_compare(const void *item1, const void *item2); + int build_tree(UINT32 totaldata, UINT32 totalweight); + huffman_error assign_canonical_codes(); + void build_lookup_table(); + +protected: + // internal state + UINT32 m_numcodes; // number of total codes being processed + UINT8 m_maxbits; // maximum bits per code + UINT8 m_prevdata; // value of the previous data (for delta-RLE encoding) + int m_rleremaining; // number of RLE bytes remaining (for delta-RLE encoding) + lookup_value * m_lookup; // pointer to the lookup table + UINT32 * m_datahisto; // histogram of data values + node_t * m_huffnode; // array of nodes +}; +// ======================> huffman_encoder -/*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ +// template class for encoding +template<int _NumCodes = 256, int _MaxBits = 16> +class huffman_encoder : public huffman_context_base +{ +public: + // pass through to the underlying constructor + huffman_encoder() + : huffman_context_base(_NumCodes, _MaxBits, NULL, m_datahisto_array, m_huffnode_array) { histo_reset(); } + + // single item operations + void histo_reset() { memset(m_datahisto_array, 0, sizeof(m_datahisto_array)); } + void histo_one(UINT32 data); + void encode_one(bitstream_out &bitbuf, UINT32 data); + + // expose tree computation and export + using huffman_context_base::compute_tree_from_histo; + using huffman_context_base::export_tree_rle; + using huffman_context_base::export_tree_huffman; + +private: + // array versions of the info we need + UINT32 m_datahisto_array[_NumCodes]; + node_t m_huffnode_array[_NumCodes * 2]; +}; + + +// ======================> huffman_decoder -huffman_error huffman_create_context(huffman_context **context, int maxbits); -void huffman_free_context(huffman_context *context); +// template class for decoding +template<int _NumCodes = 256, int _MaxBits = 16> +class huffman_decoder : public huffman_context_base +{ +public: + // pass through to the underlying constructor + huffman_decoder() + : huffman_context_base(_NumCodes, _MaxBits, m_lookup_array, NULL, m_huffnode_array) { } + + // single item operations + UINT32 decode_one(bitstream_in &bitbuf); + + // expose tree import + using huffman_context_base::import_tree_rle; + using huffman_context_base::import_tree_huffman; + +private: + // array versions of the info we need + node_t m_huffnode_array[_NumCodes]; + lookup_value m_lookup_array[1 << _MaxBits]; +}; -huffman_error huffman_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength); -huffman_error huffman_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength); -huffman_error huffman_deltarle_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength); -huffman_error huffman_deltarle_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength); -huffman_error huffman_compute_tree(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor); -huffman_error huffman_compute_tree_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor); -huffman_error huffman_deltarle_compute_tree(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor); -huffman_error huffman_deltarle_compute_tree_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor); +// ======================> huffman_8bit_encoder + +// generic 8-bit encoder/decoder +class huffman_8bit_encoder : public huffman_encoder<> +{ +public: + // construction/destruction + huffman_8bit_encoder(); + + // operations + huffman_error encode(const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 destlength, UINT32 &complength); +}; + + +// ======================> huffman_8bit_decoder + +// generic 8-bit encoder/decoder +class huffman_8bit_decoder : public huffman_decoder<> +{ +public: + // construction/destruction + huffman_8bit_decoder(); + + // operations + huffman_error decode(const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 destlength); +}; + + + +//************************************************************************** +// INLINE FUNCTIONS +//************************************************************************** + +//------------------------------------------------- +// histo_one - update the histogram +//------------------------------------------------- + +template<int _NumCodes, int _MaxBits> +inline void huffman_encoder<_NumCodes, _MaxBits>::histo_one(UINT32 data) +{ + m_datahisto[data]++; +} + + +//------------------------------------------------- +// encode_one - encode a single code to the +// huffman stream +//------------------------------------------------- + +template<int _NumCodes, int _MaxBits> +inline void huffman_encoder<_NumCodes, _MaxBits>::encode_one(bitstream_out &bitbuf, UINT32 data) +{ + // write the data + node_t &node = m_huffnode[data]; + bitbuf.write(node.m_bits, node.m_numbits); +} + + +//------------------------------------------------- +// decode_one - decode a single code from the +// huffman stream +//------------------------------------------------- + +template<int _NumCodes, int _MaxBits> +inline UINT32 huffman_decoder<_NumCodes, _MaxBits>::decode_one(bitstream_in &bitbuf) +{ + // peek ahead to get maxbits worth of data + UINT32 bits = bitbuf.peek(m_maxbits); + + // look it up, then remove the actual number of bits for this code + lookup_value lookup = m_lookup[bits]; + bitbuf.remove(lookup & 0x1f); -huffman_error huffman_encode_data(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength); -huffman_error huffman_encode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength); -huffman_error huffman_deltarle_encode_data(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength); -huffman_error huffman_deltarle_encode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength); + // return the value + return lookup >> 5; +} -huffman_error huffman_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength); -huffman_error huffman_decode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength); -huffman_error huffman_deltarle_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength); -huffman_error huffman_deltarle_decode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength); #endif diff --git a/src/lib/util/tagmap.h b/src/lib/util/tagmap.h index 4bbe458f92b..63855b30e07 100644 --- a/src/lib/util/tagmap.h +++ b/src/lib/util/tagmap.h @@ -158,7 +158,7 @@ public: for (entry_t *entry = m_table[fullhash % ARRAY_LENGTH(m_table)]; entry != NULL; entry = entry->next()) if (entry->fullhash() == fullhash && entry->tag() == tag) return entry->object(); - return (_ElementType)NULL; + return _ElementType(NULL); } // find by tag without checking anything but the hash diff --git a/src/mame/drivers/taitogn.c b/src/mame/drivers/taitogn.c index 97fa478e3dd..aec578c9628 100644 --- a/src/mame/drivers/taitogn.c +++ b/src/mame/drivers/taitogn.c @@ -451,7 +451,7 @@ static WRITE32_HANDLER(rf5c296_mem_w) taitogn_state *state = space->machine().driver_data<taitogn_state>(); if(offset >= 0x140 && offset <= 0x144) { - UINT8 key[5]; + dynamic_buffer key; int pos = (offset - 0x140)*2; UINT8 v, k; if(ACCESSING_BITS_16_23) { @@ -459,8 +459,8 @@ static WRITE32_HANDLER(rf5c296_mem_w) pos++; } else v = data; - chd_get_metadata(get_disk_handle(space->machine(), ":card"), HARD_DISK_KEY_METADATA_TAG, 0, key, 5, 0, 0, 0); - k = pos < 5 ? key[pos] : 0; + get_disk_handle(space->machine(), ":card")->read_metadata(HARD_DISK_KEY_METADATA_TAG, 0, key); + k = pos < key.count() ? key[pos] : 0; if(v == k) state->m_locked &= ~(1 << pos); else @@ -889,9 +889,10 @@ static DRIVER_INIT( coh3002t ) psx_sio_install_handler(machine, 0, sio_pad_handler); state->m_dip_timer = machine.scheduler().timer_alloc( FUNC(dip_timer_fired), NULL ); + UINT32 metalength; memset(state->m_cis, 0xff, 512); if (get_disk_handle(machine, ":card") != NULL) - chd_get_metadata(get_disk_handle(machine, ":card"), PCMCIA_CIS_METADATA_TAG, 0, state->m_cis, 512, 0, 0, 0); + get_disk_handle(machine, ":card")->read_metadata(PCMCIA_CIS_METADATA_TAG, 0, state->m_cis, 512, metalength); } static DRIVER_INIT( coh3002t_mp ) diff --git a/src/osd/windows/wintime.c b/src/osd/windows/wintime.c index 2021eaa8eb9..1d3c3d5ef9c 100644 --- a/src/osd/windows/wintime.c +++ b/src/osd/windows/wintime.c @@ -104,6 +104,8 @@ osd_ticks_t osd_ticks(void) osd_ticks_t osd_ticks_per_second(void) { + if (ticks_per_second == 0) + osd_ticks(); return ticks_per_second; } diff --git a/src/osd/windows/winwork.c b/src/osd/windows/winwork.c index dac4237c014..18c54f06e84 100644 --- a/src/osd/windows/winwork.c +++ b/src/osd/windows/winwork.c @@ -321,9 +321,14 @@ osd_work_queue *osd_work_queue_alloc(int flags) if (numprocs == 1) queue->threads = (flags & WORK_QUEUE_FLAG_IO) ? 1 : 0; - // on an n-CPU system, create (n-1) threads for multi queues, and 1 thread for everything else + // on an n-CPU system, create n threads for multi queues, and 1 thread for everything else else - queue->threads = (flags & WORK_QUEUE_FLAG_MULTI) ? (numprocs - 1) : 1; + queue->threads = (flags & WORK_QUEUE_FLAG_MULTI) ? numprocs : 1; + + // multi-queues with high frequency items should top out at 4 for now + // since we have scaling problems above that + if ((flags & WORK_QUEUE_FLAG_HIGH_FREQ) && queue->threads > 1) + queue->threads = MIN(queue->threads - 1, 4); // clamp to the maximum queue->threads = MIN(queue->threads, WORK_MAX_THREADS); @@ -706,13 +711,12 @@ static int effective_num_processors(void) int numprocs = 0; // if the OSDPROCESSORS environment variable is set, use that value if valid + // note that we permit more than the real number of processors for testing procsoverride = _tgetenv(_T("OSDPROCESSORS")); if (procsoverride != NULL && _stscanf(procsoverride, _T("%d"), &numprocs) == 1 && numprocs > 0) - // Be well behaved ... return MIN(info.dwNumberOfProcessors * 4, numprocs); - // max out at 4 for now since scaling above that seems to do poorly - return MIN(info.dwNumberOfProcessors, 4); + return info.dwNumberOfProcessors; } } diff --git a/src/tools/chdman.c b/src/tools/chdman.c index ca473ef699f..32cddcd65d2 100644 --- a/src/tools/chdman.c +++ b/src/tools/chdman.c @@ -39,11 +39,12 @@ #include "corefile.h" #include "chdcd.h" #include "aviio.h" -#include "avcomp.h" +#include "avhuff.h" #include "bitmap.h" #include "md5.h" #include "sha1.h" #include "vbiparse.h" +#include "tagmap.h" #include <stdarg.h> #include <stdlib.h> #include <stdio.h> @@ -51,3398 +52,2609 @@ #include <ctype.h> #include <new> - -/*************************************************************************** - CONSTANTS & DEFINES -***************************************************************************/ - -#define IDE_SECTOR_SIZE 512 - -#define ENABLE_CUSTOM_CHOMP 0 - -#define OPERATION_UPDATE 0 -#define OPERATION_MERGE 1 -#define OPERATION_CHOMP 2 - -#ifdef PTR64 -#define IS_FAKE_AVI_FILE(a) ((UINT64)(a) <= 2) +#ifdef _WIN32 +#define NUM_PROCESSORS_SUPPORTED 1 #else -#define IS_FAKE_AVI_FILE(a) ((UINT32)(a) <= 2) +#define NUM_PROCESSORS_SUPPORTED 0 #endif -#define AVI_FAKE_FILE_22 ((avi_file *)1) -#define AVI_FAKE_FILE_32 ((avi_file *)2) - -#define AVI_FAKE_FRAMERATE 29970000 -#define AVI_FAKE_FRAMES 54004 -#define AVI_FAKE_WIDTH 720 -#define AVI_FAKE_HEIGHT 524 -#define AVI_FAKE_CHANNELS 2 -#define AVI_FAKE_SAMPLERATE 48000 - -/*************************************************************************** - TYPE DEFINITIONS -***************************************************************************/ - -struct _chd_interface_file +//************************************************************************** +// CONSTANTS & DEFINES +//************************************************************************** + +// default hard disk sector size +const UINT32 IDE_SECTOR_SIZE = 512; + +// temporary input buffer size +const UINT32 TEMP_BUFFER_SIZE = 32 * 1024 * 1024; + +// command modifier +#define REQUIRED "~" + +// command strings +#define COMMAND_HELP "help" +#define COMMAND_INFO "info" +#define COMMAND_VERIFY "verify" +#define COMMAND_CREATE_RAW "createraw" +#define COMMAND_CREATE_HD "createhd" +#define COMMAND_CREATE_CD "createcd" +#define COMMAND_CREATE_LD "createld" +#define COMMAND_EXTRACT_RAW "extractraw" +#define COMMAND_EXTRACT_HD "extracthd" +#define COMMAND_EXTRACT_CD "extractcd" +#define COMMAND_EXTRACT_LD "extractld" +#define COMMAND_COPY "copy" +#define COMMAND_ADD_METADATA "addmeta" +#define COMMAND_DEL_METADATA "delmeta" + +// option strings +#define OPTION_INPUT "input" +#define OPTION_OUTPUT "output" +#define OPTION_OUTPUT_BIN "outputbin" +#define OPTION_OUTPUT_FORCE "force" +#define OPTION_INPUT_START_BYTE "inputstartbyte" +#define OPTION_INPUT_START_HUNK "inputstarthunk" +#define OPTION_INPUT_START_FRAME "inputstartframe" +#define OPTION_INPUT_LENGTH_BYTES "inputbytes" +#define OPTION_INPUT_LENGTH_HUNKS "inputhunks" +#define OPTION_INPUT_LENGTH_FRAMES "inputframes" +#define OPTION_HUNK_SIZE "hunksize" +#define OPTION_UNIT_SIZE "unitsize" +#define OPTION_COMPRESSION "compression" +#define OPTION_INPUT_PARENT "inputparent" +#define OPTION_OUTPUT_PARENT "outputparent" +#define OPTION_IDENT "ident" +#define OPTION_CHS "chs" +#define OPTION_SECTOR_SIZE "sectorsize" +#define OPTION_TAG "tag" +#define OPTION_INDEX "index" +#define OPTION_VALUE_TEXT "valuetext" +#define OPTION_VALUE_FILE "valuefile" +#define OPTION_NO_CHECKSUM "nochecksum" +#define OPTION_VERBOSE "verbose" +#define OPTION_FIX "fix" +#define OPTION_NUMPROCESSORS "numprocessors" + + + +//************************************************************************** +// FUNCTION PROTOTYPES +//************************************************************************** + +typedef tagmap_t<astring *> parameters_t; + +static void report_error(int error, const char *format, ...); +static void do_info(parameters_t ¶ms); +static void do_verify(parameters_t ¶ms); +static void do_create_raw(parameters_t ¶ms); +static void do_create_hd(parameters_t ¶ms); +static void do_create_cd(parameters_t ¶ms); +static void do_create_ld(parameters_t ¶ms); +static void do_copy(parameters_t ¶ms); +static void do_extract_raw(parameters_t ¶ms); +static void do_extract_cd(parameters_t ¶ms); +static void do_extract_ld(parameters_t ¶ms); +static void do_add_metadata(parameters_t ¶ms); +static void do_del_metadata(parameters_t ¶ms); + + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// ======================> option_description + +struct option_description { - osd_file *file; - UINT64 length; + const char *name; + const char *shortname; + bool parameter; + const char *description; }; +// ======================> command_description -/*************************************************************************** - FUNCTION PROTOTYPES -***************************************************************************/ - -static chd_error chdman_compress_file(chd_file *chd, const char *rawfile, UINT32 offset); -static chd_error chdman_compress_chd(chd_file *chd, chd_file *source, UINT32 totalhunks); -static chd_error chdman_clone_metadata(chd_file *source, chd_file *dest); - - - -/*************************************************************************** - GLOBAL VARIABLES -***************************************************************************/ - -static clock_t lastprogress = 0; - - - -/*************************************************************************** - IMPLEMENTATION -***************************************************************************/ - -/*------------------------------------------------- - print_big_int - 64-bit int printing with - commas --------------------------------------------------*/ - -static void print_big_int(UINT64 intvalue, char *output) +struct command_description { - int chunk; - - chunk = intvalue % 1000; - intvalue /= 1000; - if (intvalue != 0) - { - print_big_int(intvalue, output); - strcat(output, ","); - sprintf(&output[strlen(output)], "%03d", chunk); - } - else - sprintf(&output[strlen(output)], "%d", chunk); -} + const char *name; + void (*handler)(parameters_t &); + const char *description; + const char *valid_options[16]; +}; -/*------------------------------------------------- - big_int_string - return a string for a big - integer --------------------------------------------------*/ +// ======================> avi_info -static char *big_int_string(UINT64 intvalue) +struct avi_info { - static char buffer[256]; - buffer[0] = 0; - print_big_int(intvalue, buffer); - return buffer; -} + UINT32 fps_times_1million; + UINT32 width; + UINT32 height; + bool interlaced; + UINT32 channels; + UINT32 rate; + UINT32 max_samples_per_frame; + UINT32 bytes_per_frame; +}; -/*------------------------------------------------- - progress - generic progress callback --------------------------------------------------*/ +// ======================> metadata_index_info -static void ATTR_PRINTF(2,3) progress(int forceit, const char *fmt, ...) +struct metadata_index_info { - clock_t curtime = clock(); - va_list arg; + chd_metadata_tag tag; + UINT32 index; +}; - /* skip if it hasn't been long enough */ - if (!forceit && curtime - lastprogress < CLOCKS_PER_SEC / 2) - return; - lastprogress = curtime; - /* standard vfprintf stuff here */ - va_start(arg, fmt); - vfprintf(stderr, fmt, arg); - fflush(stderr); - va_end(arg); -} +// ======================> fatal_error +class fatal_error : public std::exception +{ +public: + fatal_error(int error) + : m_error(error) { } -/*------------------------------------------------- - usage - generic usage error display --------------------------------------------------*/ + int error() const { return m_error; } -static int usage(void) -{ - printf("usage: chdman -info input.chd\n"); - printf(" or: chdman -createraw inputhd.raw output.chd [inputoffs [hunksize]]\n"); - printf(" or: chdman -createhd inputhd.raw output.chd [ident.bin] [inputoffs [cylinders heads sectors [sectorsize [hunksize]]]]\n"); - printf(" or: chdman -createuncomphd inputhd.raw output.chd [ident.bin] [inputoffs [cylinders heads sectors [sectorsize [hunksize]]]]\n"); - printf(" or: chdman -createblankhd output.chd cylinders heads sectors [sectorsize [hunksize]]\n"); - printf(" or: chdman -createcd input.(toc/cue/nrg/gdi) output.chd\n"); - printf(" or: chdman -createav input.avi output.chd [firstframe [numframes]]\n"); - printf(" or: chdman -copydata input.chd output.chd\n"); - printf(" or: chdman -extract input.chd output.raw\n"); - printf(" or: chdman -extractcd input.chd output.(toc/cue) output.bin (if toc, no extension if cue)\n"); - printf(" or: chdman -extractav input.chd output.avi [firstframe [numframes]]\n"); - printf(" or: chdman -verify input.chd\n"); - printf(" or: chdman -verifyfix input.chd\n"); - printf(" or: chdman -update input.chd output.chd\n"); - printf(" or: chdman -chomp inout.chd output.chd maxhunk\n"); - printf(" or: chdman -merge parent.chd diff.chd output.chd\n"); - printf(" or: chdman -diff parent.chd compare.chd diff.chd\n"); - printf(" or: chdman -setchs inout.chd cylinders heads sectors\n"); - printf(" or: chdman -fixavdata inout.chd\n"); - printf(" or: chdman -addmetabin inout.chd tag [index] sourcefile\n"); - printf(" or: chdman -addmetatext inout.chd tag [index] sourcefile\n"); - return 1; -} +private: + int m_error; +}; -/*------------------------------------------------- - get_file_size - get the size of a file --------------------------------------------------*/ +// ======================> chd_rawfile_compressor -static UINT64 get_file_size(const char *filename) +class chd_rawfile_compressor : public chd_file_compressor { - osd_file *file; - UINT64 filesize = 0; - file_error filerr; - - filerr = osd_open(filename, OPEN_FLAG_READ, &file, &filesize); - if (filerr == FILERR_NONE) - osd_close(file); - return filesize; -} +public: + // construction/destruction + chd_rawfile_compressor(core_file *file, UINT64 offset = 0, UINT64 maxoffset = ~0) + : m_file(file), + m_offset(offset), + m_maxoffset(MIN(maxoffset, (file != NULL) ? core_fsize(file) : 0)) { } + + // read interface + virtual UINT32 read_data(void *dest, UINT64 offset, UINT32 length) + { + offset += m_offset; + if (offset >= m_maxoffset) + return 0; + if (offset + length > m_maxoffset) + length = m_maxoffset - offset; + core_fseek(m_file, offset, SEEK_SET); + return core_fread(m_file, dest, length); + } + +private: + // internal state + core_file * m_file; + UINT64 m_offset; + UINT64 m_maxoffset; +}; -/*------------------------------------------------- - guess_chs - given a file and an offset, - compute a best guess CHS value set --------------------------------------------------*/ +// ======================> chd_chdfile_compressor -static chd_error guess_chs(const char *filename, int offset, int sectorsize, UINT32 *cylinders, UINT32 *heads, UINT32 *sectors, UINT32 *bps) +class chd_chdfile_compressor : public chd_file_compressor { - UINT32 totalsecs, hds, secs; - UINT64 filesize; +public: + // construction/destruction + chd_chdfile_compressor(chd_file &file, UINT64 offset = 0, UINT64 maxoffset = ~0) + : m_file(file), + m_offset(offset), + m_maxoffset(MIN(maxoffset, file.logical_bytes())) { } + + // read interface + virtual UINT32 read_data(void *dest, UINT64 offset, UINT32 length) + { + offset += m_offset; + if (offset >= m_maxoffset) + return 0; + if (offset + length > m_maxoffset) + length = m_maxoffset - offset; + chd_error err = m_file.read_bytes(offset, dest, length); + if (err != CHDERR_NONE) + throw err; + return length; + } - /* if this is a direct physical drive read, handle it specially */ - if (osd_get_physical_drive_geometry(filename, cylinders, heads, sectors, bps)) - return CHDERR_NONE; +private: + // internal state + chd_file & m_file; + UINT64 m_offset; + UINT64 m_maxoffset; +}; - /* compute the filesize */ - filesize = get_file_size(filename); - if (filesize <= offset) - { - fprintf(stderr, "Invalid file '%s'\n", filename); - return CHDERR_INVALID_FILE; - } - filesize -= offset; - /* validate the size */ - if (filesize % sectorsize != 0) - { - fprintf(stderr, "Can't guess CHS values because data size is not divisible by the sector size\n"); - return CHDERR_INVALID_FILE; - } - totalsecs = filesize / sectorsize; +// ======================> chd_cd_compressor - /* now find a valid value */ - for (secs = 63; secs > 1; secs--) - if (totalsecs % secs == 0) +class chd_cd_compressor : public chd_file_compressor +{ +public: + // construction/destruction + chd_cd_compressor(cdrom_toc &toc, chdcd_track_input_info &info) + : m_file(NULL), + m_toc(toc), + m_info(info) { } + + ~chd_cd_compressor() + { + if (m_file != NULL) + core_fclose(m_file); + } + + // read interface + virtual UINT32 read_data(void *_dest, UINT64 offset, UINT32 length) + { + // verify assumptions made below + assert(offset % CD_FRAME_SIZE == 0); + assert(length % CD_FRAME_SIZE == 0); + + // initialize destination to 0 so that unused areas are filled + UINT8 *dest = reinterpret_cast<UINT8 *>(_dest); + memset(dest, 0, length); + + // find out which track we're starting in + UINT64 startoffs = 0; + UINT32 length_remaining = length; + for (int tracknum = 0; tracknum < m_toc.numtrks; tracknum++) { - size_t totalhds = totalsecs / secs; - for (hds = 16; hds > 1; hds--) - if (totalhds % hds == 0) + const cdrom_track_info &trackinfo = m_toc.tracks[tracknum]; + UINT64 endoffs = startoffs + (trackinfo.frames + trackinfo.extraframes) * CD_FRAME_SIZE; + if (offset >= startoffs && offset < endoffs) + { + // if we don't already have this file open, open it now + if (m_file == NULL || m_lastfile != m_info.track[tracknum].fname) { - *cylinders = totalhds / hds; - *heads = hds; - *sectors = secs; - *bps = sectorsize; - return CHDERR_NONE; + if (m_file != NULL) + core_fclose(m_file); + m_lastfile = m_info.track[tracknum].fname; + file_error filerr = core_fopen(m_lastfile, OPEN_FLAG_READ, &m_file); + if (filerr != FILERR_NONE) + report_error(1, "Error opening input file (%s)'", m_lastfile.cstr()); } + + // iterate over frames + UINT32 bytesperframe = trackinfo.datasize + trackinfo.subsize; + UINT64 src_track_start = m_info.track[tracknum].offset; + UINT64 src_track_end = src_track_start + bytesperframe * trackinfo.frames; + while (length_remaining != 0 && offset < endoffs) + { + // determine start of current frame + UINT64 src_frame_start = src_track_start + ((offset - startoffs) / CD_FRAME_SIZE) * bytesperframe; + if (src_frame_start < src_track_end) + { + // read it in + core_fseek(m_file, src_frame_start, SEEK_SET); + UINT32 count = core_fread(m_file, dest, bytesperframe); + if (count != bytesperframe) + report_error(1, "Error reading input file (%s)'", m_lastfile.cstr()); + + // swap if appropriate + if (m_info.track[tracknum].swap) + for (UINT32 swapindex = 0; swapindex < 2352; swapindex += 2) + { + UINT8 temp = dest[swapindex]; + dest[swapindex] = dest[swapindex + 1]; + dest[swapindex + 1] = temp; + } + } + + // advance + offset += CD_FRAME_SIZE; + dest += CD_FRAME_SIZE; + length_remaining -= CD_FRAME_SIZE; + if (length_remaining == 0) + break; + } + } + + // next track starts after the previous one + startoffs = endoffs; } - - /* ack, it didn't work! */ - fprintf(stderr, "Can't guess CHS values because no logical combination works!\n"); - return CHDERR_INVALID_FILE; -} - - -/*------------------------------------------------- - get_chs_from_ident - extract chs from an ident - information, validate it with the file size - - Note: limited to IDE for now --------------------------------------------------*/ - -static chd_error get_chs_from_ident(const char *filename, int offset, const UINT8 *ident, UINT32 identsize, UINT32 *cylinders, UINT32 *heads, UINT32 *sectors, UINT32 *bps) -{ - UINT64 filesize, expected_size; - - filesize = get_file_size(filename); - if (filesize <= offset) - { - fprintf(stderr, "Invalid file '%s'\n", filename); - return CHDERR_INVALID_FILE; - } - - if (filesize % 512 != 0) - { - fprintf(stderr, "Can't validate CHS values because data size is not divisible by the sector size\n"); - return CHDERR_INVALID_FILE; + return length - length_remaining; } - if (identsize < 14) - { - fprintf(stderr, "Error: the ident metadata is too short to include geometry information\n"); - return CHDERR_INVALID_FILE; - } +private: + // internal state + astring m_lastfile; + core_file * m_file; + cdrom_toc & m_toc; + chdcd_track_input_info & m_info; +}; - *bps = 512; - *cylinders = ((unsigned char)ident[ 2]) | (((unsigned char )ident[ 3]) << 8); - *heads = ((unsigned char)ident[ 6]) | (((unsigned char )ident[ 7]) << 8); - *sectors = ((unsigned char)ident[12]) | (((unsigned char )ident[13]) << 8); - expected_size = (UINT64)*cylinders * (UINT64)*heads * (UINT64)*sectors * 512; - if (expected_size != filesize - offset) - { - fprintf(stderr, "Error: Mismatch between the ident CHS data (%u/%u/%u, %u sectors) and the file size (%u sectors)\n", - *cylinders, *heads, *sectors, *cylinders * *heads * *sectors, - (UINT32)((filesize-offset)/512)); - return CHDERR_INVALID_FILE; - } - return CHDERR_NONE; -} +// ======================> chd_avi_compressor +class chd_avi_compressor : public chd_file_compressor +{ +public: + // construction/destruction + chd_avi_compressor(avi_file &file, avi_info &info, UINT32 first_frame, UINT32 num_frames) + : m_file(file), + m_info(info), + m_bitmap(info.width, info.height * (info.interlaced ? 2 : 1)), + m_start_frame(first_frame), + m_frame_count(num_frames), + m_ldframedata(num_frames * VBI_PACKED_BYTES), + m_rawdata(info.bytes_per_frame) { } + + // getters + const dynamic_buffer &ldframedata() const { return m_ldframedata; } + + // read interface + virtual UINT32 read_data(void *_dest, UINT64 offset, UINT32 length) + { + UINT8 *dest = reinterpret_cast<UINT8 *>(_dest); + int interlace_factor = m_info.interlaced ? 2 : 1; + UINT32 length_remaining = length; + + // iterate over frames + INT32 start_frame = offset / m_info.bytes_per_frame; + INT32 end_frame = (offset + length - 1) / m_info.bytes_per_frame; + for (INT32 framenum = start_frame; framenum <= end_frame; framenum++) + if (framenum < m_frame_count) + { + // determine effective frame number and first/last samples + INT32 effframe = m_start_frame + framenum; + UINT32 first_sample = (UINT64(m_info.rate) * UINT64(effframe) * UINT64(1000000) + m_info.fps_times_1million - 1) / UINT64(m_info.fps_times_1million); + UINT32 samples = (UINT64(m_info.rate) * UINT64(effframe + 1) * UINT64(1000000) + m_info.fps_times_1million - 1) / UINT64(m_info.fps_times_1million) - first_sample; + + // loop over channels and read the samples + int channels = MIN(m_info.channels, ARRAY_LENGTH(m_audio)); + INT16 *samplesptr[ARRAY_LENGTH(m_audio)]; + for (int chnum = 0; chnum < channels; chnum++) + { + // read the sound samples + m_audio[chnum].resize(samples); + samplesptr[chnum] = m_audio[chnum]; + avi_error avierr = avi_read_sound_samples(&m_file, chnum, first_sample, samples, m_audio[chnum]); + if (avierr != AVIERR_NONE) + report_error(1, "Error reading audio samples %d-%d from channel %d: %s", first_sample, samples, chnum, avi_error_string(avierr)); + } -/*------------------------------------------------- - do_createhd - create a new compressed hard - disk image from a raw file --------------------------------------------------*/ + // read the video data + avi_error avierr = avi_read_video_frame(&m_file, effframe / interlace_factor, m_bitmap); + if (avierr != AVIERR_NONE) + report_error(1, "Error reading AVI frame %d: %s", effframe / interlace_factor, avi_error_string(avierr)); + bitmap_yuy16 subbitmap(&m_bitmap.pix(effframe % interlace_factor), m_bitmap.width(), m_bitmap.height() / interlace_factor, m_bitmap.rowpixels() * interlace_factor); -static int do_createhd(int argc, char *argv[], int param) -{ - UINT32 guess_cylinders = 0, guess_heads = 0, guess_sectors = 0, guess_sectorsize = 0; - UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, offset; - const char *inputfile, *outputfile = NULL; - chd_error err = CHDERR_NONE; - UINT32 identdatasize = 0; - UINT8 *identdata = NULL; - chd_file *chd = NULL; - char metadata[256]; - - /* if a file is provided for argument 4 (ident filename), then shift the remaining arguments down */ - if (argc >= 5) - { - char *scan; + // update metadata for this frame + if (m_info.height == 524/2 || m_info.height == 624/2) + { + vbi_metadata vbi; + vbi_parse_all(&subbitmap.pix16(0), subbitmap.rowpixels(), subbitmap.width(), 8, &vbi); + vbi_metadata_pack(&m_ldframedata[framenum * VBI_PACKED_BYTES], framenum, &vbi); + } - /* if there are any non-digits in the 'offset', then treat it as a ident file */ - for (scan = argv[4]; *scan != 0; scan++) - if (!isdigit((UINT8)*scan)) - break; - if (*scan != 0) - { - /* attempt to load the file */ - file_error filerr = core_fload(argv[4], (void **)&identdata, &identdatasize); - if (filerr != FILERR_NONE) - { - fprintf(stderr, "Error opening ident file '%s'\n", argv[4]); - return 1; + // assemble the data into final form + avhuff_error averr = avhuff_encoder::assemble_data(m_rawdata, m_rawdata.count(), subbitmap, channels, samples, samplesptr); + if (averr != AVHERR_NONE) + report_error(1, "Error assembling data for frame %d", framenum); + UINT32 rawsize = avhuff_encoder::raw_data_size(m_rawdata); + if (rawsize < m_rawdata.count()) + memset(&m_rawdata[rawsize], 0, m_rawdata.count() - rawsize); + + // copy to the destination + UINT64 start_offset = UINT64(framenum) * UINT64(m_info.bytes_per_frame); + UINT64 end_offset = start_offset + m_info.bytes_per_frame; + UINT32 bytes_to_copy = MIN(length_remaining, end_offset - offset); + memcpy(dest, &m_rawdata[offset - start_offset], bytes_to_copy); + + // advance + offset += bytes_to_copy; + dest += bytes_to_copy; + length_remaining -= bytes_to_copy; } - /* shift the remaining arguments down */ - if (argc > 5) - memmove(&argv[4], &argv[5], (argc - 5) * sizeof(argv[0])); - argc--; - } + return length; } - /* require 4-5, or 8-10 args total */ - if (argc != 4 && argc != 5 && argc != 8 && argc != 9 && argc != 10) - return usage(); +private: + // internal state + avi_file & m_file; + avi_info & m_info; + bitmap_yuy16 m_bitmap; + UINT32 m_start_frame; + UINT32 m_frame_count; + dynamic_array<INT16> m_audio[8]; + dynamic_buffer m_ldframedata; + dynamic_buffer m_rawdata; +}; - /* extract the first few parameters */ - inputfile = argv[2]; - outputfile = argv[3]; - offset = (argc >= 5) ? atoi(argv[4]) : (get_file_size(inputfile) % IDE_SECTOR_SIZE); - /* if less than 8 parameters, we need to guess the CHS values */ - if (argc < 8) - { - if (identdata != NULL) - err = get_chs_from_ident(inputfile, offset, identdata, identdatasize, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); - else - err = guess_chs(inputfile, offset, IDE_SECTOR_SIZE, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); - if (err != CHDERR_NONE) - goto cleanup; - } - /* parse the remaining parameters */ - cylinders = (argc >= 6) ? atoi(argv[5]) : guess_cylinders; - heads = (argc >= 7) ? atoi(argv[6]) : guess_heads; - sectors = (argc >= 8) ? atoi(argv[7]) : guess_sectors; - sectorsize = (argc >= 9) ? atoi(argv[8]) : guess_sectorsize; - if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE; - hunksize = (argc >= 10) ? atoi(argv[9]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize); - totalsectors = cylinders * heads * sectors; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - printf("Input offset: %d\n", offset); - printf("Cylinders: %d\n", cylinders); - printf("Heads: %d\n", heads); - printf("Sectors: %d\n", sectors); - printf("Bytes/sector: %d\n", sectorsize); - printf("Sectors/hunk: %d\n", hunksize / sectorsize); - printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize)); - - /* create the new hard drive */ - err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } +//************************************************************************** +// GLOBAL VARIABLES +//************************************************************************** - /* open the new hard drive */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } +// timing +static clock_t lastprogress = 0; - /* write the metadata */ - sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); - err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - /* write the ident if present */ - if (identdata != NULL) - { - err = chd_set_metadata(chd, HARD_DISK_IDENT_METADATA_TAG, 0, identdata, identdatasize, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - } +// default compressors +static const chd_codec_type s_default_raw_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN }; +static const chd_codec_type s_default_hd_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN }; +static const chd_codec_type s_default_cd_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN, CHD_CODEC_FLAC_BE }; +static const chd_codec_type s_default_ld_compression[4] = { CHD_CODEC_AVHUFF }; - /* compress the hard drive */ - err = chdman_compress_file(chd, inputfile, offset); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); -cleanup: - /* close everything down */ - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - if (identdata != NULL) - osd_free(identdata); - return (err != CHDERR_NONE); -} +// descriptions for each option +static const option_description s_options[] = +{ + { OPTION_INPUT, "i", true, " <filename>: input file name" }, + { OPTION_INPUT_PARENT, "ip", true, " <filename>: parent file name for input CHD" }, + { OPTION_OUTPUT, "o", true, " <filename>: output file name" }, + { OPTION_OUTPUT_BIN, "ob", true, " <filename>: output file name for binary data" }, + { OPTION_OUTPUT_FORCE, "f", false, ": force overwriting an existing file" }, + { OPTION_OUTPUT_PARENT, "op", true, " <filename>: parent file name for output CHD" }, + { OPTION_INPUT_START_BYTE, "isb", true, " <offset>: starting byte offset within the input" }, + { OPTION_INPUT_START_HUNK, "ish", true, " <offset>: starting hunk offset within the input" }, + { OPTION_INPUT_START_FRAME, "isf", true, " <offset>: starting frame within the input" }, + { OPTION_INPUT_LENGTH_BYTES, "ib", true, " <length>: effective length of input in bytes" }, + { OPTION_INPUT_LENGTH_HUNKS, "ih", true, " <length>: effective length of input in hunks" }, + { OPTION_INPUT_LENGTH_FRAMES, "if", true, " <length>: effective length of input in frames" }, + { OPTION_HUNK_SIZE, "hs", true, " <bytes>: size of each hunk, in bytes" }, + { OPTION_UNIT_SIZE, "us", true, " <bytes>: size of each unit, in bytes" }, + { OPTION_COMPRESSION, "c", true, " <none|type1[,type2[,...]]>: which compression codecs to use (up to 4)" }, + { OPTION_IDENT, "id", true, " <filename>: name of ident file to provide CHS information" }, + { OPTION_CHS, "chs", true, " <cylinders,heads,sectors>: specifies CHS values directly" }, + { OPTION_SECTOR_SIZE, "ss", true, " <bytes>: size of each hard disk sector" }, + { OPTION_TAG, "t", true, " <tag>: 4-character tag for metadata" }, + { OPTION_INDEX, "ix", true, " <index>: indexed instance of this metadata tag" }, + { OPTION_VALUE_TEXT, "vt", true, " <text>: text for the metadata" }, + { OPTION_VALUE_FILE, "vf", true, " <file>: file containing data to add" }, +#if NUM_PROCESSORS_SUPPORTED + { OPTION_NUMPROCESSORS, "np", true, " <processors>: limit the number of processors to use during compression" }, +#endif + { OPTION_NO_CHECKSUM, "nocs", false, ": do not include this metadata information in the overall SHA-1" }, + { OPTION_FIX, "f", false, ": fix the SHA-1 if it is incorrect" }, + { OPTION_VERBOSE, "v", false, ": output additional information" }, +}; -/*------------------------------------------------- - do_createhd_uncomp - create a new uncompressed hard - disk image from a raw file --------------------------------------------------*/ -static int do_createhd_uncomp(int argc, char *argv[], int param) +// descriptions for each command +static const command_description s_commands[] = { - UINT32 guess_cylinders = 0, guess_heads = 0, guess_sectors = 0, guess_sectorsize = 0; - UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, offset; - const char *inputfile, *outputfile = NULL; - chd_error err = CHDERR_NONE; - UINT32 identdatasize = 0; - UINT8 *identdata = NULL; - chd_file *chd = NULL; - char metadata[256]; - chd_header header; - - /* if a file is provided for argument 4 (ident filename), then shift the remaining arguments down */ - if (argc >= 5) - { - char *scan; - - /* if there are any non-digits in the 'offset', then treat it as a ident file */ - for (scan = argv[4]; *scan != 0; scan++) - if (!isdigit((UINT8)*scan)) - break; - if (*scan != 0) + { COMMAND_INFO, do_info, ": displays information about a CHD", { - /* attempt to load the file */ - file_error filerr = core_fload(argv[4], (void **)&identdata, &identdatasize); - if (filerr != FILERR_NONE) - { - fprintf(stderr, "Error opening ident file '%s'\n", argv[4]); - return 1; - } - - /* shift the remaining arguments down */ - if (argc > 5) - memmove(&argv[4], &argv[5], (argc - 5) * sizeof(argv[0])); - argc--; + REQUIRED OPTION_INPUT, + OPTION_VERBOSE + } + }, + + { COMMAND_VERIFY, do_verify, ": verifies a CHD's integrity", + { + REQUIRED OPTION_INPUT, + OPTION_INPUT_PARENT + } + }, + + { COMMAND_CREATE_RAW, do_create_raw, ": create a raw CHD from the input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_PARENT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS, + REQUIRED OPTION_HUNK_SIZE, + REQUIRED OPTION_UNIT_SIZE, + OPTION_COMPRESSION, +#if NUM_PROCESSORS_SUPPORTED + OPTION_NUMPROCESSORS +#endif } - } - - /* require 4-5, or 8-10 args total */ - if (argc != 4 && argc != 5 && argc != 8 && argc != 9 && argc != 10) - return usage(); - - /* extract the first few parameters */ - inputfile = argv[2]; - outputfile = argv[3]; - offset = (argc >= 5) ? atoi(argv[4]) : (get_file_size(inputfile) % IDE_SECTOR_SIZE); - - /* if less than 8 parameters, we need to guess the CHS values */ - if (argc < 8) - { - if (identdata != NULL) - err = get_chs_from_ident(inputfile, offset, identdata, identdatasize, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); - else - err = guess_chs(inputfile, offset, IDE_SECTOR_SIZE, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); - if (err != CHDERR_NONE) - goto cleanup; - } - - /* parse the remaining parameters */ - cylinders = (argc >= 6) ? atoi(argv[5]) : guess_cylinders; - heads = (argc >= 7) ? atoi(argv[6]) : guess_heads; - sectors = (argc >= 8) ? atoi(argv[7]) : guess_sectors; - sectorsize = (argc >= 9) ? atoi(argv[8]) : guess_sectorsize; - if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE; - hunksize = (argc >= 10) ? atoi(argv[9]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize); - totalsectors = cylinders * heads * sectors; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - printf("Input offset: %d\n", offset); - printf("Cylinders: %d\n", cylinders); - printf("Heads: %d\n", heads); - printf("Sectors: %d\n", sectors); - printf("Bytes/sector: %d\n", sectorsize); - printf("Sectors/hunk: %d\n", hunksize / sectorsize); - printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize)); - - /* create the new hard drive */ - err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_NONE, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* open the new hard drive */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* write the metadata */ - sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); - err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* write the ident if present */ - if (identdata != NULL) - { - err = chd_set_metadata(chd, HARD_DISK_IDENT_METADATA_TAG, 0, identdata, identdatasize, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) + }, + + { COMMAND_CREATE_HD, do_create_hd, ": create a hard disk CHD from the input file", { - fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); - goto cleanup; + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_PARENT, + OPTION_OUTPUT_FORCE, + OPTION_INPUT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS, + OPTION_HUNK_SIZE, + OPTION_COMPRESSION, + OPTION_IDENT, + OPTION_CHS, + OPTION_SECTOR_SIZE, +#if NUM_PROCESSORS_SUPPORTED + OPTION_NUMPROCESSORS +#endif + } + }, + + { COMMAND_CREATE_CD, do_create_cd, ": create a CD CHD from the input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_PARENT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_HUNK_SIZE, + OPTION_COMPRESSION, +#if NUM_PROCESSORS_SUPPORTED + OPTION_NUMPROCESSORS +#endif + } + }, + + { COMMAND_CREATE_LD, do_create_ld, ": create a laserdisc CHD from the input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_PARENT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_INPUT_START_FRAME, + OPTION_INPUT_LENGTH_FRAMES, + OPTION_HUNK_SIZE, + OPTION_COMPRESSION, +#if NUM_PROCESSORS_SUPPORTED + OPTION_NUMPROCESSORS +#endif + } + }, + + { COMMAND_EXTRACT_RAW, do_extract_raw, ": extract raw file from a CHD input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_INPUT_PARENT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS + } + }, + + { COMMAND_EXTRACT_HD, do_extract_raw, ": extract raw hard disk file from a CHD input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_INPUT_PARENT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS + } + }, + + { COMMAND_EXTRACT_CD, do_extract_cd, ": extract CD file from a CHD input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_BIN, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT + OPTION_INPUT_PARENT, + } + }, + + { COMMAND_EXTRACT_LD, do_extract_ld, ": extract laserdisc AVI from a CHD input file", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_INPUT_PARENT, + OPTION_INPUT_START_FRAME, + OPTION_INPUT_LENGTH_FRAMES + } + }, + + { COMMAND_COPY, do_copy, ": copy data from one CHD to another of the same type", + { + REQUIRED OPTION_OUTPUT, + OPTION_OUTPUT_PARENT, + OPTION_OUTPUT_FORCE, + REQUIRED OPTION_INPUT, + OPTION_INPUT_PARENT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS, + OPTION_HUNK_SIZE, + OPTION_COMPRESSION, +#if NUM_PROCESSORS_SUPPORTED + OPTION_NUMPROCESSORS +#endif + } + }, + + { COMMAND_ADD_METADATA, do_add_metadata, ": add metadata to the CHD", + { + REQUIRED OPTION_INPUT, + REQUIRED OPTION_TAG, + OPTION_INDEX, + OPTION_VALUE_TEXT, + OPTION_VALUE_FILE, + OPTION_NO_CHECKSUM + } + }, + + { COMMAND_DEL_METADATA, do_del_metadata, ": remove metadata from the CHD", + { + REQUIRED OPTION_INPUT, + REQUIRED OPTION_TAG, + OPTION_INDEX } } +}; - /* compress the hard drive */ - err = chdman_compress_file(chd, inputfile, offset); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); - /* make it writeable */ - header = *chd_get_header(chd); - header.flags |= CHDFLAGS_IS_WRITEABLE; - err = chd_set_header_file(chd_core_file(chd), &header); - if (err != CHDERR_NONE) - fprintf(stderr, "Error writing new header: %s\n", chd_error_string(err)); -cleanup: - /* close everything down */ - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - if (identdata != NULL) - osd_free(identdata); - return (err != CHDERR_NONE); -} +//************************************************************************** +// IMPLEMENTATION +//************************************************************************** -/*------------------------------------------------- - do_createraw - create a new compressed raw - image from a raw file --------------------------------------------------*/ +//------------------------------------------------- +// report_error - report an error +//------------------------------------------------- -static int do_createraw(int argc, char *argv[], int param) +static void report_error(int error, const char *format, ...) { - const char *inputfile, *outputfile; - UINT32 hunksize, offset; - UINT64 logicalbytes; - chd_file *chd = NULL; - chd_error err; - - /* require 4, 5, or 6 args total */ - if (argc != 4 && argc != 5 && argc != 6) - return usage(); - - /* extract the first few parameters */ - inputfile = argv[2]; - outputfile = argv[3]; - offset = (argc >= 5) ? atoi(argv[4]) : 0; - hunksize = (argc >= 6) ? atoi(argv[5]) : 4096; - logicalbytes = get_file_size(inputfile) - offset; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - printf("Input offset: %d\n", offset); - printf("Bytes/hunk: %d\n", hunksize); - printf("Logical size: %s\n", big_int_string(logicalbytes)); - - /* create the new CHD */ - err = chd_create(outputfile, logicalbytes, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* open the new CHD */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* compress the CHD */ - err = chdman_compress_file(chd, inputfile, offset); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); - -cleanup: - /* close everything down */ - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); + // output to stderr + va_list arg; + va_start(arg, format); + vfprintf(stderr, format, arg); + fflush(stderr); + va_end(arg); + fprintf(stderr, "\n"); + + // reset time for progress and return the error + lastprogress = 0; + throw fatal_error(error); } -/*------------------------------------------------- - do_createcd - create a new compressed CD - image from a raw file --------------------------------------------------*/ +//------------------------------------------------- +// progress - generic progress callback +//------------------------------------------------- -static int do_createcd(int argc, char *argv[], int param) +static void ATTR_PRINTF(2,3) progress(bool forceit, const char *format, ...) { - static chdcd_track_input_info track_info; - static cdrom_toc toc; - UINT32 hunksize = CD_FRAME_SIZE * CD_FRAMES_PER_HUNK; - UINT32 sectorsize = CD_FRAME_SIZE; - const char *inputfile, *outputfile; - core_file *srcfile = NULL; - UINT32 origtotalsectors; - chd_file *chd = NULL; - UINT8 *cache = NULL; - UINT32 totalsectors; - double ratio = 1.0; - UINT32 totalhunks; - file_error filerr; - chd_error err; - int i; - - /* require 4 args total */ - if (argc != 4) - return usage(); - - /* extract the data */ - inputfile = argv[2]; - outputfile = argv[3]; - - /* clear the TOC */ - memset(&toc, 0, sizeof(toc)); - - /* allocate a cache */ - cache = (UINT8 *)malloc(hunksize); - if (cache == NULL) - { - fprintf(stderr, "Out of memory allocating temporary buffer\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - - /* setup the CDROM module and get the disc info */ - err = chdcd_parse_toc(inputfile, &toc, &track_info); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error reading input file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* pad each track to a hunk boundry. cdrom.c will deal with this on the read side */ - for (i = 0; i < toc.numtrks; i++) - { - int hunks = (toc.tracks[i].frames + CD_FRAMES_PER_HUNK - 1) / CD_FRAMES_PER_HUNK; - toc.tracks[i].extraframes = hunks * CD_FRAMES_PER_HUNK - toc.tracks[i].frames; - } - + // skip if it hasn't been long enough + clock_t curtime = clock(); + if (!forceit && lastprogress != 0 && curtime - lastprogress < CLOCKS_PER_SEC / 2) + return; + lastprogress = curtime; - /* count up the total number of frames */ - origtotalsectors = totalsectors = 0; - for (i = 0; i < toc.numtrks; i++) - { - origtotalsectors += toc.tracks[i].frames; - totalsectors += toc.tracks[i].frames + toc.tracks[i].extraframes; - } - printf("\nCD-ROM %s has %d tracks and %d total frames\n", inputfile, toc.numtrks, origtotalsectors); + // standard vfprintf stuff here + va_list arg; + va_start(arg, format); + vfprintf(stderr, format, arg); + fflush(stderr); + va_end(arg); +} - /* create the new CHD file */ - if (param) err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS_WITH_FLAC, NULL); - else err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - /* open the new CHD file */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } +//------------------------------------------------- +// print_help - print help for all the commands +//------------------------------------------------- - /* write the metadata */ - err = cdrom_write_metadata(chd, &toc); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding CD-ROM metadata: %s\n", chd_error_string(err)); - goto cleanup; - } +static int print_help(const char *argv0, const char *error = NULL) +{ + // print the error message first + if (error != NULL) + fprintf(stderr, "Error: %s\n\n", error); - /* begin state for writing */ - err = chd_compress_begin(chd); - if (err != CHDERR_NONE) + // print a summary of each command + printf("Usage:\n"); + for (int cmdnum = 0; cmdnum < ARRAY_LENGTH(s_commands); cmdnum++) { - fprintf(stderr, "Error compressing: %s\n", chd_error_string(err)); - goto cleanup; + const command_description &desc = s_commands[cmdnum]; + printf(" %s %s%s\n", argv0, desc.name, desc.description); } - - /* loop over tracks */ - totalhunks = 0; - for (i = 0; i < toc.numtrks; i++) - { - int frames = 0; - int bytespersector = toc.tracks[i].datasize + toc.tracks[i].subsize; - int trackhunks = (toc.tracks[i].frames + toc.tracks[i].extraframes) / CD_FRAMES_PER_HUNK; - UINT64 sourcefileoffset = track_info.offset[i]; - int curhunk; - - /* open the input file for this track */ - filerr = core_fopen(track_info.fname[i], OPEN_FLAG_READ, &srcfile); - if (filerr != FILERR_NONE) - { - fprintf(stderr, "Unable to open file: %s\n", track_info.fname[i]); - err = CHDERR_FILE_NOT_FOUND; - goto cleanup; - } - - printf("Track %02d/%02d (%s:%d,%d frames,%d hunks,swap %d,pregap %d,postgap %d)\n", i+1, toc.numtrks, track_info.fname[i], track_info.offset[i], toc.tracks[i].frames, trackhunks, track_info.swap[i], toc.tracks[i].pregap, toc.tracks[i].postgap); - - /* loop over hunks */ - for (curhunk = 0; curhunk < trackhunks; curhunk++, totalhunks++) - { - int secnum; - - progress(FALSE, "Compressing hunk %d/%d... (ratio=%d%%) \r", totalhunks, chd_get_header(chd)->totalhunks, (int)(ratio * 100)); - - /* loop over sectors in this hunk, reading the source data into a fixed start location */ - /* relative to the start; we zero out the buffer ahead of time to ensure that unpopulated */ - /* areas are cleared */ - memset(cache, 0, hunksize); - for (secnum = 0; secnum < CD_FRAMES_PER_HUNK; secnum++) - { - if (frames < toc.tracks[i].frames) - { - core_fseek(srcfile, sourcefileoffset, SEEK_SET); - core_fread(srcfile, &cache[secnum * CD_FRAME_SIZE], bytespersector); - - if (track_info.swap[i]) - { - int swapindex; - - for (swapindex = 0; swapindex < 2352; swapindex += 2 ) - { - int swapoffset = ( secnum * CD_FRAME_SIZE ) + swapindex; - - int swaptemp = cache[ swapoffset ]; - cache[ swapoffset ] = cache[ swapoffset + 1 ]; - cache[ swapoffset + 1 ] = swaptemp; - } - } - } - - sourcefileoffset += bytespersector; - frames++; - } - - int is_half_hunk = 0; - if ((curhunk == trackhunks-1) && (toc.tracks[i].extraframes>3)) - is_half_hunk = 1; + printf("\nFor help with any command, run:\n"); + printf(" %s %s <command>\n", argv0, COMMAND_HELP); + return 1; +} - /* compress the current hunk */ +//------------------------------------------------- +// print_help - print help for all a specific +// command +//------------------------------------------------- - err = chd_compress_hunk(chd, cache, &ratio, is_half_hunk); - if (err != CHDERR_NONE) +static int print_help(const char *argv0, const command_description &desc, const char *error = NULL) +{ + // print the error message first + if (error != NULL) + fprintf(stderr, "Error: %s\n\n", error); + + // print usage for this command + printf("Usage:\n"); + printf(" %s %s [options], where valid options are:\n", argv0, desc.name); + for (int valid = 0; valid < ARRAY_LENGTH(desc.valid_options); valid++) + { + // determine whether we are required + const char *option = desc.valid_options[valid]; + if (option == NULL) + break; + bool required = (option[0] == REQUIRED[0]); + if (required) + option++; + + // find the option + for (int optnum = 0; optnum < ARRAY_LENGTH(s_options); optnum++) + if (strcmp(option, s_options[optnum].name) == 0) { - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); - goto cleanup; + const option_description &odesc = s_options[optnum]; + printf(" --%s", odesc.name); + if (odesc.shortname != NULL) + printf(", -%s", odesc.shortname); + printf("%s%s\n", odesc.description, required ? " (required)" : ""); } - } - - /* close the file */ - core_fclose(srcfile); - srcfile = NULL; } - - /* cleanup */ - err = chd_compress_finish(chd, TRUE); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression finalization: %s\n", chd_error_string(err)); - else - progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); - -cleanup: - if (cache != NULL) - free(cache); - if (srcfile != NULL) - core_fclose(srcfile); - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); + return 1; } -/*------------------------------------------------- - read_avi_frame - read an AVI frame --------------------------------------------------*/ +//------------------------------------------------- +// big_int_string - create a 64-bit string +//------------------------------------------------- -static avi_error read_avi_frame(avi_file *avi, UINT32 framenum, UINT32 first_sample, bitmap_yuy16 &fullbitmap, int interlaced, av_codec_compress_config *avconfig) +const char *big_int_string(astring &string, UINT64 intvalue) { - const avi_movie_info *info = avi_get_movie_info(avi); - int interlace_factor = interlaced ? 2 : 1; - avi_error avierr = AVIERR_NONE; - int chnum; - - /* loop over channels and read the samples */ - for (chnum = 0; chnum < info->audio_channels; chnum++) - { - /* read the sound samples */ - avierr = avi_read_sound_samples(avi, chnum, first_sample, avconfig->samples, avconfig->audio[chnum]); - if (avierr != AVIERR_NONE) - goto cleanup; - } + // 0 is a special case + if (intvalue == 0) + return string.cpy("0"); + + // loop until all chunks are done + string.reset(); + bool first = true; + while (intvalue != 0) + { + int chunk = intvalue % 1000; + intvalue /= 1000; + + astring insert; + insert.format((intvalue != 0) ? "%03d" : "%d", chunk); + + if (!first) + string.ins(0, ","); + first = false; + string.ins(0, insert); + } + return string; +} - /* read the video data when we hit a new frame */ - if (framenum % interlace_factor == 0) - { - avierr = avi_read_video_frame(avi, framenum / interlace_factor, fullbitmap); - if (avierr != AVIERR_NONE) - goto cleanup; - } - /* build the fake bitmap */ - if (!interlaced) - avconfig->video.wrap(fullbitmap, fullbitmap.cliprect()); - else - avconfig->video.wrap(&fullbitmap.pix16(framenum % interlace_factor), fullbitmap.width(), fullbitmap.height() / 2, fullbitmap.rowpixels() * 2); +//------------------------------------------------- +// msf_string_from_frames - output the given +// number of frames in M:S:F format +//------------------------------------------------- -cleanup: - return avierr; +const char *msf_string_from_frames(astring &string, UINT32 frames) +{ + return string.format("%02d:%02d:%02d", frames / (75 * 60), (frames / 75) % 60, frames % 75); } -/*------------------------------------------------- - fake_avi_frame - fake an AVI frame --------------------------------------------------*/ +//------------------------------------------------- +// parse_number - parse a number string with an +// optional k/m/g suffix +//------------------------------------------------- -static avi_error fake_avi_frame(avi_file *avi, UINT32 framenum, UINT32 first_sample, bitmap_yuy16 &fullbitmap, int interlaced, av_codec_compress_config *avconfig) +UINT64 parse_number(const char *string) { - static int framecounter = 0; - int leftsamp = (framenum % 200 < 10) ? 10000 : 0; - int rightsamp = (framenum % 200 >= 100 && framenum % 200 < 110) ? 10000 : 0; - int interlace_factor = interlaced ? 2 : 1; - int chnum, sampnum, x, y; - int whiteflag, line1718; - - /* reset framecounter to 1 on frame 0 */ - if (framenum == 0) - framecounter = 1; - - /* loop over channels and read the samples */ - for (chnum = 0; chnum < AVI_FAKE_CHANNELS; chnum++) - { - int modcheck = AVI_FAKE_SAMPLERATE / ((chnum == 0) ? 110 : 220); - int samp = (chnum == 0) ? leftsamp : rightsamp; - INT16 *dest = avconfig->audio[chnum]; - - /* store them to the audio buffer */ - for (sampnum = 0; sampnum < avconfig->samples; sampnum++) - *dest++ = ((first_sample + sampnum) % modcheck < modcheck / 2) ? samp : -samp; - } + // 0-length string is 0 + int length = strlen(string); + if (length == 0) + return 0; + + // scan forward over digits + UINT64 result = 0; + while (isdigit(*string)) + { + result = (result * 10) + (*string - '0'); + string++; + } + + // handle multipliers + if (*string == 'k' || *string == 'K') + result *= 1024; + if (*string == 'm' || *string == 'M') + result *= 1024 * 1024; + if (*string == 'g' || *string == 'G') + result *= 1024 * 1024 * 1024; + + return result; +} - /* determine what metadata we should generate */ - whiteflag = line1718 = 0; - if (framenum < 2) - { - whiteflag = (framenum == 0); - line1718 = 0x88ffff; - } - else if (framenum >= AVI_FAKE_FRAMES * 2 - 2) - { - whiteflag = (framenum == AVI_FAKE_FRAMES * 2 - 2); - line1718 = 0x80eeee; - } - else - { - int effnum = framenum - 2; - if (avi == AVI_FAKE_FILE_22 && effnum % 2 == 0) - whiteflag = 1; - else if (avi == AVI_FAKE_FILE_32 && (effnum % 5 == 0 || effnum % 5 == 2)) - whiteflag = 1; - if (whiteflag) - { - line1718 = 0xf80000 | (((framecounter / 10000) % 10) << 16) | (((framecounter / 1000) % 10) << 12) | - (((framecounter / 100) % 10) << 8) | (((framecounter / 10) % 10) << 4) | (framecounter % 10); - framecounter++; - } - } - /* build the fake bitmap */ - if (!interlaced) - avconfig->video.wrap(fullbitmap, fullbitmap.cliprect()); - else - avconfig->video.wrap(&fullbitmap.pix16(framenum % interlace_factor), fullbitmap.width(), fullbitmap.height() / 2, fullbitmap.rowpixels() * 2); +//------------------------------------------------- +// guess_chs - given a file and an offset, +// compute a best guess CHS value set +//------------------------------------------------- - /* loop over the data and copy it to the cache */ - for (y = 0; y < avconfig->video.height(); y++) - { - UINT16 *dest = &avconfig->video.pix16(y); +static void guess_chs(astring *filename, UINT64 filesize, int sectorsize, UINT32 &cylinders, UINT32 &heads, UINT32 §ors, UINT32 &bps) +{ + // if this is a direct physical drive read, handle it specially + if (filename != NULL && osd_get_physical_drive_geometry(*filename, &cylinders, &heads, §ors, &bps)) + return; + + // if we have no length to work with, we can't guess + if (filesize == 0) + report_error(1, "Can't guess CHS values because there is no input file"); - /* white flag? */ - if (y == 11 && whiteflag) - { - for (x = 0; x < AVI_FAKE_WIDTH; x++) - *dest++ = (x > 10 && x < avconfig->video.width() - 10) ? 0xff80 : 0x0080; - } + // validate the size + if (filesize % sectorsize != 0) + report_error(1, "Can't guess CHS values because data size is not divisible by %d", sectorsize); + ; - /* line 17/18 */ - else if ((y == 17 || y == 18) && line1718 != 0) - { - for (x = 0; x < avconfig->video.width(); x++) + // now find a valid value + for (UINT32 totalsectors = filesize / sectorsize; ; totalsectors++) + for (UINT32 cursectors = 63; cursectors > 1; cursectors--) + if (totalsectors % cursectors == 0) { - UINT16 pixel = 0x0080; - if (x >= 20) - { - int bitnum = (x - 20) / 28; - if (bitnum < 24) + UINT32 totalheads = totalsectors / cursectors; + for (UINT32 curheads = 16; curheads > 1; curheads--) + if (totalheads % curheads == 0) { - int bithalf = (x - (bitnum * 28 + 20)) / 14; - if ((line1718 << bitnum) & 0x800000) bithalf ^= 1; - pixel = bithalf ? 0x0080 : 0xff80; + cylinders = totalheads / curheads; + heads = curheads; + sectors = cursectors; + return; } - } - *dest++ = pixel; } - } - /* anything else in VBI-land */ - else if (y < 22) - { - for (x = 0; x < avconfig->video.width(); x++) - *dest++ = 0x0080; - } - - /* everything else */ - else - { - for (x = 0; x < avconfig->video.width(); x++) - *dest++ = framenum; - } - } - - return AVIERR_NONE; + // ack, it didn't work! + report_error(1, "Can't guess CHS values because no logical combination works!"); } -/*------------------------------------------------- - do_createav - create a new A/V file from an - input AVI file and metadata --------------------------------------------------*/ +//------------------------------------------------- +// parse_input_chd_parameters - parse the +// standard set of input CHD parameters +//------------------------------------------------- -static int do_createav(int argc, char *argv[], int param) +static void parse_input_chd_parameters(const parameters_t ¶ms, chd_file &input_chd, chd_file &input_parent_chd, bool writeable = false) { - UINT32 fps_times_1million, width, height, interlaced, channels, rate, totalframes; - UINT32 max_samples_per_frame, bytes_per_frame, firstframe, numframes; - av_codec_compress_config avconfig; - const char *inputfile, *outputfile; - const avi_movie_info *info; - UINT8 *ldframedata = NULL; - const chd_header *header; - chd_file *chd = NULL; - avi_file *avi = NULL; - double ratio = 1.0; - char metadata[256]; - avi_error avierr; - UINT32 framenum; - chd_error err; - int chnum; - - /* require 4-6 args total */ - if (argc < 4 || argc > 6) - return usage(); - - /* extract the first few parameters */ - inputfile = argv[2]; - outputfile = argv[3]; - firstframe = (argc > 4) ? atoi(argv[4]) : 0; - numframes = (argc > 5) ? atoi(argv[5]) : 1000000; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - - /* special AVI files */ - if (strcmp(inputfile, "2:2") == 0 || strcmp(inputfile, "3:2") == 0) - { - /* create a fake handle */ - avi = (strcmp(inputfile, "2:2") == 0) ? AVI_FAKE_FILE_22 : AVI_FAKE_FILE_32; - - /* fake the movie information */ - fps_times_1million = AVI_FAKE_FRAMERATE; - width = AVI_FAKE_WIDTH; - height = AVI_FAKE_HEIGHT; - interlaced = TRUE; - channels = 2; - rate = AVI_FAKE_SAMPLERATE; - totalframes = AVI_FAKE_FRAMES; - } - else - { - /* open the source file */ - avierr = avi_open(inputfile, &avi); - if (avierr != AVIERR_NONE) - { - fprintf(stderr, "Error opening AVI file: %s\n", avi_error_string(avierr)); - err = CHDERR_INVALID_FILE; - goto cleanup; - } - - /* get the movie information */ - info = avi_get_movie_info(avi); - fps_times_1million = (UINT64)info->video_timescale * 1000000 / info->video_sampletime; - width = info->video_width; - height = info->video_height; - interlaced = ((fps_times_1million / 1000000) <= 30) && (height % 2 == 0) && (height > 288); - channels = info->audio_channels; - rate = info->audio_samplerate; - totalframes = info->video_numsamples; - } - numframes = MIN(totalframes - firstframe, numframes); - - /* print some of it */ - printf("Use frames: %d-%d\n", firstframe, firstframe + numframes - 1); - printf("Frame rate: %d.%06d\n", fps_times_1million / 1000000, fps_times_1million % 1000000); - printf("Frame size: %d x %d %s\n", width, height, interlaced ? "interlaced" : "non-interlaced"); - printf("Audio: %d channels at %d Hz\n", channels, rate); - printf("Total frames: %d (%02d:%02d:%02d)\n", totalframes, - (UINT32)((UINT64)totalframes * 1000000 / fps_times_1million / 60 / 60), - (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million / 60) % 60), - (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million) % 60)); - - /* adjust for interlacing */ - if (interlaced) - { - fps_times_1million *= 2; - totalframes *= 2; - height /= 2; - firstframe *= 2; - numframes *= 2; - } - - /* allocate space for the frame data */ - if (height == 524/2 || height == 624/2) + // process input parent file + astring *input_chd_parent_str = params.find(OPTION_INPUT_PARENT); + if (input_chd_parent_str != NULL) { - ldframedata = (UINT8 *)malloc(numframes * VBI_PACKED_BYTES); - if (ldframedata == NULL) - { - fprintf(stderr, "Out of memory allocating frame metadata\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - memset(ldframedata, 0, numframes * VBI_PACKED_BYTES); + chd_error err = input_parent_chd.open(*input_chd_parent_str); + if (err != CHDERR_NONE) + report_error(1, "Error opening parent CHD file (%s): %s", input_chd_parent_str->cstr(), chd_file::error_string(err)); } - /* determine the number of bytes per frame */ - 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; - - /* allocate a video buffer */ + // process input file + astring *input_chd_str = params.find(OPTION_INPUT); + if (input_chd_str != NULL) { - bitmap_yuy16 fullbitmap(width, height * (interlaced ? 2 : 1)); - avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); - - /* allocate audio buffers */ - avconfig.channels = channels; - for (chnum = 0; chnum < channels; chnum++) - { - avconfig.audio[chnum] = (INT16 *)malloc(max_samples_per_frame * 2); - if (avconfig.audio[chnum] == NULL) - { - fprintf(stderr, "Out of memory allocating temporary audio buffer\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - } - - /* create the new CHD */ - err = chd_create(outputfile, (UINT64)numframes * (UINT64)bytes_per_frame, bytes_per_frame, CHDCOMPRESSION_AV, NULL); + chd_error err = input_chd.open(*input_chd_str, writeable, input_parent_chd.opened() ? &input_parent_chd : NULL); if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* open the new CHD */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - header = chd_get_header(chd); - - /* write the metadata */ - sprintf(metadata, AV_METADATA_FORMAT, fps_times_1million / 1000000, fps_times_1million % 1000000, width, height, interlaced, channels, rate); - err = chd_set_metadata(chd, AV_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding AV metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* begin compressing */ - err = chd_compress_begin(chd); - if (err != CHDERR_NONE) - goto cleanup; - - /* loop over source hunks until we run out */ - for (framenum = 0; framenum < numframes; framenum++) - { - int effframe = firstframe + framenum; - UINT32 first_sample; - - /* progress */ - progress(framenum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", framenum, header->totalhunks, (int)(100.0 * ratio)); - - /* compute the number of samples in this frame */ - first_sample = ((UINT64)rate * (UINT64)effframe * (UINT64)1000000 + fps_times_1million - 1) / (UINT64)fps_times_1million; - avconfig.samples = ((UINT64)rate * (UINT64)(effframe + 1) * (UINT64)1000000 + fps_times_1million - 1) / (UINT64)fps_times_1million - first_sample; - - /* read the frame into its proper format in the cache */ - if (IS_FAKE_AVI_FILE(avi)) - avierr = fake_avi_frame(avi, effframe, first_sample, fullbitmap, interlaced, &avconfig); - else - avierr = read_avi_frame(avi, effframe, first_sample, fullbitmap, interlaced, &avconfig); - if (avierr != AVIERR_NONE) - { - fprintf(stderr, "Error reading frame %d from AVI file: %s\n", effframe, avi_error_string(avierr)); - err = CHDERR_COMPRESSION_ERROR; - } - - /* update metadata for this frame */ - if (ldframedata != NULL) - { - /* parse the data and pack it */ - vbi_metadata vbi; - vbi_parse_all(&avconfig.video.pix16(0), avconfig.video.rowpixels(), avconfig.video.width(), 8, &vbi); - vbi_metadata_pack(&ldframedata[framenum * VBI_PACKED_BYTES], framenum, &vbi); - } - - /* configure the compressor for this frame */ - chd_codec_config(chd, AV_CODEC_COMPRESS_CONFIG, &avconfig); - - /* append the data */ - err = chd_compress_hunk(chd, NULL, &ratio); - if (err != CHDERR_NONE) - goto cleanup; - } - - /* write the final metadata */ - if (ldframedata != NULL) - { - err = chd_set_metadata(chd, AV_LD_METADATA_TAG, 0, ldframedata, numframes * VBI_PACKED_BYTES, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding AVLD metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - } - - /* finish compression */ - err = chd_compress_finish(chd, TRUE); - if (err != CHDERR_NONE) - goto cleanup; - else - progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); + report_error(1, "Error opening CHD file (%s): %s", input_chd_str->cstr(), chd_file::error_string(err)); } - -cleanup: - /* close everything down */ - if (avi != NULL && !IS_FAKE_AVI_FILE(avi)) - avi_close(avi); - if (chd != NULL) - chd_close(chd); - for (chnum = 0; chnum < ARRAY_LENGTH(avconfig.audio); chnum++) - if (avconfig.audio[chnum] != NULL) - free(avconfig.audio[chnum]); - if (ldframedata != NULL) - free(ldframedata); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_createblankhd - create a new non-compressed - hard disk image, with all hunks filled with 0s --------------------------------------------------*/ +//------------------------------------------------- +// parse_input_start_end - parse input start/end +// parameters in a standard way +//------------------------------------------------- -static int do_createblankhd(int argc, char *argv[], int param) +static void parse_input_start_end(const parameters_t ¶ms, UINT64 logical_size, UINT32 hunkbytes, UINT32 framebytes, UINT64 &input_start, UINT64 &input_end) { - UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, hunknum; - const char *outputfile; - chd_file *chd = NULL; - UINT8 *cache = NULL; - char metadata[256]; - chd_error err; - - /* require 6, 7, or 8 args total */ - if (argc != 6 && argc != 7 && argc != 8) - return usage(); - - /* extract the data */ - outputfile = argv[2]; - cylinders = atoi(argv[3]); - heads = atoi(argv[4]); - sectors = atoi(argv[5]); - sectorsize = (argc >= 7) ? atoi(argv[6]) : IDE_SECTOR_SIZE; - if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE; - hunksize = (argc >= 8) ? atoi(argv[7]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize); - totalsectors = cylinders * heads * sectors; - - /* print some info */ - printf("Output file: %s\n", outputfile); - printf("Cylinders: %d\n", cylinders); - printf("Heads: %d\n", heads); - printf("Sectors: %d\n", sectors); - printf("Bytes/sector: %d\n", sectorsize); - printf("Sectors/hunk: %d\n", hunksize / sectorsize); - printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize)); - - /* create the new hard drive */ - err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_NONE, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } + // process start/end if we were provided an input CHD + input_start = 0; + input_end = logical_size; + + // process input start + astring *input_start_byte_str = params.find(OPTION_INPUT_START_BYTE); + astring *input_start_hunk_str = params.find(OPTION_INPUT_START_HUNK); + astring *input_start_frame_str = params.find(OPTION_INPUT_START_FRAME); + if (input_start_byte_str != NULL) + input_start = parse_number(*input_start_byte_str); + if (input_start_hunk_str != NULL) + input_start = parse_number(*input_start_hunk_str) * hunkbytes; + if (input_start_frame_str != NULL) + input_start = parse_number(*input_start_frame_str) * framebytes; + if (input_start >= input_end) + report_error(1, "Input start offset greater than input file size"); + + // process input length + astring *input_length_bytes_str = params.find(OPTION_INPUT_LENGTH_BYTES); + astring *input_length_hunks_str = params.find(OPTION_INPUT_LENGTH_HUNKS); + astring *input_length_frames_str = params.find(OPTION_INPUT_LENGTH_FRAMES); + UINT64 input_length = input_end; + if (input_length_bytes_str != NULL) + input_length = parse_number(*input_length_bytes_str); + if (input_length_hunks_str != NULL) + input_length = parse_number(*input_length_hunks_str) * hunkbytes; + if (input_length_frames_str != NULL) + input_length = parse_number(*input_length_frames_str) * framebytes; + if (input_start + input_length < input_end) + input_end = input_start + input_length; +} - /* open the new hard drive */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - /* write the metadata */ - sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); - err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); - goto cleanup; - } +//------------------------------------------------- +// parse_output_chd_parameters - parse the +// standard set of output CHD parameters +//------------------------------------------------- - /* alloc and zero buffer*/ - cache = (UINT8 *)malloc(hunksize); - if (cache == NULL) +static astring *parse_output_chd_parameters(const parameters_t ¶ms, chd_file &output_parent_chd) +{ + // process output parent file + astring *output_chd_parent_str = params.find(OPTION_OUTPUT_PARENT); + if (output_chd_parent_str != NULL) { - fprintf(stderr, "Error allocating memory buffer\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; + chd_error err = output_parent_chd.open(*output_chd_parent_str); + if (err != CHDERR_NONE) + report_error(1, "Error opening parent CHD file (%s): %s", output_chd_parent_str->cstr(), chd_file::error_string(err)); } - memset(cache, 0, hunksize); - /* Zero every hunk */ - for (hunknum = 0; hunknum < chd_get_header(chd)->totalhunks; hunknum++) + // process output file + astring *output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str != NULL && params.find(OPTION_OUTPUT_FORCE) == NULL) { - /* progress */ - progress(hunknum == 0, "Zeroing hunk %d/%d... \r", hunknum, chd_get_header(chd)->totalhunks); - - /* write out the data */ - err = chd_write(chd, hunknum, cache); - if (err != CHDERR_NONE) + core_file *file; + file_error filerr = core_fopen(*output_chd_str, OPEN_FLAG_READ, &file); + if (filerr == FILERR_NONE) { - fprintf(stderr, "Error writing CHD file: %s\n", chd_error_string(err)); - goto cleanup; + core_fclose(file); + report_error(1, "Error: file already exists (%s)\nUse --force (or -f) to force overwriting", output_chd_str->cstr()); } } - progress(TRUE, "Creation complete! \n"); - -cleanup: - /* close everything down */ - if (cache != NULL) - free(cache); - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); + return output_chd_str; } -/*------------------------------------------------- - do_copydata - copy all hunks of data from one - CHD file to another. The hunk sizes do not - need to match. If the source is shorter than - the destination, the source data will be - padded with 0s. --------------------------------------------------*/ +//------------------------------------------------- +// parse_hunk_size - parse the hunk_size +// parameter in a standard way +//------------------------------------------------- -static int do_copydata(int argc, char *argv[], int param) +static void parse_hunk_size(const parameters_t ¶ms, UINT32 required_granularity, UINT32 &hunk_size) { - const char *inputfile, *outputfile; - chd_file *outputchd = NULL; - chd_file *inputchd = NULL; - chd_error err; - - /* require 4 args total */ - if (argc != 4) - return usage(); - - /* extract the data */ - inputfile = argv[2]; - outputfile = argv[3]; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - - /* open the src hard drive */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening src CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* open the dest hard drive */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &outputchd); - if (err != CHDERR_NONE) + astring *hunk_size_str = params.find(OPTION_HUNK_SIZE); + if (hunk_size_str != NULL) { - fprintf(stderr, "Error opening dest CHD file: %s\n", chd_error_string(err)); - goto cleanup; + hunk_size = parse_number(*hunk_size_str); + if (hunk_size < 16 || hunk_size > 1024 * 1024) + report_error(1, "Invalid hunk size"); + if (hunk_size % required_granularity != 0) + report_error(1, "Hunk size is not an even multiple of %d", required_granularity); } - - /* compress the source into the dest */ - err = chdman_compress_chd(outputchd, inputchd, 0); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); - -cleanup: - /* close everything down */ - if (outputchd != NULL) - chd_close(outputchd); - if (inputchd != NULL) - chd_close(inputchd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_extract - extract a raw file from a - CHD image --------------------------------------------------*/ +//------------------------------------------------- +// parse_compression - parse a standard +// compression parameter string +//------------------------------------------------- -static int do_extract(int argc, char *argv[], int param) +static void parse_compression(const parameters_t ¶ms, chd_codec_type compression[4]) { - const char *inputfile, *outputfile; - core_file *outfile = NULL; - chd_file *infile = NULL; - const chd_header *header; - UINT64 bytesremaining; - void *hunk = NULL; - file_error filerr; - chd_error err; - int hunknum; - - /* require 4 args total */ - if (argc != 4) - return usage(); - - /* extract the data */ - inputfile = argv[2]; - outputfile = argv[3]; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - - /* get the header */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &infile); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } - header = chd_get_header(infile); + // see if anything was specified + astring *compression_str = params.find(OPTION_COMPRESSION); + if (compression_str == NULL) + return; - /* allocate memory to hold a hunk */ - hunk = malloc(header->hunkbytes); - if (hunk == NULL) + // special case: 'none' + if (*compression_str == "none") { - fprintf(stderr, "Out of memory allocating hunk buffer!\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; + compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; + return; } - - /* create the output file */ - filerr = core_fopen(outputfile, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &outfile); - if (filerr != FILERR_NONE) - { - fprintf(stderr, "Error opening output file '%s'\n", outputfile); - err = CHDERR_CANT_CREATE_FILE; - goto cleanup; + + // iterate through compressors + int index = 0; + for (int start = 0, end = compression_str->chr(0, ','); index < 4; start = end + 1, end = compression_str->chr(end + 1, ',')) + { + astring name(*compression_str, start, (end == -1) ? -1 : end - start); + if (name.len() != 4) + report_error(1, "Invalid compressor '%s' specified", name.cstr()); + chd_codec_type type = CHD_MAKE_TAG(name[0], name[1], name[2], name[3]); + if (!chd_codec_list::codec_exists(type)) + report_error(1, "Invalid compressor '%s' specified", name.cstr()); + compression[index++] = type; + if (end == -1) + break; } +} - /* loop over hunks, reading and writing */ - bytesremaining = header->logicalbytes; - for (hunknum = 0; hunknum < header->totalhunks; hunknum++) - { - UINT32 byteswritten, bytes_to_write; - /* progress */ - progress(hunknum == 0, "Extracting hunk %d/%d... \r", hunknum, header->totalhunks); +//------------------------------------------------- +// parse_numprocessors - handle the numprocessors +// command +//------------------------------------------------- - /* read the hunk into a buffer */ - err = chd_read(infile, hunknum, hunk); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", hunknum, chd_error_string(err)); - goto cleanup; - } +static void parse_numprocessors(const parameters_t ¶ms) +{ +#if NUM_PROCESSORS_SUPPORTED + astring *numprocessors_str = params.find(OPTION_NUMPROCESSORS); + if (numprocessors_str == NULL) + return; - /* write the hunk to the file */ - bytes_to_write = MIN(bytesremaining, header->hunkbytes); - core_fseek(outfile, (UINT64)hunknum * (UINT64)header->hunkbytes, SEEK_SET); - byteswritten = core_fwrite(outfile, hunk, bytes_to_write); - if (byteswritten != bytes_to_write) - { - fprintf(stderr, "Error writing hunk %d to output file: %s\n", hunknum, chd_error_string(CHDERR_WRITE_ERROR)); - err = CHDERR_WRITE_ERROR; - goto cleanup; - } - bytesremaining -= byteswritten; + int count = atoi(*numprocessors_str); + if (count > 0) + { + extern int osd_num_processors; + osd_num_processors = count; } - progress(TRUE, "Extraction complete! \n"); - -cleanup: - /* clean up our mess */ - if (outfile != NULL) - core_fclose(outfile); - if (hunk != NULL) - free(hunk); - if (infile != NULL) - chd_close(infile); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); +#endif } -/*------------------------------------------------- - do_extractcd - extract a CDRDAO .toc/.bin - or CDRWIN .cue/.bin file from a CHD-CD image --------------------------------------------------*/ +//------------------------------------------------- +// compression_string - create a friendly string +// describing a set of compressors +//------------------------------------------------- -static int do_extractcd(int argc, char *argv[], int param) +static const char *compression_string(astring &string, chd_codec_type compression[4]) { - const char *inputfile, *outputfile, *outputfile2; - core_file *outfile2 = NULL; - chd_file *inputchd = NULL; - cdrom_file *cdrom = NULL; - FILE *outfile = NULL; - const cdrom_toc *toc; - UINT64 out2offs, discoffs; - file_error filerr; - chd_error err; - int track, cuemode; + // output compression types + string.reset(); + if (compression[0] == CHD_CODEC_NONE) + return string.cpy("none"); + + // iterate over types + for (int index = 0; index < 4; index++) + { + chd_codec_type type = compression[index]; + if (type == CHD_CODEC_NONE) + break; + if (index != 0) + string.cat(", "); + string.cat((type >> 24) & 0xff).cat((type >> 16) & 0xff).cat((type >> 8) & 0xff).cat(type & 0xff); + string.cat(" (").cat(chd_codec_list::codec_name(type)).cat(")"); + } + return string; +} + - /* require 5 args total */ - if (argc != 5) - return usage(); +//------------------------------------------------- +// compress_common - standard compression loop +//------------------------------------------------- - /* extract the data */ - inputfile = argv[2]; - outputfile = argv[3]; - outputfile2 = argv[4]; +static void compress_common(chd_file_compressor &chd) +{ + // begin compressing + chd.compress_begin(); - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output files: %s and %s\n", outputfile, outputfile2); + // loop until done + double complete, ratio; + chd_error err; + while ((err = chd.compress_continue(complete, ratio)) == CHDERR_WALKING_PARENT || err == CHDERR_COMPRESSING) + if (err == CHDERR_WALKING_PARENT) + progress(false, "Examining parent, %.1f%% complete... \r", 100.0 * complete); + else + progress(false, "Compressing, %.1f%% complete... (ratio=%.1f%%) \r", 100.0 * complete, 100.0 * ratio); - /* get the header */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd); + // handle errors if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } - - /* open the CD */ - cdrom = cdrom_open(inputchd); - if (cdrom == NULL) - { - fprintf(stderr, "Error opening CHD-CD '%s'\n", inputfile); - err = CHDERR_INVALID_FILE; - goto cleanup; - } + report_error(1, "Error during compression: %-40s", chd_file::error_string(err)); - /* check for CDRWIN format */ - cuemode = 0; - if (strstr(outputfile, ".cue")) - { - cuemode = 1; - } - - /* get the TOC data */ - toc = cdrom_get_toc(cdrom); - - /* create the output files */ - outfile = fopen(outputfile, "w"); - if (outfile == NULL) - { - fprintf(stderr, "Error opening output file '%s'\n", outputfile); - err = CHDERR_CANT_CREATE_FILE; - goto cleanup; - } + // final progress update + progress(true, "Compression complete ... final ratio = %.1f%% \n", 100.0 * ratio); +} - if (!cuemode) - { - fprintf(outfile, "CD_ROM\n\n\n"); - } - filerr = core_fopen(outputfile2, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &outfile2); - if (filerr != FILERR_NONE) - { - fprintf(stderr, "Error opening output file '%s'\n", outputfile2); - err = CHDERR_CANT_CREATE_FILE; - goto cleanup; - } +//------------------------------------------------- +// output_track_metadata - output track metadata +// to a CUE file +//------------------------------------------------- - /* process away */ - out2offs = 0; - discoffs = 0; - for (track = 0; track < toc->numtrks; track++) +void output_track_metadata(bool cuemode, core_file *file, int tracknum, const cdrom_track_info &info, const char *filename, UINT32 frameoffs, UINT64 discoffs) +{ + // CUE mode? + if (cuemode) { - UINT32 m, s, f, frame, trackframes; + // first track specifies the file + if (tracknum == 0) + core_fprintf(file, "FILE \"%s\" BINARY\n", filename); - progress(TRUE, "Extracting track %d... \r", track+1); - - trackframes = toc->tracks[track].frames; - - if (cuemode) + // determine submode + astring tempstr; + switch (info.trktype) { - char modestr[16]; - - if (track == 0) - { - fprintf(outfile, "FILE \"%s\" BINARY\n", outputfile2); - } - - switch (toc->tracks[track].trktype) - { - case CD_TRACK_MODE1: - case CD_TRACK_MODE1_RAW: - sprintf(modestr, "MODE1/%04d", toc->tracks[track].datasize); - break; + case CD_TRACK_MODE1: + case CD_TRACK_MODE1_RAW: + tempstr.format("MODE1/%04d", info.datasize); + break; - case CD_TRACK_MODE2: - case CD_TRACK_MODE2_FORM1: - case CD_TRACK_MODE2_FORM2: - case CD_TRACK_MODE2_FORM_MIX: - case CD_TRACK_MODE2_RAW: - sprintf(modestr, "MODE2/%04d", toc->tracks[track].datasize); - break; + case CD_TRACK_MODE2: + case CD_TRACK_MODE2_FORM1: + case CD_TRACK_MODE2_FORM2: + case CD_TRACK_MODE2_FORM_MIX: + case CD_TRACK_MODE2_RAW: + tempstr.format("MODE2/%04d", info.datasize); + break; - case CD_TRACK_AUDIO: - strcpy(modestr, "AUDIO"); - break; - } + case CD_TRACK_AUDIO: + tempstr.cpy("AUDIO"); + break; + } + + // output TRACK entry + core_fprintf(file, " TRACK %02d %s\n", tracknum + 1, tempstr.cstr()); - fprintf(outfile, " TRACK %02d %s\n", track+1, modestr); + // output PREGAP + if (info.pregap > 0) + core_fprintf(file, " PREGAP %s\n", msf_string_from_frames(tempstr, info.pregap)); - if (toc->tracks[track].pregap > 0) - { - f = toc->tracks[track].pregap; - s = f / 75; - f %= 75; - m = s / 60; - s %= 60; - fprintf(outfile, " PREGAP %02d:%02d:%02d\n", m, s, f); - } + // output track data + core_fprintf(file, " INDEX 01 %s\n", msf_string_from_frames(tempstr, frameoffs)); - f = discoffs; - s = f / 75; - f %= 75; - m = s / 60; - s %= 60; - fprintf(outfile, " INDEX 01 %02d:%02d:%02d\n", m, s, f); + // output POSTGAP + if (info.postgap > 0) + core_fprintf(file, " POSTGAP %s\n", msf_string_from_frames(tempstr, info.postgap)); + } + + // non-CUE mode + else + { + // header on the first track + if (tracknum == 0) + core_fprintf(file, "CD_ROM\n\n\n"); + core_fprintf(file, "// Track %d\n", tracknum + 1); - if (toc->tracks[track].postgap > 0) - { - f = toc->tracks[track].postgap; - s = f / 75; - f %= 75; - m = s / 60; - s %= 60; - fprintf(outfile, " POSTGAP %02d:%02d:%02d\n", m, s, f); - } - } + // write out the track type + astring modesubmode; + if (info.subtype != CD_SUB_NONE) + modesubmode.format("%s %s", cdrom_get_type_string(info.trktype), cdrom_get_subtype_string(info.subtype)); else - { - char modesubmode[64]; - - fprintf(outfile, "// Track %d\n", track+1); + modesubmode.format("%s", cdrom_get_type_string(info.trktype)); + core_fprintf(file, "TRACK %s\n", modesubmode.cstr()); - /* write out the track type */ - if (toc->tracks[track].subtype != CD_SUB_NONE) - { - sprintf(modesubmode, "%s %s", cdrom_get_type_string(toc->tracks[track].trktype), cdrom_get_subtype_string(toc->tracks[track].subtype)); - } - else - { - sprintf(modesubmode, "%s", cdrom_get_type_string(toc->tracks[track].trktype)); - } + // write out the attributes + core_fprintf(file, "NO COPY\n"); + if (info.trktype == CD_TRACK_AUDIO) + { + core_fprintf(file, "NO PRE_EMPHASIS\n"); + core_fprintf(file, "TWO_CHANNEL_AUDIO\n"); + } - fprintf(outfile, "TRACK %s\n", modesubmode); + // output pregap + astring tempstr; + if (info.pregap > 0) + core_fprintf(file, "ZERO %s %s\n", modesubmode.cstr(), msf_string_from_frames(tempstr, info.pregap)); - /* write out the attributes */ - fprintf(outfile, "NO COPY\n"); - if (toc->tracks[track].trktype == CD_TRACK_AUDIO) - { - fprintf(outfile, "NO PRE_EMPHASIS\n"); - fprintf(outfile, "TWO_CHANNEL_AUDIO\n"); - } + // all tracks but the first one have a file offset + if (tracknum > 0) + core_fprintf(file, "DATAFILE \"%s\" #%d %s // length in bytes: %d\n", filename, UINT32(discoffs), msf_string_from_frames(tempstr, info.frames), info.frames * (info.datasize + info.subsize)); + else + core_fprintf(file, "DATAFILE \"%s\" %s // length in bytes: %d\n", filename, msf_string_from_frames(tempstr, info.frames), info.frames * (info.datasize + info.subsize)); - if (toc->tracks[track].pregap > 0) - { - f = toc->tracks[track].pregap; - s = f / 75; - f %= 75; - m = s / 60; - s %= 60; + // tracks with pregaps get a START marker too + if (info.pregap > 0) + core_fprintf(file, "START %s\n", msf_string_from_frames(tempstr, info.pregap)); - fprintf(outfile, "ZERO %s %02d:%02d:%02d\n", modesubmode, m, s, f); - } + core_fprintf(file, "\n\n"); + } +} - /* convert to minutes/seconds/frames */ - f = trackframes; - s = f / 75; - f %= 75; - m = s / 60; - s %= 60; - /* all tracks but the first one have a file offset */ - if (track > 0) - fprintf(outfile, "DATAFILE \"%s\" #%d %02d:%02d:%02d // length in bytes: %d\n", outputfile2, (UINT32)out2offs, m, s, f, trackframes*(toc->tracks[track].datasize+toc->tracks[track].subsize)); - else - fprintf(outfile, "DATAFILE \"%s\" %02d:%02d:%02d // length in bytes: %d\n", outputfile2, m, s, f, trackframes*(toc->tracks[track].datasize+toc->tracks[track].subsize)); +//------------------------------------------------- +// do_info - dump the header information from +// a drive image +//------------------------------------------------- - /* tracks with pregaps get a START marker too */ - if (toc->tracks[track].pregap > 0) +static void do_info(parameters_t ¶ms) +{ + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd); + + // print filename and version + astring tempstr; + printf("Input file: %s\n", params.find(OPTION_INPUT)->cstr()); + printf("File Version: %d\n", input_chd.version()); + if (input_chd.version() < 3) + report_error(1, "Unsupported version (%d); use an older chdman to upgrade to version 3 or later", input_chd.version()); + + // output cmpression and size information + chd_codec_type compression[4] = { input_chd.compression(0), input_chd.compression(1), input_chd.compression(2), input_chd.compression(3) }; + printf("Logical size: %s bytes\n", big_int_string(tempstr, input_chd.logical_bytes())); + printf("Hunk Size: %s bytes\n", big_int_string(tempstr, input_chd.hunk_bytes())); + printf("Total Hunks: %s\n", big_int_string(tempstr, input_chd.hunk_count())); + printf("Unit Size: %s bytes\n", big_int_string(tempstr, input_chd.unit_bytes())); + printf("Total Units: %s\n", big_int_string(tempstr, input_chd.unit_count())); + printf("Compression: %s\n", compression_string(tempstr, compression)); + printf("CHD size: %s bytes\n", big_int_string(tempstr, core_fsize(input_chd))); + if (compression[0] != CHD_CODEC_NONE) + printf("Ratio: %.1f%%\n", 100.0 * double(core_fsize(input_chd)) / double(input_chd.logical_bytes())); + + // add SHA1 output + sha1_t overall = input_chd.sha1(); + if (overall != sha1_t::null) + { + printf("SHA1: %s\n", overall.as_string(tempstr)); + if (input_chd.version() >= 4) + printf("Data SHA1: %s\n", input_chd.raw_sha1().as_string(tempstr)); + } + sha1_t parent = input_chd.parent_sha1(); + if (parent != sha1_t::null) + printf("Parent SHA1: %s\n", parent.as_string(tempstr)); + + // print out metadata + dynamic_buffer buffer; + dynamic_array<metadata_index_info> info; + for (int index = 0; ; index++) + { + // get the indexed metadata item; stop when we hit an error + chd_metadata_tag metatag; + UINT8 metaflags; + chd_error err = input_chd.read_metadata(CHDMETATAG_WILDCARD, index, buffer, metatag, metaflags); + if (err != CHDERR_NONE) + break; + + // determine our index + UINT32 metaindex = ~0; + for (int cur = 0; cur < info.count(); cur++) + if (info[cur].tag == metatag) { - f = toc->tracks[track].pregap; - s = f / 75; - f %= 75; - m = s / 60; - s %= 60; - - fprintf(outfile, "START %02d:%02d:%02d\n", m, s, f); + metaindex = ++info[cur].index; + break; } - - fprintf(outfile, "\n\n"); - } - - /* now write the actual data */ - for (frame = 0; frame < trackframes; frame++) + + // if not found, add to our tracking + if (metaindex == ~0) { - UINT8 sector[CD_MAX_SECTOR_DATA + CD_MAX_SUBCODE_DATA]; - UINT32 byteswritten; - - progress(frame == 0, "Extracting track %d... %d/%d... \r", track+1, frame, trackframes); - - /* read the raw data */ - cdrom_read_data(cdrom, cdrom_get_track_start(cdrom, track) + frame, sector, toc->tracks[track].trktype); - - /* for CDRWin, audio tracks must be reversed */ - if ((cuemode) && (toc->tracks[track].trktype == CD_TRACK_AUDIO)) - { - int swapindex; - - for (swapindex = 0; swapindex < toc->tracks[track].datasize; swapindex += 2) - { - UINT8 swaptemp = sector[swapindex]; - sector[swapindex] = sector[swapindex+1]; - sector[swapindex + 1] = swaptemp; - } - } + metadata_index_info curinfo = { metatag, 0 }; + info.append(curinfo); + metaindex = 0; + } - /* write it out */ - core_fseek(outfile2, out2offs, SEEK_SET); - byteswritten = core_fwrite(outfile2, sector, toc->tracks[track].datasize); - if (byteswritten != toc->tracks[track].datasize) - { - fprintf(stderr, "Error writing frame %d to output file: %s\n", frame, chd_error_string(CHDERR_WRITE_ERROR)); - err = CHDERR_WRITE_ERROR; - goto cleanup; - } - out2offs += toc->tracks[track].datasize; - discoffs++; + // print either a string representation or a hex representation of the tag + if (isprint((metatag >> 24) & 0xff) && isprint((metatag >> 16) & 0xff) && isprint((metatag >> 8) & 0xff) && isprint(metatag & 0xff)) + printf("Metadata: Tag='%c%c%c%c' Index=%d Length=%d bytes\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metaindex, buffer.count()); + else + printf("Metadata: Tag=%08x Index=%d Length=%d bytes\n", metatag, metaindex, buffer.count()); + printf(" "); - /* read the subcode data */ - if (toc->tracks[track].subtype != CD_SUB_NONE) + // print up to 60 characters of metadata + UINT32 count = MIN(60, buffer.count()); + for (int chnum = 0; chnum < count; chnum++) + printf("%c", isprint(UINT8(buffer[chnum])) ? buffer[chnum] : '.'); + printf("\n"); + } + + // print compression stats if verbose + if (params.find(OPTION_VERBOSE) != NULL) + { + UINT32 compression_types[10] = { 0 }; + for (UINT32 hunknum = 0; hunknum < input_chd.hunk_count(); hunknum++) + { + // get info on this hunk + chd_codec_type codec; + UINT32 compbytes; + chd_error err = input_chd.hunk_info(hunknum, codec, compbytes); + if (err != CHDERR_NONE) + report_error(1, "Error getting info on hunk %d: %s", hunknum, chd_file::error_string(err)); + + // decode into our data + if (codec > CHD_CODEC_MINI) + for (int comptype = 0; comptype < 4; comptype++) + if (codec == input_chd.compression(comptype)) + { + codec = CHD_CODEC_MINI + 1 + comptype; + break; + } + if (codec > ARRAY_LENGTH(compression_types)) + codec = ARRAY_LENGTH(compression_types) - 1; + + // count stats + compression_types[codec]++; + } + + // output the stats + printf("\n"); + printf(" Hunks Percent Name\n"); + printf("---------- ------- ------------------------------------\n"); + for (int comptype = 0; comptype < ARRAY_LENGTH(compression_types); comptype++) + if (compression_types[comptype] != 0) { - cdrom_read_subcode(cdrom, cdrom_get_track_start(cdrom, track) + frame, sector); - - /* write it out */ - core_fseek(outfile2, out2offs, SEEK_SET); - byteswritten = core_fwrite(outfile2, sector, toc->tracks[track].subsize); - if (byteswritten != toc->tracks[track].subsize) + // determine the name + const char *name = "Unknown"; + switch (comptype) { - fprintf(stderr, "Error writing frame %d to output file: %s\n", frame, chd_error_string(CHDERR_WRITE_ERROR)); - err = CHDERR_WRITE_ERROR; - goto cleanup; + case CHD_CODEC_NONE: name = "Uncompressed"; break; + case CHD_CODEC_SELF: name = "Copy from self"; break; + case CHD_CODEC_PARENT: name = "Copy from parent"; break; + case CHD_CODEC_MINI: name = "Legacy 8-byte mini"; break; + default: + int index = comptype - 1 - CHD_CODEC_MINI; + if (index < 4) + name = chd_codec_list::codec_name(input_chd.compression(index)); + break; } - out2offs += toc->tracks[track].subsize; + + // output the stats + astring tempstr; + printf("%10s %5.1f%% %-40s\n", + big_int_string(tempstr, compression_types[comptype]), + 100.0 * double(compression_types[comptype]) / double(input_chd.hunk_count()), + name); } - } - progress(TRUE, "Extracting track %d... complete \n", track+1); } - progress(TRUE, "Completed!\n"); - -cleanup: - /* close everything down */ - if (outfile != NULL) - fclose(outfile); - if (outfile2 != NULL) - core_fclose(outfile2); - if (cdrom != NULL) - cdrom_close(cdrom); - if (inputchd != NULL) - chd_close(inputchd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_extractav - extract an AVI file from a - CHD image --------------------------------------------------*/ +//------------------------------------------------- +// do_verify - validate the SHA1 on a CHD +//------------------------------------------------- -static int do_extractav(int argc, char *argv[], int param) +static void do_verify(parameters_t ¶ms) { - int fps, fpsfrac, width, height, interlaced, channels, rate, totalframes; - av_codec_decompress_config avconfig; - const char *inputfile, *outputfile; - UINT32 firstframe, numframes; - UINT32 framenum, numsamples; - UINT32 fps_times_1million; - const chd_header *header; - chd_file *chd = NULL; - avi_file *avi = NULL; - avi_movie_info info; - char metadata[256]; - avi_error avierr; - chd_error err; - int chnum; - - /* require 4-6 args total */ - if (argc < 4 || argc > 6) - return usage(); - - /* extract the data */ - inputfile = argv[2]; - outputfile = argv[3]; - firstframe = (argc > 4) ? atoi(argv[4]) : 0; - numframes = (argc > 5) ? atoi(argv[5]) : 1000000; - - /* print some info */ - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - - /* get the header */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } - header = chd_get_header(chd); - - /* get the metadata */ - err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error getting A/V metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* extract the info */ - if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) - { - fprintf(stderr, "Improperly formatted metadata\n"); - err = CHDERR_INVALID_METADATA; - goto cleanup; - } - fps_times_1million = fps * 1000000 + fpsfrac; - totalframes = header->totalhunks; - - /* adjust for interlacing */ - if (interlaced) - { - fps_times_1million /= 2; - height *= 2; - firstframe *= 2; - numframes *= 2; - } - numframes = MIN(totalframes - firstframe, numframes); - - /* allocate a video buffer */ - { - bitmap_yuy16 fullbitmap(width, height); - avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); - - /* allocate audio buffers */ - avconfig.maxsamples = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; - avconfig.actsamples = &numsamples; - for (chnum = 0; chnum < channels; chnum++) - { - avconfig.audio[chnum] = (INT16 *)malloc(avconfig.maxsamples * 2); - if (avconfig.audio[chnum] == NULL) - { - fprintf(stderr, "Out of memory allocating temporary audio buffer\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - } - - /* print some of it */ - printf("Use frames: %d-%d\n", firstframe, firstframe + numframes - 1); - printf("Frame rate: %d.%06d\n", fps_times_1million / 1000000, fps_times_1million % 1000000); - printf("Frame size: %d x %d %s\n", width, height, interlaced ? "interlaced" : "non-interlaced"); - printf("Audio: %d channels at %d Hz\n", channels, rate); - printf("Total frames: %d (%02d:%02d:%02d)\n", totalframes, - (UINT32)((UINT64)totalframes * 1000000 / fps_times_1million / 60 / 60), - (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million / 60) % 60), - (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million) % 60)); - - /* build up the movie info */ - info.video_format = FORMAT_YUY2; - info.video_timescale = fps_times_1million; - info.video_sampletime = 1000000; - info.video_width = width; - info.video_height = height; - info.video_depth = 16; - info.audio_format = 0; - info.audio_timescale = rate; - info.audio_sampletime = 1; - info.audio_channels = channels; - info.audio_samplebits = 16; - info.audio_samplerate = rate; - - /* create the output file */ - avierr = avi_create(outputfile, &info, &avi); - if (avierr != AVIERR_NONE) + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd); + + // only makes sense for compressed CHDs with valid SHA1's + if (!input_chd.compressed()) + report_error(0, "No verification to be done; CHD is uncompressed"); + sha1_t raw_sha1 = (input_chd.version() <= 3) ? input_chd.sha1() : input_chd.raw_sha1(); + if (raw_sha1 == sha1_t::null) + report_error(0, "No verification to be done; CHD has no checksum"); + + // create an array to read into + dynamic_buffer buffer((TEMP_BUFFER_SIZE / input_chd.hunk_bytes()) * input_chd.hunk_bytes()); + + // read all the data and build up an SHA-1 + sha1_creator rawsha1; + for (UINT64 offset = 0; offset < input_chd.logical_bytes(); ) + { + progress(false, "Verifying, %.1f%% complete... \r", 100.0 * double(offset) / double(input_chd.logical_bytes())); + + // determine how much to read + UINT32 bytes_to_read = MIN(buffer.count(), input_chd.logical_bytes() - offset); + chd_error err = input_chd.read_bytes(offset, buffer, bytes_to_read); + if (err != CHDERR_NONE) + report_error(1, "Error reading CHD file (%s): %s", params.find(OPTION_INPUT)->cstr(), chd_file::error_string(err)); + + // add to the checksum + rawsha1.append(buffer, bytes_to_read); + offset += bytes_to_read; + } + sha1_t computed_sha1 = rawsha1.finish(); + + // finish up + if (raw_sha1 != computed_sha1) + { + astring tempstr; + fprintf(stderr, "Error: Raw SHA1 in header = %s\n", raw_sha1.as_string(tempstr)); + fprintf(stderr, " actual SHA1 = %s\n", computed_sha1.as_string(tempstr)); + + // fix it if requested; this also fixes the overall one so we don't need to do any more + if (params.find(OPTION_FIX) != NULL) { - fprintf(stderr, "Error opening output file '%s': %s\n", outputfile, avi_error_string(avierr)); - err = CHDERR_CANT_CREATE_FILE; - goto cleanup; + input_chd.set_raw_sha1(computed_sha1); + printf("SHA-1 updated to correct value in input CHD\n"); } - - /* loop over hunks, reading and writing */ - for (framenum = 0; framenum < numframes; framenum++) + } + else + { + printf("Raw SHA1 verification successful!\n"); + + // now include the metadata for >= v4 + if (input_chd.version() >= 4) { - /* progress */ - progress(framenum == 0, "Extracting hunk %d/%d... \r", framenum, numframes); - - /* set up the fake bitmap for this frame */ - if (!interlaced) - avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); + sha1_t computed_overall_sha1 = input_chd.compute_overall_sha1(computed_sha1); + if (input_chd.sha1() == computed_overall_sha1) + printf("Overall SHA1 verification successful!\n"); else - avconfig.video.wrap(&fullbitmap.pix16(framenum % 2), fullbitmap.width(), fullbitmap.height() / 2, fullbitmap.rowpixels() * 2); - - /* configure the decompressor for this frame */ - chd_codec_config(chd, AV_CODEC_DECOMPRESS_CONFIG, &avconfig); - - /* read the hunk into the buffers */ - err = chd_read(chd, firstframe + framenum, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", firstframe + framenum, chd_error_string(err)); - goto cleanup; - } - - /* write audio */ - for (chnum = 0; chnum < channels; chnum++) { - avierr = avi_append_sound_samples(avi, chnum, avconfig.audio[chnum], numsamples, 0); - if (avierr != AVIERR_NONE) - { - fprintf(stderr, "Error writing samples for hunk %d to AVI file: %s\n", firstframe + framenum, avi_error_string(avierr)); - goto cleanup; - } - } + astring tempstr; + fprintf(stderr, "Error: Overall SHA1 in header = %s\n", input_chd.sha1().as_string(tempstr)); + fprintf(stderr, " actual SHA1 = %s\n", computed_overall_sha1.as_string(tempstr)); - /* write video */ - if (!interlaced || (firstframe + framenum) % 2 == 1) - { - avierr = avi_append_video_frame(avi, fullbitmap); - if (avierr != AVIERR_NONE) + // fix it if requested + if (params.find(OPTION_FIX) != NULL) { - fprintf(stderr, "Error writing video for hunk %d to AVI file: %s\n", firstframe + framenum, avi_error_string(avierr)); - goto cleanup; + input_chd.set_raw_sha1(computed_sha1); + printf("SHA-1 updated to correct value in input CHD\n"); } } } - progress(TRUE, "Extraction complete! \n"); } - -cleanup: - /* clean up our mess */ - if (avi != NULL) - avi_close(avi); - for (chnum = 0; chnum < ARRAY_LENGTH(avconfig.audio); chnum++) - if (avconfig.audio[chnum] != NULL) - free(avconfig.audio[chnum]); - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_verify - validate the MD5/SHA1 on a drive - image --------------------------------------------------*/ +//------------------------------------------------- +// do_create_raw - create a new compressed raw +// image from a raw file +//------------------------------------------------- -static int do_verify(int argc, char *argv[], int param) +static void do_create_raw(parameters_t ¶ms) { - chd_verify_result verify; - const char *inputfile; - chd_file *chd = NULL; - chd_header header; - int fixed = FALSE; - chd_error err; - int i; - - /* require 3 args total */ - if (argc != 3) - return usage(); - - /* extract the data */ - inputfile = argv[2]; - - /* print some info */ - printf("Input file: %s\n", inputfile); - - /* open the CHD file */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); - if (err != CHDERR_NONE) + // catch errors so we can close & delete the output file + try { - fprintf(stderr, "Error opening CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - header = *chd_get_header(chd); - - /* verify the CHD data */ - err = chd_verify_begin(chd); - if (err == CHDERR_NONE) - { - UINT32 hunknum; - for (hunknum = 0; hunknum < header.totalhunks; hunknum++) + // process input file + core_file *input_file = NULL; + astring *input_file_str = params.find(OPTION_INPUT); + if (input_file_str != NULL) { - /* progress */ - progress(FALSE, "Verifying hunk %d/%d... \r", hunknum, header.totalhunks); - - /* verify the data */ - err = chd_verify_hunk(chd); - if (err != CHDERR_NONE) - break; + file_error filerr = core_fopen(*input_file_str, OPEN_FLAG_READ, &input_file); + if (filerr != FILERR_NONE) + report_error(1, "Unable to open file (%s)", input_file_str->cstr()); } - /* finish it */ - if (err == CHDERR_NONE) - err = chd_verify_finish(chd, &verify); - } - - /* handle errors */ - if (err != CHDERR_NONE) - { - if (err == CHDERR_CANT_VERIFY) - fprintf(stderr, "Can't verify this type of image (probably writeable)\n"); - else - fprintf(stderr, "\nError during verify: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* verify the MD5 */ - if (header.version <= 3) - { - if (memcmp(header.md5, verify.md5, sizeof(header.md5)) == 0) - printf("MD5 verification successful!\n"); - else + // process output CHD + chd_file output_parent; + astring *output_chd_str = parse_output_chd_parameters(params, output_parent); + + // process hunk size + UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : 0; + parse_hunk_size(params, 1, hunk_size); + + // process unit size + UINT32 unit_size = output_parent.opened() ? output_parent.unit_bytes() : 0; + astring *unit_size_str = params.find(OPTION_UNIT_SIZE); + if (unit_size_str != NULL) { - fprintf(stderr, "Error: MD5 in header = "); - for (i = 0; i < CHD_MD5_BYTES; i++) - fprintf(stderr, "%02x", header.md5[i]); - fprintf(stderr, "\n"); - fprintf(stderr, " actual MD5 = "); - for (i = 0; i < CHD_MD5_BYTES; i++) - fprintf(stderr, "%02x", verify.md5[i]); - fprintf(stderr, "\n"); - - /* fix it */ - if (param) - { - memcpy(header.md5, verify.md5, sizeof(header.md5)); - fixed = TRUE; - } + unit_size = parse_number(*unit_size_str); + if (hunk_size % unit_size != 0) + report_error(1, "Unit size is not an even divisor of the hunk size"); } - } - /* verify the SHA1 */ - if (header.version >= 3) - { - if (memcmp(header.sha1, verify.sha1, sizeof(header.sha1)) == 0) - printf("SHA1 verification successful!\n"); - else + // process input start/end (needs to know hunk_size) + UINT64 input_start; + UINT64 input_end; + parse_input_start_end(params, core_fsize(input_file), hunk_size, hunk_size, input_start, input_end); + + // process compression + chd_codec_type compression[4]; + memcpy(compression, s_default_raw_compression, sizeof(compression)); + parse_compression(params, compression); + + // process numprocessors + parse_numprocessors(params); + + // print some info + astring tempstr; + printf("Output CHD: %s\n", output_chd_str->cstr()); + if (output_parent.opened()) + printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); + printf("Input file: %s\n", input_file_str->cstr()); + if (input_start != 0 || input_end != core_fsize(input_file)) { - fprintf(stderr, "Error: SHA1 in header = "); - for (i = 0; i < CHD_SHA1_BYTES; i++) - fprintf(stderr, "%02x", header.sha1[i]); - fprintf(stderr, "\n"); - fprintf(stderr, " actual SHA1 = "); - for (i = 0; i < CHD_SHA1_BYTES; i++) - fprintf(stderr, "%02x", verify.sha1[i]); - fprintf(stderr, "\n"); - - /* fix it */ - if (param) - { - memcpy(header.sha1, verify.sha1, sizeof(header.sha1)); - fixed = TRUE; - } - - /* verify the raw SHA1 */ - if (header.version >= 4) - { - if (memcmp(header.rawsha1, verify.rawsha1, sizeof(header.rawsha1)) != 0) - { - fprintf(stderr, "Error: raw SHA1 in header = "); - for (i = 0; i < CHD_SHA1_BYTES; i++) - fprintf(stderr, "%02x", header.rawsha1[i]); - fprintf(stderr, "\n"); - fprintf(stderr, " actual raw SHA1 = "); - for (i = 0; i < CHD_SHA1_BYTES; i++) - fprintf(stderr, "%02x", verify.rawsha1[i]); - fprintf(stderr, "\n"); - - /* fix it */ - if (param) - { - memcpy(header.rawsha1, verify.rawsha1, sizeof(header.rawsha1)); - fixed = TRUE; - } - } - } + printf("Input start: %s\n", big_int_string(tempstr, input_start)); + printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); } - } - - /* close the drive */ - chd_close(chd); - chd = NULL; + printf("Compression: %s\n", compression_string(tempstr, compression)); + printf("Hunk size: %s\n", big_int_string(tempstr, hunk_size)); + printf("Logical size: %s\n", big_int_string(tempstr, input_end - input_start)); - /* update the header */ - if (fixed) - { - err = chd_set_header(inputfile, &header); - if (err != CHDERR_NONE) - fprintf(stderr, "Error writing new header: %s\n", chd_error_string(err)); + // create the new CHD + chd_rawfile_compressor chd(input_file, input_start, input_end); + chd_error err; + if (output_parent.opened()) + err = chd.create(*output_chd_str, input_end - input_start, hunk_size, compression, output_parent); else - printf("Updated header successfully\n"); - } - -cleanup: - /* close everything down */ - if (chd != NULL) - chd_close(chd); - return (err != CHDERR_NONE); -} - - -/*------------------------------------------------- - do_fixavdata - fix the AV metadata for an - A/V file --------------------------------------------------*/ - -static int do_fixavdata(int argc, char *argv[], int param) -{ - int fps, fpsfrac, width, height, interlaced, channels, rate; - av_codec_decompress_config avconfig; - const char *inputfile; - UINT8 *vbidata = NULL; - int writeable = FALSE; - chd_file *chd = NULL; - bitmap_yuy16 fakebitmap; - char metadata[256]; - chd_header header; - UINT32 actlength; - UINT32 framenum; - chd_error err; - int fixframes = 0; - int fixes = 0; - - /* require 3 args total */ - if (argc != 3) - return usage(); - - /* extract the data */ - inputfile = argv[2]; - - /* print some info */ - printf("Input file: %s\n", inputfile); + err = chd.create(*output_chd_str, input_end - input_start, hunk_size, unit_size, compression); + if (err != CHDERR_NONE) + report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); + + // if we have a parent, copy forward all the metadata + if (output_parent.opened()) + chd.clone_all_metadata(output_parent); - /* get the header */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; + // compress it generically + compress_common(chd); } - header = *chd_get_header(chd); - - /* get the metadata */ - err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); - if (err != CHDERR_NONE) + catch (...) { - fprintf(stderr, "Error getting A/V metadata: %s\n", chd_error_string(err)); - goto cleanup; + // delete the output file + astring *output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str != NULL) + osd_rmfile(*output_chd_str); + throw; } +} - /* extract the info */ - if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) - { - fprintf(stderr, "Improperly formatted metadata\n"); - err = CHDERR_INVALID_METADATA; - goto cleanup; - } - /* allocate space for the frame data */ - if ((height != 524/2 && height != 624/2) || !interlaced) - { - fprintf(stderr, "This file does not need VBI metadata\n"); - err = CHDERR_INVALID_METADATA; - goto cleanup; - } +//------------------------------------------------- +// do_create_hd - create a new compressed hard +// disk image from a raw file +//------------------------------------------------- - /* allocate a video buffer */ +static void do_create_hd(parameters_t ¶ms) +{ + // catch errors so we can close & delete the output file + try { - bitmap_yuy16 fullbitmap(width, height); - avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); - - /* allocate memory for VBI data */ - vbidata = (UINT8 *)malloc(header.totalhunks * VBI_PACKED_BYTES); - if (vbidata == NULL) + // process input file + core_file *input_file = NULL; + astring *input_file_str = params.find(OPTION_INPUT); + if (input_file_str != NULL) { - fprintf(stderr, "Out of memory allocating VBI data\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; + file_error filerr = core_fopen(*input_file_str, OPEN_FLAG_READ, &input_file); + if (filerr != FILERR_NONE) + report_error(1, "Unable to open file (%s)", input_file_str->cstr()); } - - /* read the metadata */ - err = chd_get_metadata(chd, AV_LD_METADATA_TAG, 0, vbidata, header.totalhunks * VBI_PACKED_BYTES, &actlength, NULL, NULL); - if (err != CHDERR_NONE) + + // process output CHD + chd_file output_parent; + astring *output_chd_str = parse_output_chd_parameters(params, output_parent); + + // process sectorsize + UINT32 sector_size = output_parent.opened() ? output_parent.unit_bytes() : IDE_SECTOR_SIZE; + astring *sectorsize_str = params.find(OPTION_SECTOR_SIZE); + if (sectorsize_str != NULL) { - fprintf(stderr, "Error getting VBI metadata: %s\n", chd_error_string(err)); - memset(vbidata, 0, header.totalhunks * VBI_PACKED_BYTES); - fixes++; + if (output_parent.opened()) + report_error(1, "Sector size does not apply when creating a diff from the parent"); + sector_size = parse_number(*sectorsize_str); } - if (actlength != header.totalhunks * VBI_PACKED_BYTES) + + // process hunk size (needs to know sector_size) + UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : MAX((4096 / sector_size) * sector_size, sector_size); + parse_hunk_size(params, sector_size, hunk_size); + + // process input start/end (needs to know hunk_size) + UINT64 input_start; + UINT64 input_end; + parse_input_start_end(params, core_fsize(input_file), hunk_size, hunk_size, input_start, input_end); + + // process compression + chd_codec_type compression[4]; + memcpy(compression, s_default_hd_compression, sizeof(compression)); + if (input_file == NULL) + compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; + parse_compression(params, compression); + if (input_file == NULL && compression[0] != CHD_CODEC_NONE) + report_error(1, "Blank hard disks must be uncompressed"); + + // process numprocessors + parse_numprocessors(params); + + // process chs + UINT32 cylinders = 0; + UINT32 heads = 0; + UINT32 sectors = 0; + astring *chs_str = params.find(OPTION_CHS); + if (chs_str != NULL) { - fprintf(stderr, "VBI metadata incorrect size\n"); - memset(vbidata, 0, header.totalhunks * VBI_PACKED_BYTES); - fixes++; + if (output_parent.opened()) + report_error(1, "CHS does not apply when creating a diff from the parent"); + if (sscanf(*chs_str, "%d,%d,%d", &cylinders, &heads, §ors) != 3) + report_error(1, "Invalid CHS string; must be of the form <cylinders>,<heads>,<sectors>"); } - /* loop over hunks, reading */ - for (framenum = 0; framenum < header.totalhunks; framenum++) + // process ident + dynamic_buffer identdata; + if (output_parent.opened()) + output_parent.read_metadata(HARD_DISK_IDENT_METADATA_TAG, 0, identdata); + astring *ident_str = params.find(OPTION_IDENT); + if (ident_str != NULL) { - vbi_metadata origvbi; - vbi_metadata vbi; - UINT32 vbiframe; - - /* progress */ - progress(framenum == 0, "Processing hunk %d/%d... \r", framenum, header.totalhunks); - - /* set up the fake bitmap for this frame */ - avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); - - /* configure the decompressor for this frame */ - chd_codec_config(chd, AV_CODEC_DECOMPRESS_CONFIG, &avconfig); - - /* read the hunk into the buffers */ - err = chd_read(chd, framenum, NULL); - if (err != CHDERR_NONE) + // load the file + file_error filerr = core_fload(*ident_str, identdata); + if (filerr != FILERR_NONE) + report_error(1, "Error reading ident file (%s)", ident_str->cstr()); + + // must be at least 14 bytes; extract CHS data from there + if (identdata.count() < 14) + report_error(1, "Ident file '%s' is invalid (too short)", ident_str->cstr()); + cylinders = (identdata[3] << 8) | identdata[2]; + heads = (identdata[7] << 8) | identdata[6]; + sectors = (identdata[13] << 8) | identdata[12]; + } + + // extract geometry from the parent if we have one + if (output_parent.opened() && cylinders == 0) + { + astring metadata; + if (output_parent.read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) != CHDERR_NONE) + report_error(1, "Unable to find hard disk metadata in parent CHD"); + if (sscanf(metadata, HARD_DISK_METADATA_FORMAT, &cylinders, &heads, §ors, §or_size) != 4) + report_error(1, "Error parsing hard disk metadata in parent CHD"); + } + + // if no CHS values, try to guess them + if (cylinders == 0) + { + if (input_file == NULL && input_end - input_start == 0) + report_error(1, "Blank hard drives must specify either a length or a set of CHS values"); + guess_chs(input_file_str, input_end - input_start, sector_size, cylinders, heads, sectors, sector_size); + } + UINT32 totalsectors = cylinders * heads * sectors; + + // print some info + astring tempstr; + printf("Output CHD: %s\n", output_chd_str->cstr()); + if (output_parent.opened()) + printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); + if (input_file != NULL) + { + printf("Input file: %s\n", input_file_str->cstr()); + if (input_start != 0 || input_end != core_fsize(input_file)) { - fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", framenum, chd_error_string(err)); - goto cleanup; + printf("Input start: %s\n", big_int_string(tempstr, input_start)); + printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); } - - /* unpack the current data for this frame */ - vbi_metadata_unpack(&origvbi, &vbiframe, &vbidata[framenum * VBI_PACKED_BYTES]); - - /* parse the video data */ - vbi_parse_all(&avconfig.video.pix16(0), avconfig.video.rowpixels(), avconfig.video.width(), 8, &vbi); - - /* verify the data */ - if (vbiframe != 0 || origvbi.white != 0 || origvbi.line16 != 0 || origvbi.line17 != 0 || origvbi.line18 != 0 || origvbi.line1718 != 0) - { - int errors = 0; - - if (vbiframe != framenum) - { - fprintf(stderr, "%d:Frame mismatch in VBI data (%d, should be %d)\n", framenum, vbiframe, framenum); - errors++; - } - if (vbi.white != origvbi.white) - { - fprintf(stderr, "%d:White flag mismatch in VBI data (%d, should be %d)\n", framenum, origvbi.white, vbi.white); - errors++; - } - if (vbi.line16 != origvbi.line16) - { - fprintf(stderr, "%d:Line 16 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line16, vbi.line16); - errors++; - } - if (vbi.line17 != origvbi.line17) - { - fprintf(stderr, "%d:Line 17 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line17, vbi.line17); - errors++; - } - if (vbi.line18 != origvbi.line18) - { - fprintf(stderr, "%d:Line 18 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line18, vbi.line18); - errors++; - } - if (vbi.line1718 != origvbi.line1718) - { - fprintf(stderr, "%d:Line 17/18 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line1718, vbi.line1718); - errors++; - } - fixes += errors; - fixframes += (errors != 0); - } - - /* pack the new data */ - vbi_metadata_pack(&vbidata[framenum * VBI_PACKED_BYTES], framenum, &vbi); } - progress(TRUE, "Processing complete! \n"); - - /* print final results */ - if (fixes == 0) - printf("\nNo fixes required\n"); + printf("Compression: %s\n", compression_string(tempstr, compression)); + printf("Cylinders: %d\n", cylinders); + printf("Heads: %d\n", heads); + printf("Sectors: %d\n", sectors); + printf("Bytes/sector: %d\n", sector_size); + printf("Sectors/hunk: %d\n", hunk_size / sector_size); + printf("Logical size: %s\n", big_int_string(tempstr, UINT64(totalsectors) * UINT64(sector_size))); + + // create the new hard drive + chd_rawfile_compressor chd(input_file, input_start, input_end); + chd_error err; + if (output_parent.opened()) + err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(sector_size), hunk_size, compression, output_parent); else - printf("\nFound %d errors on %d frames\n", fixes, fixframes); + err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(sector_size), hunk_size, sector_size, compression); + if (err != CHDERR_NONE) + report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); - /* close the drive */ - chd_close(chd); - chd = NULL; + // add the standard hard disk metadata + astring metadata; + metadata.format(HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sector_size); + err = chd.write_metadata(HARD_DISK_METADATA_TAG, 0, metadata); + if (err != CHDERR_NONE) + report_error(1, "Error adding hard disk metadata: %s", chd_file::error_string(err)); - /* apply fixes */ - if (fixes > 0) + // write the ident if present + if (identdata.count() > 0) { - /* mark the CHD writeable */ - header.flags |= CHDFLAGS_IS_WRITEABLE; - err = chd_set_header(inputfile, &header); - if (err != CHDERR_NONE) - fprintf(stderr, "Error writing new header: %s\n", chd_error_string(err)); - header.flags &= ~CHDFLAGS_IS_WRITEABLE; - writeable = TRUE; - - /* open the file */ - err = chd_open(inputfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } - - /* write new metadata */ - err = chd_set_metadata(chd, AV_LD_METADATA_TAG, 0, vbidata, header.totalhunks * VBI_PACKED_BYTES, CHD_MDFLAGS_CHECKSUM); + err = chd.write_metadata(HARD_DISK_IDENT_METADATA_TAG, 0, identdata); if (err != CHDERR_NONE) - { - fprintf(stderr, "Error adding AVLD metadata: %s\n", chd_error_string(err)); - goto cleanup; - } - else - printf("Updated metadata written successfully\n"); - - /* allow cleanup code to close the file and revert the header */ + report_error(1, "Error adding hard disk metadata: %s", chd_file::error_string(err)); } - } -cleanup: - /* clean up our mess */ - if (vbidata != NULL) - free(vbidata); - if (chd != NULL) - chd_close(chd); - if (writeable) - chd_set_header(inputfile, &header); - return (err != CHDERR_NONE); + // compress it generically + if (input_file != NULL) + compress_common(chd); + } + catch (...) + { + // delete the output file + astring *output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str != NULL) + osd_rmfile(*output_chd_str); + throw; + } } -/*------------------------------------------------- - do_info - dump the header information from - a drive image --------------------------------------------------*/ +//------------------------------------------------- +// do_create_cd - create a new compressed CD +// image from a raw file +//------------------------------------------------- -static int do_info(int argc, char *argv[], int param) +static void do_create_cd(parameters_t ¶ms) { - const char *inputfile; - chd_file *chd = NULL; - UINT8 metadata[256]; - chd_header header; - chd_error err; - int i, j; - - /* require 3 args total */ - if (argc != 3) - return usage(); + // catch errors so we can close & delete the output file + try + { + // process input file + chdcd_track_input_info track_info; + cdrom_toc toc = { 0 }; + astring *input_file_str = params.find(OPTION_INPUT); + if (input_file_str != NULL) + { + chd_error err = chdcd_parse_toc(*input_file_str, toc, track_info); + if (err != CHDERR_NONE) + report_error(1, "Error parsing input file (%s: %s\n", input_file_str->cstr(), chd_file::error_string(err)); + } + + // process output CHD + chd_file output_parent; + astring *output_chd_str = parse_output_chd_parameters(params, output_parent); + + // process hunk size + UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : CD_FRAMES_PER_HUNK * CD_FRAME_SIZE; + parse_hunk_size(params, CD_FRAME_SIZE, hunk_size); + + // process compression + chd_codec_type compression[4]; + memcpy(compression, s_default_cd_compression, sizeof(compression)); + parse_compression(params, compression); + + // process numprocessors + parse_numprocessors(params); + + // pad each track to a 4-frame boundry. cdrom.c will deal with this on the read side + UINT32 origtotalsectors = 0; + UINT32 totalsectors = 0; + for (int tracknum = 0; tracknum < toc.numtrks; tracknum++) + { + cdrom_track_info &trackinfo = toc.tracks[tracknum]; + int padded = (trackinfo.frames + CD_TRACK_PADDING - 1) / CD_TRACK_PADDING; + trackinfo.extraframes = padded * CD_TRACK_PADDING - trackinfo.frames; + origtotalsectors += trackinfo.frames; + totalsectors += trackinfo.frames + trackinfo.extraframes; + } - /* extract the data */ - inputfile = argv[2]; + // print some info + astring tempstr; + printf("Output CHD: %s\n", output_chd_str->cstr()); + if (output_parent.opened()) + printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); + printf("Input file: %s\n", input_file_str->cstr()); + printf("Input tracks: %d\n", toc.numtrks); + printf("Input length: %s\n", msf_string_from_frames(tempstr, origtotalsectors)); + printf("Compression: %s\n", compression_string(tempstr, compression)); + printf("Logical size: %s\n", big_int_string(tempstr, UINT64(totalsectors) * CD_FRAME_SIZE)); + + // create the new hard drive + chd_cd_compressor chd(toc, track_info); + chd_error err; + if (output_parent.opened()) + err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(CD_FRAME_SIZE), hunk_size, compression, output_parent); + else + err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(CD_FRAME_SIZE), hunk_size, CD_FRAME_SIZE, compression); + if (err != CHDERR_NONE) + report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); - /* print some info */ - printf("Input file: %s\n", inputfile); + // add the standard CD metadata; we do this even if we have a parent because it might be different + err = cdrom_write_metadata(&chd, &toc); + if (err != CHDERR_NONE) + report_error(1, "Error adding CD metadata: %s", chd_file::error_string(err)); - /* get the header */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } - header = *chd_get_header(chd); - - /* print the info */ - printf("Header Size: %d bytes\n", header.length); - printf("File Version: %d\n", header.version); - printf("Flags: %s, %s\n", - (header.flags & CHDFLAGS_HAS_PARENT) ? "HAS_PARENT" : "NO_PARENT", - (header.flags & CHDFLAGS_IS_WRITEABLE) ? "WRITEABLE" : "READ_ONLY"); - printf("Compression: %s\n", chd_get_codec_name(header.compression)); - printf("Hunk Size: %d bytes\n", header.hunkbytes); - printf("Total Hunks: %d\n", header.totalhunks); - printf("Logical size: %s bytes\n", big_int_string(header.logicalbytes)); - if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version <= 3) - printf("MD5: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", - header.md5[0], header.md5[1], header.md5[2], header.md5[3], - header.md5[4], header.md5[5], header.md5[6], header.md5[7], - header.md5[8], header.md5[9], header.md5[10], header.md5[11], - header.md5[12], header.md5[13], header.md5[14], header.md5[15]); - if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version >= 3) - printf("SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", - header.sha1[0], header.sha1[1], header.sha1[2], header.sha1[3], - header.sha1[4], header.sha1[5], header.sha1[6], header.sha1[7], - header.sha1[8], header.sha1[9], header.sha1[10], header.sha1[11], - header.sha1[12], header.sha1[13], header.sha1[14], header.sha1[15], - header.sha1[16], header.sha1[17], header.sha1[18], header.sha1[19]); - if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version >= 4) - { - printf("Raw SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", - header.rawsha1[0], header.rawsha1[1], header.rawsha1[2], header.rawsha1[3], - header.rawsha1[4], header.rawsha1[5], header.rawsha1[6], header.rawsha1[7], - header.rawsha1[8], header.rawsha1[9], header.rawsha1[10], header.rawsha1[11], - header.rawsha1[12], header.rawsha1[13], header.rawsha1[14], header.rawsha1[15], - header.rawsha1[16], header.rawsha1[17], header.rawsha1[18], header.rawsha1[19]); - } - if (header.flags & CHDFLAGS_HAS_PARENT) - { - printf("Parent MD5: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", - header.parentmd5[0], header.parentmd5[1], header.parentmd5[2], header.parentmd5[3], - header.parentmd5[4], header.parentmd5[5], header.parentmd5[6], header.parentmd5[7], - header.parentmd5[8], header.parentmd5[9], header.parentmd5[10], header.parentmd5[11], - header.parentmd5[12], header.parentmd5[13], header.parentmd5[14], header.parentmd5[15]); - if (header.version >= 3) - printf("Parent SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", - header.parentsha1[0], header.parentsha1[1], header.parentsha1[2], header.parentsha1[3], - header.parentsha1[4], header.parentsha1[5], header.parentsha1[6], header.parentsha1[7], - header.parentsha1[8], header.parentsha1[9], header.parentsha1[10], header.parentsha1[11], - header.parentsha1[12], header.parentsha1[13], header.parentsha1[14], header.parentsha1[15], - header.parentsha1[16], header.parentsha1[17], header.parentsha1[18], header.parentsha1[19]); + // compress it generically + compress_common(chd); } - - /* print out metadata */ - for (i = 0; ; i++) + catch (...) { - UINT32 metatag, metasize; - - /* get the indexed metadata item; stop when we hit an error */ - err = chd_get_metadata(chd, CHDMETATAG_WILDCARD, i, metadata, sizeof(metadata), &metasize, &metatag, NULL); - if (err != CHDERR_NONE) - break; - - /* print either a string representation or a hex representation of the tag */ - if (isprint((metatag >> 24) & 0xff) && isprint((metatag >> 16) & 0xff) && isprint((metatag >> 8) & 0xff) && isprint(metatag & 0xff)) - printf("Metadata: Tag='%c%c%c%c' Length=%d\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metasize); - else - printf("Metadata: Tag=%08x Length=%d\n", metatag, metasize); - printf(" "); - - /* print up to 60 characters of metadata */ - metasize = MIN(60, metasize); - for (j = 0; j < metasize; j++) - printf("%c", isprint(metadata[j]) ? metadata[j] : '.'); - printf("\n"); + // delete the output file + astring *output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str != NULL) + osd_rmfile(*output_chd_str); + throw; } - -cleanup: - /* close everything down */ - if (chd != NULL) - chd_close(chd); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - handle_custom_chomp - custom chomp a file --------------------------------------------------*/ +//------------------------------------------------- +// do_create_ld - create a new A/V file from an +// input AVI file and metadata +//------------------------------------------------- -#if ENABLE_CUSTOM_CHOMP -static chd_error handle_custom_chomp(const char *name, chd_file *chd, UINT32 *maxhunk) +static void do_create_ld(parameters_t ¶ms) { - const chd_header *header = chd_get_header(chd); - int sectors_per_hunk = (header->hunkbytes / IDE_SECTOR_SIZE); - chd_error err = CHDERR_INVALID_DATA; - UINT8 *temp = NULL; - - /* allocate memory to hold a hunk */ - temp = malloc(header->hunkbytes); - if (temp == NULL) - { - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - - /* check for midway */ - if (strcmp(name, "midway") == 0) + // catch errors so we can close & delete the output file + try { - UINT32 maxsector = 0; - UINT32 numparts; - chd_error err; - int i; - - /* read sector 0 */ - err = chd_read(chd, 0, temp); - if (err != CHDERR_NONE) + // process input file + avi_file *input_file = NULL; + astring *input_file_str = params.find(OPTION_INPUT); + if (input_file_str != NULL) { - fprintf(stderr, "Error reading hunk 0\n"); - goto cleanup; - } - - /* look for the signature */ - if (temp[0] != 0x54 || temp[1] != 0x52 || temp[2] != 0x41 || temp[3] != 0x50) - goto cleanup; - - /* determine the number of partitions */ - numparts = temp[4] | (temp[5] << 8) | (temp[6] << 16) | (temp[7] << 24); - printf("%d partitions\n", numparts); - - /* get the partition information for each one and track the maximum referenced sector */ - for (i = 0; i < numparts; i++) - { - UINT32 pstart = temp[i*12 + 8] | (temp[i*12 + 9] << 8) | (temp[i*12 + 10] << 16) | (temp[i*12 + 11] << 24); - UINT32 psize = temp[i*12 + 12] | (temp[i*12 + 13] << 8) | (temp[i*12 + 14] << 16) | (temp[i*12 + 15] << 24); - UINT32 pflags = temp[i*12 + 16] | (temp[i*12 + 17] << 8) | (temp[i*12 + 18] << 16) | (temp[i*12 + 19] << 24); - printf(" %2d. %7d - %7d (%X)\n", i, pstart, pstart + psize - 1, pflags); - if (i != 0 && pstart + psize > maxsector) - maxsector = pstart + psize; + avi_error avierr = avi_open(*input_file_str, &input_file); + if (avierr != AVIERR_NONE) + report_error(1, "Error opening AVI file (%s): %s\n", input_file_str->cstr(), avi_error_string(avierr)); } - - /* the maximum hunk is the one that contains the last sector */ - *maxhunk = (maxsector + sectors_per_hunk - 1) / sectors_per_hunk; - printf("Maximum hunk: %d\n", *maxhunk); - - /* warn if there will be no effect */ - if (*maxhunk >= header->totalhunks) + const avi_movie_info *aviinfo = avi_get_movie_info(input_file); + + // process output CHD + chd_file output_parent; + astring *output_chd_str = parse_output_chd_parameters(params, output_parent); + + // process input start/end + UINT64 input_start; + UINT64 input_end; + parse_input_start_end(params, aviinfo->video_numsamples, 0, 1, input_start, input_end); + + // determine parameters of the incoming video stream + avi_info info; + info.fps_times_1million = UINT64(aviinfo->video_timescale) * 1000000 / aviinfo->video_sampletime; + info.width = aviinfo->video_width; + info.height = aviinfo->video_height; + info.interlaced = ((info.fps_times_1million / 1000000) <= 30) && (info.height % 2 == 0) && (info.height > 288); + info.channels = aviinfo->audio_channels; + info.rate = aviinfo->audio_samplerate; + + // adjust for interlacing + if (info.interlaced) { - printf("Warning: chomp will have no effect\n"); - *maxhunk = header->totalhunks; + info.fps_times_1million *= 2; + info.height /= 2; + input_start *= 2; + input_end *= 2; } - } - /* check for atari */ - if (strcmp(name, "atari") == 0) - { - UINT32 sectors[4]; - UINT8 *data; - int i, maxdiff; + // determine the number of bytes per frame + info.max_samples_per_frame = (UINT64(info.rate) * 1000000 + info.fps_times_1million - 1) / info.fps_times_1million; + info.bytes_per_frame = avhuff_encoder::raw_data_size(info.width, info.height, info.channels, info.max_samples_per_frame); + + // process hunk size + UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : info.bytes_per_frame; + parse_hunk_size(params, info.bytes_per_frame, hunk_size); + + // process compression + chd_codec_type compression[4]; + memcpy(compression, s_default_ld_compression, sizeof(compression)); + parse_compression(params, compression); + + // process numprocessors + parse_numprocessors(params); + + // print some info + astring tempstr; + printf("Output CHD: %s\n", output_chd_str->cstr()); + if (output_parent.opened()) + printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); + printf("Input file: %s\n", input_file_str->cstr()); + if (input_start != 0 && input_end != aviinfo->video_numsamples) + printf("Input start: %s\n", big_int_string(tempstr, input_start)); + printf("Input length: %s (%02d:%02d:%02d)\n", big_int_string(tempstr, input_end - input_start), + UINT32((UINT64(input_end - input_start) * 1000000 / info.fps_times_1million / 60 / 60)), + UINT32(((UINT64(input_end - input_start) * 1000000 / info.fps_times_1million / 60) % 60)), + UINT32(((UINT64(input_end - input_start) * 1000000 / info.fps_times_1million) % 60))); + printf("Frame rate: %d.%06d\n", info.fps_times_1million / 1000000, info.fps_times_1million % 1000000); + printf("Frame size: %d x %d %s\n", info.width, info.height * (info.interlaced ? 2 : 1), info.interlaced ? "interlaced" : "non-interlaced"); + printf("Audio: %d channels at %d Hz\n", info.channels, info.rate); + printf("Compression: %s\n", compression_string(tempstr, compression)); + printf("Hunk size: %s\n", big_int_string(tempstr, hunk_size)); + printf("Logical size: %s\n", big_int_string(tempstr, UINT64(input_end - input_start) * hunk_size)); + + // create the new CHD + chd_avi_compressor chd(*input_file, info, input_start, input_end); chd_error err; + if (output_parent.opened()) + err = chd.create(*output_chd_str, UINT64(input_end - input_start) * hunk_size, hunk_size, compression, output_parent); + else + err = chd.create(*output_chd_str, UINT64(input_end - input_start) * hunk_size, hunk_size, info.bytes_per_frame, compression); + if (err != CHDERR_NONE) + report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); - /* read the second sector */ - err = chd_read(chd, 0x200 / header->hunkbytes, temp); + // write the core A/V metadata + astring metadata; + metadata.format(AV_METADATA_FORMAT, info.fps_times_1million / 1000000, info.fps_times_1million % 1000000, info.width, info.height, info.interlaced, info.channels, info.rate); + err = chd.write_metadata(AV_METADATA_TAG, 0, metadata); if (err != CHDERR_NONE) + report_error(1, "Error adding AV metadata: %s\n", chd_file::error_string(err)); + + // create the compressor and then run it generically + compress_common(chd); + + // write the final LD metadata + if (info.height == 524/2 || info.height == 624/2) { - fprintf(stderr, "Error reading sector 1\n"); - goto cleanup; - } - data = &temp[0x200 % header->hunkbytes]; - - /* look for the signature */ - if (data[0] != 0x0d || data[1] != 0xf0 || data[2] != 0xed || data[3] != 0xfe) - goto cleanup; - - /* loop over 4 partitions and compute the number of sectors in each */ - for (i = 0; i < 4; i++) - sectors[i] = data[i*4+0x40] | (data[i*4+0x41] << 8) | (data[i*4+0x42] << 16) | (data[i*4+0x43] << 24); - maxdiff = sectors[2] - sectors[1]; - if (sectors[3] - sectors[2] > maxdiff) - maxdiff = sectors[3] - sectors[2]; - if (sectors[0] != 8) - goto cleanup; - - /* the maximum hunk is the end of the fourth copy of the data */ - *maxhunk = (sectors[3] + maxdiff + sectors_per_hunk - 1) / sectors_per_hunk; - printf("Maximum hunk: %d\n", *maxhunk); - - /* warn if there will be no effect */ - if (*maxhunk >= header->totalhunks) - { - fprintf(stderr, "Warning: chomp will have no effect\n"); - *maxhunk = header->totalhunks; + err = chd.write_metadata(AV_LD_METADATA_TAG, 0, chd.ldframedata()); + if (err != CHDERR_NONE) + report_error(1, "Error adding AVLD metadata: %s\n", chd_file::error_string(err)); } } - - /* if we fall through, there was no error */ - err = CHDERR_NONE; - -cleanup: - if (temp != NULL) - free(temp); - if (err == CHDERR_INVALID_DATA) - fprintf(stderr, "Error: unable to identify file or compute chomping size.\n"); - return err; + catch (...) + { + // delete the output file + astring *output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str != NULL) + osd_rmfile(*output_chd_str); + throw; + } } -#endif -/*------------------------------------------------- - do_merge_update_chomp - merge a parent and its - child together (also works for update & chomp) --------------------------------------------------*/ +//------------------------------------------------- +// do_copy - create a new CHD with data from +// another CHD +//------------------------------------------------- -static int do_merge_update_chomp(int argc, char *argv[], int param) +static void do_copy(parameters_t ¶ms) { - const char *parentfile, *inputfile, *outputfile; - const chd_header *inputheader; - chd_file *parentchd = NULL; - chd_file *outputchd = NULL; - chd_file *inputchd = NULL; - UINT32 maxhunk = ~0; - chd_error err; - - /* require 4-5 args total */ - if (param == OPERATION_UPDATE && argc != 4) - return usage(); - if ((param == OPERATION_MERGE || param == OPERATION_CHOMP) && argc != 5) - return usage(); - - /* extract the data */ - if (param == OPERATION_MERGE) - { - parentfile = argv[2]; - inputfile = argv[3]; - outputfile = argv[4]; - } - else - { - parentfile = NULL; - inputfile = argv[2]; - outputfile = argv[3]; - if (param == OPERATION_CHOMP) - maxhunk = atoi(argv[4]); - } - - /* print some info */ - if (parentfile != NULL) - { - printf("Parent file: %s\n", parentfile); - printf("Diff file: %s\n", inputfile); - } - else - printf("Input file: %s\n", inputfile); - printf("Output file: %s\n", outputfile); - if (param == OPERATION_CHOMP) - printf("Maximum hunk: %d\n", maxhunk); + // catch errors so we can close & delete the output file + try + { + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd); + + // parse out input start/end + UINT64 input_start; + UINT64 input_end; + parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); + + // process output CHD + chd_file output_parent; + astring *output_chd_str = parse_output_chd_parameters(params, output_parent); + + // process hunk size + UINT32 hunk_size = input_chd.hunk_bytes(); + parse_hunk_size(params, 1, hunk_size); + if (hunk_size % input_chd.hunk_bytes() != 0 && input_chd.hunk_bytes() % hunk_size != 0) + report_error(1, "Hunk size is not an even multiple or divisor of input hunk size"); + + // process compression; we default to our current preferences using metadata to pick the type + chd_codec_type compression[4]; + { + dynamic_buffer metadata; + if (input_chd.read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) == CHDERR_NONE) + memcpy(compression, s_default_hd_compression, sizeof(compression)); + else if (input_chd.read_metadata(AV_METADATA_TAG, 0, metadata) == CHDERR_NONE) + memcpy(compression, s_default_ld_compression, sizeof(compression)); + else if (input_chd.read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE || + input_chd.read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE || + input_chd.read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) == CHDERR_NONE) + memcpy(compression, s_default_cd_compression, sizeof(compression)); + else + memcpy(compression, s_default_raw_compression, sizeof(compression)); + } + parse_compression(params, compression); + + // process numprocessors + parse_numprocessors(params); + + // print some info + astring tempstr; + printf("Output CHD: %s\n", output_chd_str->cstr()); + if (output_parent.opened()) + printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); + printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); + if (input_start != 0 || input_end != input_chd.logical_bytes()) + { + printf("Input start: %s\n", big_int_string(tempstr, input_start)); + printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); + } + printf("Compression: %s\n", compression_string(tempstr, compression)); + printf("Hunk size: %s\n", big_int_string(tempstr, hunk_size)); + printf("Logical size: %s\n", big_int_string(tempstr, input_end - input_start)); - /* open the parent CHD */ - if (parentfile != NULL) - { - err = chd_open(parentfile, CHD_OPEN_READ, NULL, &parentchd); + // create the new CHD + chd_chdfile_compressor chd(input_chd, input_start, input_end); + chd_error err; + if (output_parent.opened()) + err = chd.create(*output_chd_str, input_end - input_start, hunk_size, compression, output_parent); + else + err = chd.create(*output_chd_str, input_end - input_start, hunk_size, input_chd.unit_bytes(), compression); if (err != CHDERR_NONE) + report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); + + // clone all the metadata, upgrading where appropriate + dynamic_buffer metadata; + chd_metadata_tag metatag; + UINT8 metaflags; + UINT32 index = 0; + bool redo_cd = false; + for (err = input_chd.read_metadata(CHDMETATAG_WILDCARD, index++, metadata, metatag, metaflags); err == CHDERR_NONE; err = input_chd.read_metadata(CHDMETATAG_WILDCARD, index++, metadata, metatag, metaflags)) { - fprintf(stderr, "Error opening CHD file '%s': %s\n", parentfile, chd_error_string(err)); - goto cleanup; + // if this is an old CD-CHD tag, note that we want to re-do it + if (metatag == CDROM_OLD_METADATA_TAG || metatag == CDROM_TRACK_METADATA_TAG) + { + redo_cd = true; + continue; + } + + // otherwise, clone it + err = chd.write_metadata(metatag, CHDMETAINDEX_APPEND, metadata); + if (err != CHDERR_NONE) + report_error(1, "Error writing cloned metadata: %s", chd_file::error_string(err)); } + + // if we need to re-do the CD metadata, do it now + if (redo_cd) + { + cdrom_file *cdrom = cdrom_open(&input_chd); + if (cdrom == NULL) + report_error(1, "Error upgrading CD metadata"); + const cdrom_toc *toc = cdrom_get_toc(cdrom); + err = cdrom_write_metadata(&chd, toc); + if (err != CHDERR_NONE) + report_error(1, "Error writing upgraded CD metadata: %s", chd_file::error_string(err)); + } + + // compress it generically + compress_common(chd); } - - /* open the diff CHD */ - err = chd_open(inputfile, CHD_OPEN_READ, parentchd, &inputchd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } - inputheader = chd_get_header(inputchd); - -#if ENABLE_CUSTOM_CHOMP - /* if we're chomping with a auto parameter, now is the time to figure it out */ - if (param == OPERATION_CHOMP && maxhunk == 0) - if (handle_custom_chomp(argv[4], inputchd, &maxhunk) != CHDERR_NONE) - return 1; -#endif - - /* create the new merged CHD */ - err = chd_create(outputfile, inputheader->logicalbytes, inputheader->hunkbytes, inputheader->compression, NULL); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* open the new CHD */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &outputchd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* clone the metadata from the input file (which should have inherited from the parent) */ - err = chdman_clone_metadata(inputchd, outputchd); - if (err != CHDERR_NONE) + catch (...) { - fprintf(stderr, "Error cloning metadata: %s\n", chd_error_string(err)); - goto cleanup; + // delete the output file + astring *output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str != NULL) + osd_rmfile(*output_chd_str); + throw; } - - /* do the compression; our interface will route reads for us */ - err = chdman_compress_chd(outputchd, inputchd, (param == OPERATION_CHOMP) ? (maxhunk + 1) : 0); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); - -cleanup: - /* close everything down */ - if (outputchd != NULL) - chd_close(outputchd); - if (inputchd != NULL) - chd_close(inputchd); - if (parentchd != NULL) - chd_close(parentchd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_diff - generate a difference between two - CHD files --------------------------------------------------*/ +//------------------------------------------------- +// do_extract_raw - extract a raw file from a +// CHD image +//------------------------------------------------- -static int do_diff(int argc, char *argv[], int param) +static void do_extract_raw(parameters_t ¶ms) { - const char *parentfile = NULL, *inputfile = NULL, *outputfile = NULL; - chd_file *parentchd = NULL; - chd_file *outputchd = NULL; - chd_file *inputchd = NULL; - chd_error err; - - /* require 5 args total */ - if (argc != 5) - return usage(); - - /* extract the data */ - parentfile = argv[2]; - inputfile = argv[3]; - outputfile = argv[4]; - - /* print some info */ - printf("Parent file: %s\n", parentfile); - printf("Input file: %s\n", inputfile); - printf("Diff file: %s\n", outputfile); + // catch errors so we can close & delete the output file + astring *output_file_str = params.find(OPTION_OUTPUT); + core_file *output_file = NULL; + try + { + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd); + + // parse out input start/end + UINT64 input_start; + UINT64 input_end; + parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); + + // process output file + if (output_file_str != NULL) + { + file_error filerr = core_fopen(*output_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &output_file); + if (filerr != FILERR_NONE) + report_error(1, "Unable to open file (%s)", output_file_str->cstr()); + } - /* open the soon-to-be-parent CHD */ - err = chd_open(parentfile, CHD_OPEN_READ, NULL, &parentchd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", parentfile, chd_error_string(err)); - goto cleanup; - } + // print some info + astring tempstr; + printf("Output File: %s\n", output_file_str->cstr()); + printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); + if (input_start != 0 || input_end != input_chd.logical_bytes()) + { + printf("Input start: %s\n", big_int_string(tempstr, input_start)); + printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); + } - /* open the input CHD */ - err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); - goto cleanup; - } + // copy all data + dynamic_buffer buffer((TEMP_BUFFER_SIZE / input_chd.hunk_bytes()) * input_chd.hunk_bytes()); + for (UINT64 offset = input_start; offset < input_end; ) + { + progress(false, "Extracting, %.1f%% complete... \r", 100.0 * double(offset - input_start) / double(input_end - input_start)); - /* create the new CHD as a diff against the parent */ - err = chd_create(outputfile, 0, 0, chd_get_header(parentchd)->compression, parentchd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); - goto cleanup; + // determine how much to read + UINT32 bytes_to_read = MIN(buffer.count(), input_end - offset); + chd_error err = input_chd.read_bytes(offset, buffer, bytes_to_read); + if (err != CHDERR_NONE) + report_error(1, "Error reading CHD file (%s): %s", params.find(OPTION_INPUT)->cstr(), chd_file::error_string(err)); + + // write to the output + UINT32 count = core_fwrite(output_file, buffer, bytes_to_read); + if (count != bytes_to_read) + report_error(1, "Error writing to file; check disk space (%s)", output_file_str->cstr()); + + // advance + offset += bytes_to_read; + } + + // finish up + core_fclose(output_file); + printf("Extraction complete \n"); } - - /* open the new CHD */ - err = chd_open(outputfile, CHD_OPEN_READWRITE, parentchd, &outputchd); - if (err != CHDERR_NONE) + catch (...) { - fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); - goto cleanup; + // delete the output file + if (output_file != NULL) + core_fclose(output_file); + osd_rmfile(*output_file_str); + throw; } - - /* do the compression; our interface will route reads for us */ - err = chdman_compress_chd(outputchd, inputchd, 0); - if (err != CHDERR_NONE) - fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); - -cleanup: - /* close everything down */ - if (outputchd != NULL) - chd_close(outputchd); - if (inputchd != NULL) - chd_close(inputchd); - if (parentchd != NULL) - chd_close(parentchd); - if (err != CHDERR_NONE) - osd_rmfile(outputfile); - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_setchs - change the CHS values on a hard - disk image --------------------------------------------------*/ +//------------------------------------------------- +// do_extract_cd - extract a CD file from a +// CHD image +//------------------------------------------------- -static int do_setchs(int argc, char *argv[], int param) +static void do_extract_cd(parameters_t ¶ms) { - int oldcyls, oldhds, oldsecs, oldsecsize; - UINT8 was_readonly = FALSE; - UINT64 old_logicalbytes; - const char *inoutfile; - chd_file *chd = NULL; - int cyls, hds, secs; - char metadata[256]; - chd_header header; - chd_error err; - - /* require 6 args total */ - if (argc != 6) - return usage(); - - /* extract the data */ - inoutfile = argv[2]; - cyls = atoi(argv[3]); - hds = atoi(argv[4]); - secs = atoi(argv[5]); - - /* print some info */ - printf("Input file: %s\n", inoutfile); - printf("Cylinders: %d\n", cyls); - printf("Heads: %d\n", hds); - printf("Sectors: %d\n", secs); - - /* open the file read-only and get the header */ - err = chd_open(inoutfile, CHD_OPEN_READ, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s' read-only: %s\n", inoutfile, chd_error_string(err)); - goto cleanup; - } - header = *chd_get_header(chd); - chd_close(chd); - chd = NULL; - - /* if the drive is not writeable, note that, and make it so */ - if (!(header.flags & CHDFLAGS_IS_WRITEABLE)) - { - was_readonly = TRUE; - header.flags |= CHDFLAGS_IS_WRITEABLE; - - /* write the new header */ - err = chd_set_header(inoutfile, &header); - if (err != CHDERR_NONE) + // catch errors so we can close & delete the output file + astring *output_file_str = params.find(OPTION_OUTPUT); + astring *output_bin_file_str = params.find(OPTION_OUTPUT_BIN); + core_file *output_bin_file = NULL; + core_file *output_toc_file = NULL; + try + { + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd); + + // further process input file + cdrom_file *cdrom = cdrom_open(&input_chd); + if (cdrom == NULL) + report_error(1, "Unable to recognize CHD file as a CD"); + const cdrom_toc *toc = cdrom_get_toc(cdrom); + + // process output file + if (output_file_str != NULL) { - fprintf(stderr, "Error making CHD file writeable: %s\n", chd_error_string(err)); - goto cleanup; + file_error filerr = core_fopen(*output_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_NO_BOM, &output_toc_file); + if (filerr != FILERR_NONE) + report_error(1, "Unable to open file (%s)", output_file_str->cstr()); + } + bool cuemode = (output_file_str->find(".cue") != -1); + + // process output BIN file + astring default_name(*output_file_str); + int chop = default_name.rchr(0, '.'); + if (chop != -1) + default_name.substr(0, chop); + default_name.cat(".bin"); + if (output_bin_file_str == NULL) + output_bin_file_str = &default_name; + { + file_error filerr = core_fopen(*output_bin_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &output_bin_file); + if (filerr != FILERR_NONE) + report_error(1, "Unable to open file (%s)", output_bin_file_str->cstr()); } - } - - /* open the file read/write */ - err = chd_open(inoutfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s' read/write: %s\n", inoutfile, chd_error_string(err)); - goto cleanup; - } - - /* get the hard disk metadata */ - err = chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); - if (err != CHDERR_NONE || sscanf(metadata, HARD_DISK_METADATA_FORMAT, &oldcyls, &oldhds, &oldsecs, &oldsecsize) != 4) - { - fprintf(stderr, "CHD file '%s' is not a hard disk!\n", inoutfile); - err = CHDERR_INVALID_FILE; - goto cleanup; - } - - /* write our own */ - sprintf(metadata, HARD_DISK_METADATA_FORMAT, cyls, hds, secs, oldsecsize); - err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error writing new metadata to CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - - /* get the header and compute the new logical size */ - header = *chd_get_header(chd); - old_logicalbytes = header.logicalbytes; - header.logicalbytes = (UINT64)cyls * (UINT64)hds * (UINT64)secs * (UINT64)oldsecsize; - /* close the file */ - chd_close(chd); - chd = NULL; + // print some info + astring tempstr; + printf("Output TOC: %s\n", output_file_str->cstr()); + printf("Output Data: %s\n", output_bin_file_str->cstr()); + printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); + + // determine total frames + UINT64 total_bytes = 0; + for (int tracknum = 0; tracknum < toc->numtrks; tracknum++) + total_bytes += toc->tracks[tracknum].frames * (toc->tracks[tracknum].datasize + toc->tracks[tracknum].subsize); + + // iterate over tracks and copy all data + UINT64 outputoffs = 0; + UINT32 discoffs = 0; + dynamic_buffer buffer; + for (int tracknum = 0; tracknum < toc->numtrks; tracknum++) + { + // output the metadata about the track to the TOC file + const cdrom_track_info &trackinfo = toc->tracks[tracknum]; + output_track_metadata(cuemode, output_toc_file, tracknum, trackinfo, *output_bin_file_str, discoffs, outputoffs); + + // resize the buffer for the track + UINT32 output_frame_size = trackinfo.datasize + ((trackinfo.subtype != CD_SUB_NONE) ? trackinfo.subsize : 0); + buffer.resize((TEMP_BUFFER_SIZE / output_frame_size) * output_frame_size); + + // now read and output the actual data + UINT32 bufferoffs = 0; + for (int frame = 0; frame < trackinfo.frames; frame++) + { + progress(false, "Extracting, %.1f%% complete... \r", 100.0 * double(outputoffs) / double(total_bytes)); - /* restore the read-only state */ - if (was_readonly) - header.flags &= ~CHDFLAGS_IS_WRITEABLE; + // read the data + cdrom_read_data(cdrom, cdrom_get_track_start(cdrom, tracknum) + frame, &buffer[bufferoffs], trackinfo.trktype); - /* set the new logical size */ - if (header.logicalbytes != old_logicalbytes || was_readonly) - { - err = chd_set_header(inoutfile, &header); - if (err != CHDERR_NONE) - fprintf(stderr, "Error writing new header to CHD file: %s\n", chd_error_string(err)); - } + // for CDRWin, audio tracks must be reversed + if (cuemode && (trackinfo.trktype == CD_TRACK_AUDIO)) + for (int swapindex = 0; swapindex < trackinfo.datasize; swapindex += 2) + { + UINT8 swaptemp = buffer[bufferoffs + swapindex]; + buffer[bufferoffs + swapindex] = buffer[bufferoffs + swapindex + 1]; + buffer[bufferoffs + swapindex + 1] = swaptemp; + } + bufferoffs += trackinfo.datasize; + discoffs++; - /* print a warning if the size is different */ - if (header.logicalbytes < old_logicalbytes) - fprintf(stderr, "WARNING: new size is smaller; run chdman -update to reclaim empty space\n"); - else if (header.logicalbytes > old_logicalbytes) - fprintf(stderr, "WARNING: new size is larger; run chdman -update to account for new empty space\n"); + // read the subcode data + if (trackinfo.subtype != CD_SUB_NONE) + { + cdrom_read_subcode(cdrom, cdrom_get_track_start(cdrom, tracknum) + frame, &buffer[bufferoffs]); + bufferoffs += trackinfo.subsize; + } -cleanup: - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE && was_readonly) - { - header.flags &= ~CHDFLAGS_IS_WRITEABLE; - chd_set_header(inoutfile, &header); + // write it out if we need to + if (bufferoffs == buffer.count() || frame == trackinfo.frames - 1) + { + core_fseek(output_bin_file, outputoffs, SEEK_SET); + UINT32 byteswritten = core_fwrite(output_bin_file, buffer, bufferoffs); + if (byteswritten != bufferoffs) + report_error(1, "Error writing frame %d to file (%s): %s\n", frame, output_file_str->cstr(), chd_file::error_string(CHDERR_WRITE_ERROR)); + outputoffs += bufferoffs; + bufferoffs = 0; + } + } + } + + // finish up + core_fclose(output_bin_file); + core_fclose(output_toc_file); + printf("Extraction complete \n"); + } + catch (...) + { + // delete the output files + if (output_bin_file != NULL) + core_fclose(output_bin_file); + if (output_toc_file != NULL) + core_fclose(output_toc_file); + osd_rmfile(*output_bin_file_str); + osd_rmfile(*output_file_str); + throw; } - return (err != CHDERR_NONE); } -/*------------------------------------------------- - do_addmeta - add metadata to a CHD from a - file --------------------------------------------------*/ +//------------------------------------------------- +// do_extract_ld - extract an AVI file from a +// CHD image +//------------------------------------------------- -static int do_addmeta(int argc, char *argv[], int param) +static void do_extract_ld(parameters_t ¶ms) { - const char *inoutfile, *srcfile, *tagstring; - UINT8 was_readonly = FALSE; - UINT8 *metadata = NULL; - chd_file *chd = NULL; - chd_header header; - file_error filerr; - UINT32 metalength; - UINT32 metaindex; - UINT32 metatag; - chd_error err; - - /* require 5 or 6 args total */ - if (argc != 5 && argc != 6) - return usage(); - - /* extract the data */ - inoutfile = argv[2]; - tagstring = argv[3]; - if (argc == 5) - { - metaindex = 0; - srcfile = argv[4]; - } - else - { - metaindex = atoi(argv[4]); - srcfile = argv[5]; - } - - /* verify the tag */ - if (strlen(tagstring) > 4) - { - fprintf(stderr, "Invalid tag '%s'; must be 4 characters or less\n", tagstring); - return CHDERR_INVALID_PARAMETER; - } - metatag = ((tagstring[0] == 0) ? ' ' : tagstring[0]) << 24; - metatag |= ((tagstring[1] == 0) ? ' ' : tagstring[1]) << 16; - metatag |= ((tagstring[2] == 0) ? ' ' : tagstring[2]) << 8; - metatag |= ((tagstring[3] == 0) ? ' ' : tagstring[3]) << 0; - - /* print some info */ - printf("Input file: %s\n", inoutfile); - printf("Tag: '%c%c%c%c'\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff); - printf("Index: %d\n", metaindex); - printf("Source file: %s\n", srcfile); - - /* open the file read-only and get the header */ - err = chd_open(inoutfile, CHD_OPEN_READ, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s' read-only: %s\n", inoutfile, chd_error_string(err)); - goto cleanup; - } - header = *chd_get_header(chd); - chd_close(chd); - chd = NULL; - - /* if the drive is not writeable, note that, and make it so */ - if (!(header.flags & CHDFLAGS_IS_WRITEABLE)) - { - was_readonly = TRUE; - header.flags |= CHDFLAGS_IS_WRITEABLE; - - /* write the new header */ - err = chd_set_header(inoutfile, &header); + // catch errors so we can close & delete the output file + astring *output_file_str = params.find(OPTION_OUTPUT); + avi_file *output_file = NULL; + try + { + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd); + + // read core metadata + astring metadata; + chd_error err = input_chd.read_metadata(AV_METADATA_TAG, 0, metadata); if (err != CHDERR_NONE) + report_error(1, "Unable to find A/V metadata in the input CHD"); + + // parse the metadata + UINT32 fps_times_1million; + UINT32 max_samples_per_frame; + UINT32 frame_bytes; + int width; + int height; + int interlaced; + int channels; + int rate; { - fprintf(stderr, "Error making CHD file writeable: %s\n", chd_error_string(err)); - goto cleanup; + int fps; + int fpsfrac; + if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) + report_error(1, "Improperly formatted A/V metadata found"); + fps_times_1million = fps * 1000000 + fpsfrac; } - } + int interlace_factor = interlaced ? 2 : 1; + + // determine key parameters and validate + max_samples_per_frame = (UINT64(rate) * 1000000 + fps_times_1million - 1) / fps_times_1million; + frame_bytes = avhuff_encoder::raw_data_size(width, height, channels, max_samples_per_frame); + if (frame_bytes != input_chd.hunk_bytes()) + report_error(1, "Frame size does not match hunk size for this CHD"); + + // parse out input start/end + UINT64 input_start; + UINT64 input_end; + parse_input_start_end(params, input_chd.hunk_count() / interlace_factor, 0, 1, input_start, input_end); + input_start *= interlace_factor; + input_end *= interlace_factor; + + // build up the movie info + avi_movie_info info; + info.video_format = FORMAT_YUY2; + info.video_timescale = fps_times_1million / interlace_factor; + info.video_sampletime = 1000000; + info.video_width = width; + info.video_height = height * interlace_factor; + info.video_depth = 16; + info.audio_format = 0; + info.audio_timescale = rate; + info.audio_sampletime = 1; + info.audio_channels = channels; + info.audio_samplebits = 16; + info.audio_samplerate = rate; - /* open the file read/write */ - err = chd_open(inoutfile, CHD_OPEN_READWRITE, NULL, &chd); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error opening CHD file '%s' read/write: %s\n", inoutfile, chd_error_string(err)); - goto cleanup; - } + // process output file + if (output_file_str != NULL) + { + avi_error avierr = avi_create(*output_file_str, &info, &output_file); + if (avierr != AVIERR_NONE) + report_error(1, "Unable to open file (%s)", output_file_str->cstr()); + } - /* attempt to open the source file */ - filerr = core_fload(srcfile, (void **)&metadata, &metalength); - if (filerr != FILERR_NONE) - { - fprintf(stderr, "Error opening source file '%s'\n", srcfile); - err = CHDERR_FILE_NOT_FOUND; - goto cleanup; - } + // print some info + astring tempstr; + printf("Output File: %s\n", output_file_str->cstr()); + printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); + if (input_start != 0 || input_end != input_chd.hunk_count()) + { + printf("Input start: %s\n", big_int_string(tempstr, input_start)); + printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); + } - /* if it's text, strip any trailing Ctrl-Z and CR/LF and add a trailing NULL */ - if (param) - { - metadata = (UINT8 *)realloc(metadata, metalength + 1); - if (metadata == NULL) + // create the codec configuration + avhuff_decompress_config avconfig; + dynamic_array<INT16> audio_data[16]; + UINT32 actsamples; + avconfig.maxsamples = max_samples_per_frame; + avconfig.actsamples = &actsamples; + for (int chnum = 0; chnum < ARRAY_LENGTH(audio_data); chnum++) { - fprintf(stderr, "Out of memory preparing metadata\n"); - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; + audio_data[chnum].resize(max_samples_per_frame); + avconfig.audio[chnum] = audio_data[chnum]; } - metadata[metalength++] = 0; - while (metalength > 0 && (metadata[metalength - 2] == 0x0a || metadata[metalength - 2] == 0x0d || metadata[metalength - 2] == 0x1a)) - metadata[--metalength] = 0; - } + + // iterate over frames + bitmap_yuy16 fullbitmap(width, height * interlace_factor); + for (int framenum = input_start; framenum < input_end; framenum++) + { + progress(framenum == 0, "Extracting, %.1f%% complete... \r", 100.0 * double(framenum - input_start) / double(input_end - input_start)); + + // set up the fake bitmap for this frame + avconfig.video.wrap(&fullbitmap.pix(framenum % interlace_factor), fullbitmap.width(), fullbitmap.height() / interlace_factor, fullbitmap.rowpixels() * interlace_factor); + input_chd.codec_configure(CHD_CODEC_AVHUFF, AVHUFF_CODEC_DECOMPRESS_CONFIG, &avconfig); + + // read the hunk into the buffers + chd_error err = input_chd.read_hunk(framenum, NULL); + if (err != CHDERR_NONE) + report_error(1, "Error reading hunk %d from CHD file (%s): %s\n", framenum, params.find(OPTION_INPUT)->cstr(), chd_file::error_string(err)); - /* write the new metadata */ - err = chd_set_metadata(chd, metatag, metaindex, metadata, metalength, CHD_MDFLAGS_CHECKSUM); - if (err != CHDERR_NONE) - { - fprintf(stderr, "Error writing new metadata to CHD file: %s\n", chd_error_string(err)); - goto cleanup; - } - header = *chd_get_header(chd); + // write audio + for (int chnum = 0; chnum < channels; chnum++) + { + avi_error avierr = avi_append_sound_samples(output_file, chnum, avconfig.audio[chnum], actsamples, 0); + if (avierr != AVIERR_NONE) + report_error(1, "Error writing samples for hunk %d to file (%s): %s\n", framenum, output_file_str->cstr(), avi_error_string(avierr)); + } - /* close the file */ - chd_close(chd); - chd = NULL; + // write video + if (framenum % interlace_factor == interlace_factor - 1) + { + avi_error avierr = avi_append_video_frame(output_file, fullbitmap); + if (avierr != AVIERR_NONE) + report_error(1, "Error writing video for hunk %d to file (%s): %s\n", framenum, output_file_str->cstr(), avi_error_string(avierr)); + } + } - /* restore the read-only state */ - if (was_readonly) - { - header.flags &= ~CHDFLAGS_IS_WRITEABLE; - err = chd_set_header(inoutfile, &header); - if (err != CHDERR_NONE) - fprintf(stderr, "Error writing new header to CHD file: %s\n", chd_error_string(err)); + // close and return + avi_close(output_file); + printf("Extraction complete \n"); } - if (err == CHDERR_NONE) - printf("Metadata added successfully as %s\n", param ? "text" : "binary"); - -cleanup: - if (metadata != NULL) - osd_free(metadata); - if (chd != NULL) - chd_close(chd); - if (err != CHDERR_NONE && was_readonly) + catch (...) { - header.flags &= ~CHDFLAGS_IS_WRITEABLE; - chd_set_header(inoutfile, &header); + // delete the output file + if (output_file != NULL) + avi_close(output_file); + osd_rmfile(*output_file_str); + throw; } - return (err != CHDERR_NONE); } -/*------------------------------------------------- - chdman_compress_file - compress a regular - file via the compression interfaces --------------------------------------------------*/ +//------------------------------------------------- +// do_add_metadata - add metadata to a CHD from a +// file +//------------------------------------------------- -static chd_error chdman_compress_file(chd_file *chd, const char *rawfile, UINT32 offset) +static void do_add_metadata(parameters_t ¶ms) { - core_file *sourcefile; - const chd_header *header; - UINT64 sourceoffset = 0; - UINT8 *cache = NULL; - double ratio = 1.0; - file_error filerr; - chd_error err; - int hunknum; - - /* open the raw file */ - filerr = core_fopen(rawfile, OPEN_FLAG_READ, &sourcefile); - if (filerr != FILERR_NONE) - { - err = CHDERR_FILE_NOT_FOUND; - goto cleanup; - } - - /* get the header */ - header = chd_get_header(chd); - cache = (UINT8 *)malloc(header->hunkbytes); - if (cache == NULL) - { - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - - /* begin compressing */ - err = chd_compress_begin(chd); - if (err != CHDERR_NONE) - goto cleanup; - - /* loop over source hunks until we run out */ - for (hunknum = 0; hunknum < header->totalhunks; hunknum++) - { - UINT32 bytesread; - - /* progress */ - progress(hunknum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", hunknum, header->totalhunks, (int)(100.0 * ratio)); - - /* read the data */ - core_fseek(sourcefile, sourceoffset + offset, SEEK_SET); - bytesread = core_fread(sourcefile, cache, header->hunkbytes); - if (bytesread < header->hunkbytes) - memset(&cache[bytesread], 0, header->hunkbytes - bytesread); - - /* append the data */ - err = chd_compress_hunk(chd, cache, &ratio); - if (err != CHDERR_NONE) - goto cleanup; - - /* prepare for the next hunk */ - sourceoffset += header->hunkbytes; - } + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd, true); + + // process tag + chd_metadata_tag tag = CHD_MAKE_TAG('?','?','?','?'); + astring *tag_str = params.find(OPTION_TAG); + if (tag_str != NULL) + { + tag_str->cat(" "); + tag = CHD_MAKE_TAG((*tag_str)[0], (*tag_str)[1], (*tag_str)[2], (*tag_str)[3]); + } + + // process index + UINT32 index = 0; + astring *index_str = params.find(OPTION_INDEX); + if (index_str != NULL) + index = atoi(*index_str); + + // process text input + astring *text_str = params.find(OPTION_VALUE_TEXT); + astring text; + if (text_str != NULL) + { + text = *text_str; + if (text[0] == '"' && text[text.len() - 1] == '"') + text.substr(1, text.len() - 2); + } + + // process file input + astring *file_str = params.find(OPTION_VALUE_FILE); + dynamic_buffer file; + if (file_str != NULL) + { + file_error filerr = core_fload(*file_str, file); + if (filerr != FILERR_NONE) + report_error(1, "Error reading metadata file (%s)", file_str->cstr()); + } + + // make sure we have one or the other + if (text_str == NULL && file_str == NULL) + report_error(1, "Error: missing either --valuetext/-vt or --valuefile/-vf parameters"); + if (text_str != NULL && file_str != NULL) + report_error(1, "Error: both --valuetext/-vt or --valuefile/-vf parameters specified; only one permitted"); + + // process no checksum + UINT8 flags = CHD_MDFLAGS_CHECKSUM; + if (params.find(OPTION_NO_CHECKSUM) != NULL) + flags &= ~CHD_MDFLAGS_CHECKSUM; + + // print some info + astring tempstr; + printf("Input file: %s\n", params.find(OPTION_INPUT)->cstr()); + printf("Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); + printf("Index: %d\n", index); + if (text_str != NULL) + printf("Text: %s\n", text.cstr()); + else + printf("Data: %s (%d bytes)\n", file_str->cstr(), file.count()); - /* finish compression */ - err = chd_compress_finish(chd, TRUE); + // write the metadata + chd_error err; + if (text_str != NULL) + err = input_chd.write_metadata(tag, index, text, flags); + else + err = input_chd.write_metadata(tag, index, file, flags); if (err != CHDERR_NONE) - goto cleanup; - - /* final progress update */ - progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); - -cleanup: - if (sourcefile != NULL) - core_fclose(sourcefile); - if (cache != NULL) - free(cache); - return err; + report_error(1, "Error adding metadata: %s", chd_file::error_string(err)); + else + printf("Metadata added\n"); } -/*------------------------------------------------- - chdman_compress_chd - (re)compress a CHD file - via the compression interfaces --------------------------------------------------*/ +//------------------------------------------------- +// do_del_metadata - remove metadata from a CHD +//------------------------------------------------- -static chd_error chdman_compress_chd(chd_file *chd, chd_file *source, UINT32 totalhunks) +static void do_del_metadata(parameters_t ¶ms) { - const chd_header *source_header; - const chd_header *header; - UINT8 *source_cache = NULL; - UINT64 source_offset = 0; - UINT32 source_bytes = 0; - UINT8 *cache = NULL; - double ratio = 1.0; - chd_error err, verifyerr; - int hunknum; - - /* get the header */ - header = chd_get_header(chd); - cache = (UINT8 *)malloc(header->hunkbytes); - if (cache == NULL) - { - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - - /* get the source CHD header */ - source_header = chd_get_header(source); - source_cache = (UINT8 *)malloc(source_header->hunkbytes); - if (source_cache == NULL) - { - err = CHDERR_OUT_OF_MEMORY; - goto cleanup; - } - - /* begin compressing */ - err = chd_compress_begin(chd); - if (err != CHDERR_NONE) - goto cleanup; - - /* also begin verifying the source driver */ - verifyerr = chd_verify_begin(source); + // parse out input files + chd_file input_parent_chd; + chd_file input_chd; + parse_input_chd_parameters(params, input_chd, input_parent_chd, true); + + // process tag + chd_metadata_tag tag = CHD_MAKE_TAG('?','?','?','?'); + astring *tag_str = params.find(OPTION_TAG); + if (tag_str != NULL) + { + tag_str->cat(" "); + tag = CHD_MAKE_TAG((*tag_str)[0], (*tag_str)[1], (*tag_str)[2], (*tag_str)[3]); + } + + // process index + UINT32 index = 0; + astring *index_str = params.find(OPTION_INDEX); + if (index_str != NULL) + index = atoi(*index_str); + + // print some info + astring tempstr; + printf("Input file: %s\n", params.find(OPTION_INPUT)->cstr()); + printf("Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); + printf("Index: %d\n", index); + + // write the metadata + chd_error err = input_chd.delete_metadata(tag, index); + if (err != CHDERR_NONE) + report_error(1, "Error removing metadata: %s", chd_file::error_string(err)); + else + printf("Metadata removed\n"); +} - /* a zero count means the natural number */ - if (totalhunks == 0) - totalhunks = source_header->totalhunks; - /* loop over source hunks until we run out */ - for (hunknum = 0; hunknum < totalhunks; hunknum++) - { - UINT32 bytesremaining = header->hunkbytes; - UINT8 *dest = cache; +//------------------------------------------------- +// main - entry point +//------------------------------------------------- - /* progress */ - progress(hunknum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", hunknum, totalhunks, (int)(100.0 * ratio)); +int CLIB_DECL main(int argc, char *argv[]) +{ + // print the header + extern const char build_version[]; + printf("chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version); - /* read the data */ - while (bytesremaining > 0) + // handle help specially + if (argc < 2) + return print_help(argv[0]); + int argnum = 1; + const char *command = argv[argnum++]; + bool help = (strcmp(command, COMMAND_HELP) == 0); + if (help) + { + if (argc <= 2) + return print_help(argv[0]); + command = argv[argnum++]; + } + + // iterate over commands to find our match + for (int cmdnum = 0; cmdnum < ARRAY_LENGTH(s_commands); cmdnum++) + if (strcmp(command, s_commands[cmdnum].name) == 0) { - /* if we have data in the buffer, copy it */ - if (source_bytes > 0) + const command_description &desc = s_commands[cmdnum]; + + // print help if that was requested + if (help) + return print_help(argv[0], desc); + + // otherwise, verify the parameters + tagmap_t<astring *> parameters; + while (argnum < argc) { - UINT32 bytestocopy = MIN(bytesremaining, source_bytes); - memcpy(dest, &source_cache[source_header->hunkbytes - source_bytes], bytestocopy); - dest += bytestocopy; - source_bytes -= bytestocopy; - bytesremaining -= bytestocopy; - } - - /* otherwise, read in another hunk of the source */ - else - { - /* verify the next hunk */ - if (verifyerr == CHDERR_NONE) - err = chd_verify_hunk(source); - - /* then read it (should be the same) */ - err = chd_read(source, source_offset / source_header->hunkbytes, source_cache); - if (err != CHDERR_NONE) - memset(source_cache, 0, source_header->hunkbytes); - source_bytes = source_header->hunkbytes; - source_offset += source_bytes; - } - } - - /* append the data */ - err = chd_compress_hunk(chd, cache, &ratio); - if (err != CHDERR_NONE) - goto cleanup; - } - - /* if we read all the source data, verify the checksums */ - if (verifyerr == CHDERR_NONE && source_offset >= source_header->logicalbytes) - { - static const UINT8 empty_checksum[CHD_SHA1_BYTES] = { 0 }; - chd_verify_result verify; - int i; - - /* get the final values */ - err = chd_verify_finish(source, &verify); + // should be an option name + const char *arg = argv[argnum++]; + if (arg[0] != '-') + return print_help(argv[0], desc, "Expected option, not parameter"); + + // iterate over valid options + int valid; + for (valid = 0; valid < ARRAY_LENGTH(desc.valid_options); valid++) + { + // reduce to the option name + const char *validname = desc.valid_options[valid]; + if (validname == NULL) + break; + if (*validname == REQUIRED[0]) + validname++; + + // find the matching option description + int optnum; + for (optnum = 0; optnum < ARRAY_LENGTH(s_options); optnum++) + if (strcmp(s_options[optnum].name, validname) == 0) + break; + assert(optnum != ARRAY_LENGTH(s_options)); + + // do we match? + const option_description &odesc = s_options[optnum]; + if ((arg[1] == '-' && strcmp(odesc.name, &arg[2]) == 0) || + (arg[1] != '-' && odesc.shortname != NULL && strcmp(odesc.shortname, &arg[1]) == 0)) + { + // if we need a parameter, consume it + const char *param = ""; + if (odesc.parameter) + { + if (argnum >= argc || argv[argnum][0] == '-') + return print_help(argv[0], desc, "Option is missing parameter"); + param = argv[argnum++]; + } + + // add to the map + if (parameters.add(odesc.name, new astring(param)) == TMERR_DUPLICATE) + return print_help(argv[0], desc, "Multiple parameters of the same type specified"); + break; + } + } - /* check the MD5 */ - if (memcmp(source_header->md5, empty_checksum, CHD_MD5_BYTES) != 0) - { - if (memcmp(source_header->md5, verify.md5, CHD_MD5_BYTES) != 0) - { - progress(TRUE, "WARNING: expected input MD5 = "); - for (i = 0; i < CHD_MD5_BYTES; i++) - progress(TRUE, "%02x", source_header->md5[i]); - progress(TRUE, "\n"); - - progress(TRUE, " actual MD5 = "); - for (i = 0; i < CHD_MD5_BYTES; i++) - progress(TRUE, "%02x", verify.md5[i]); - progress(TRUE, "\n"); + // if not valid, error + if (valid == ARRAY_LENGTH(desc.valid_options)) + return print_help(argv[0], desc, "Option not valid for this command"); } - else - progress(TRUE, "Input MD5 verified \n"); - } - - /* check the SHA1 */ - if (memcmp(source_header->sha1, empty_checksum, CHD_SHA1_BYTES) != 0) - { - if (memcmp(source_header->sha1, verify.sha1, CHD_SHA1_BYTES) != 0) + + // make sure we got all our required parameters + for (int valid = 0; valid < ARRAY_LENGTH(desc.valid_options); valid++) { - progress(TRUE, "WARNING: expected input SHA1 = "); - for (i = 0; i < CHD_SHA1_BYTES; i++) - progress(TRUE, "%02x", source_header->sha1[i]); - progress(TRUE, "\n"); - - progress(TRUE, " actual SHA1 = "); - for (i = 0; i < CHD_SHA1_BYTES; i++) - progress(TRUE, "%02x", verify.sha1[i]); - progress(TRUE, "\n"); + const char *validname = desc.valid_options[valid]; + if (validname == NULL) + break; + if (*validname == REQUIRED[0] && parameters.find(++validname) == NULL) + return print_help(argv[0], desc, "Required parameters missing"); } - else - progress(TRUE, "Input SHA1 verified \n"); - } - } - - /* finish compression */ - err = chd_compress_finish(chd, !(source_header->flags & CHDFLAGS_IS_WRITEABLE)); - if (err != CHDERR_NONE) - goto cleanup; - - /* final progress update */ - progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); - -cleanup: - if (source_cache != NULL) - free(source_cache); - if (cache != NULL) - free(cache); - return err; -} - - -/*------------------------------------------------- - chdman_clone_metadata - clone the metadata from - one CHD to a second --------------------------------------------------*/ - -static chd_error chdman_clone_metadata(chd_file *source, chd_file *dest) -{ - const chd_header *header = chd_get_header(source); - UINT8 metabuffer[MAX(4096, sizeof(cdrom_toc))]; - UINT32 metatag, metasize, metaindex; - UINT8 metaflags; - chd_error err; - - /* clone the metadata */ - for (metaindex = 0; ; metaindex++) - { - /* fetch the next piece of metadata */ - err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, metabuffer, sizeof(metabuffer), &metasize, &metatag, &metaflags); - if (err != CHDERR_NONE) - { - if (err == CHDERR_METADATA_NOT_FOUND) - err = CHDERR_NONE; - break; - } - - /* promote certain bits of metadata to checksummed for older CHDs */ - if (header->version <= 3) - { - if (metatag == HARD_DISK_METADATA_TAG || metatag == CDROM_OLD_METADATA_TAG || - metatag == CDROM_TRACK_METADATA_TAG || metatag == AV_METADATA_TAG || - metatag == CDROM_TRACK_METADATA2_TAG || metatag == AV_LD_METADATA_TAG) + + // all clear, run the command + try { - metaflags |= CHD_MDFLAGS_CHECKSUM; + (*s_commands[cmdnum].handler)(parameters); + return 0; } - - /* convert old-style CD-ROM data to newer */ - if (metatag == CDROM_OLD_METADATA_TAG) + catch (fatal_error &err) { - cdrom_toc *toc = (cdrom_toc *)metabuffer; - err = cdrom_parse_metadata(source, toc); - if (err == CHDERR_NONE) - err = cdrom_write_metadata(dest, toc); - if (err == CHDERR_NONE) - continue; + return err.error(); } - } - - /* if that fit, just write it back from the temporary buffer */ - if (metasize <= sizeof(metabuffer)) - { - /* write it to the target */ - err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, metabuffer, metasize, metaflags); - if (err != CHDERR_NONE) - break; - } - - /* otherwise, allocate a bigger temporary buffer */ - else - { - UINT8 *allocbuffer = (UINT8 *)malloc(metasize); - if (allocbuffer == NULL) + catch (std::bad_alloc &) { - err = CHDERR_OUT_OF_MEMORY; - break; + fprintf(stderr, "Out of memory\n"); + return 1; } - - /* re-read the whole thing */ - err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, allocbuffer, metasize, &metasize, &metatag, &metaflags); - if (err != CHDERR_NONE) + catch (...) { - free(allocbuffer); - break; + fprintf(stderr, "Unhandled exception\n"); + return 1; } - - /* write it to the target */ - err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, allocbuffer, metasize, metaflags); - free(allocbuffer); - if (err != CHDERR_NONE) - break; } - } - return err; -} - - -/*------------------------------------------------- - main - entry point --------------------------------------------------*/ - -int CLIB_DECL main(int argc, char *argv[]) -{ - static const struct - { - const char * option; - int (*callback)(int argc, char *argv[], int param); - int param; - } option_list[] = - { - { "-createhd", do_createhd, 0 }, - { "-createuncomphd", do_createhd_uncomp, 0 }, - { "-createraw", do_createraw, 0 }, - { "-createcd", do_createcd, 0 }, -// { "-createcdflac", do_createcd, 1 }, - { "-createblankhd", do_createblankhd, 0 }, - { "-createav", do_createav, 0 }, - { "-copydata", do_copydata, 0 }, - { "-extract", do_extract, 0 }, - { "-extractcd", do_extractcd, 0 }, - { "-extractav", do_extractav, 0 }, - { "-verify", do_verify, 0 }, - { "-verifyfix", do_verify, 1 }, - { "-update", do_merge_update_chomp, OPERATION_UPDATE }, - { "-chomp", do_merge_update_chomp, OPERATION_CHOMP }, - { "-info", do_info, 0 }, - { "-merge", do_merge_update_chomp, OPERATION_MERGE }, - { "-diff", do_diff, 0 }, - { "-setchs", do_setchs, 0 }, - { "-fixavdata", do_fixavdata, 0 }, - { "-addmetatext", do_addmeta, TRUE }, - { "-addmetabin", do_addmeta, FALSE }, - }; - extern const char build_version[]; - int i; - - /* print the header */ - printf("chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version); - - /* require at least 1 argument */ - if (argc < 2) - return usage(); - - /* handle the appropriate command */ - for (i = 0; i < ARRAY_LENGTH(option_list); i++) - if (strcmp(argv[1], option_list[i].option) == 0) - return (*option_list[i].callback)(argc, argv, option_list[i].param); - - return usage(); + + // print generic help if nothing found + return print_help(argv[0]); } diff --git a/src/tools/tools.mak b/src/tools/tools.mak index 20bd9448cf4..5625c01d2a8 100644 --- a/src/tools/tools.mak +++ b/src/tools/tools.mak @@ -84,7 +84,8 @@ romcmp$(EXE): $(ROMCMPOBJS) $(LIBUTIL) $(ZLIB) $(EXPAT) $(LIBOCORE) CHDMANOBJS = \ $(TOOLSOBJ)/chdman.o \ -chdman$(EXE): $(VERSIONOBJ) $(CHDMANOBJS) $(LIBUTIL) $(ZLIB) $(EXPAT) $(FLAC_LIB) $(LIBOCORE) +chdman$(EXE): $(VERSIONOBJ) $(CHDMANOBJS) $(LIBUTIL) $(ZLIB) $(EXPAT) $(FLAC_LIB) $(7Z_LIB) $(LIBOCORE) + $(CC) $(CDEFS) $(CFLAGS) -c $(SRC)/version.c -o $(VERSIONOBJ) @echo Linking $@... $(LD) $(LDFLAGS) $^ $(LIBS) $(FLAC_LIB) -o $@ |