diff options
Diffstat (limited to 'src/lib/util')
38 files changed, 2950 insertions, 2146 deletions
diff --git a/src/lib/util/aviio.cpp b/src/lib/util/aviio.cpp index 40ce6ebe689..7b5e8d00d87 100644 --- a/src/lib/util/aviio.cpp +++ b/src/lib/util/aviio.cpp @@ -48,7 +48,7 @@ static constexpr std::uint64_t FOUR_GB = std::uint64_t(1) << 32; * @brief A constant that defines maximum sound channels. */ -static constexpr unsigned MAX_SOUND_CHANNELS = 2; +static constexpr unsigned MAX_SOUND_CHANNELS = 16; /** * @def SOUND_BUFFER_MSEC diff --git a/src/lib/util/bitstream.h b/src/lib/util/bitstream.h index 4f3817afe6f..eb8b2005329 100644 --- a/src/lib/util/bitstream.h +++ b/src/lib/util/bitstream.h @@ -40,10 +40,11 @@ public: private: // internal state uint32_t m_buffer; // current bit accumulator - int m_bits; // number of bits in the accumulator + int m_bits; // number of bits in the accumulator const uint8_t * m_read; // read pointer uint32_t m_doffset; // byte offset within the data uint32_t m_dlength; // length of the data + int m_dbitoffs; // bit offset within current read pointer }; @@ -64,7 +65,7 @@ public: private: // internal state uint32_t m_buffer; // current bit accumulator - int m_bits; // number of bits in the accumulator + int m_bits; // number of bits in the accumulator uint8_t * m_write; // write pointer uint32_t m_doffset; // byte offset within the data uint32_t m_dlength; // length of the data @@ -85,7 +86,8 @@ inline bitstream_in::bitstream_in(const void *src, uint32_t srclength) m_bits(0), m_read(reinterpret_cast<const uint8_t *>(src)), m_doffset(0), - m_dlength(srclength) + m_dlength(srclength), + m_dbitoffs(0) { } @@ -103,12 +105,31 @@ inline uint32_t bitstream_in::peek(int numbits) // fetch data if we need more if (numbits > m_bits) { - while (m_bits <= 24) + while (m_bits < 32) { + uint32_t newbits = 0; + if (m_doffset < m_dlength) - m_buffer |= m_read[m_doffset] << (24 - m_bits); - m_doffset++; - m_bits += 8; + { + // adjust current data to discard any previously read partial bits + newbits = (m_read[m_doffset] << m_dbitoffs) & 0xff; + } + + if (m_bits + 8 > 32) + { + // take only what can be used to fill out the rest of the buffer + m_dbitoffs = 32 - m_bits; + newbits >>= 8 - m_dbitoffs; + m_buffer |= newbits; + m_bits += m_dbitoffs; + } + else + { + m_buffer |= newbits << (24 - m_bits); + m_bits += 8 - m_dbitoffs; + m_dbitoffs = 0; + m_doffset++; + } } } @@ -154,6 +175,10 @@ inline uint32_t bitstream_in::read_offset() const result--; bits -= 8; } + + if (m_dbitoffs > bits) + result++; + return result; } @@ -169,7 +194,11 @@ inline uint32_t bitstream_in::flush() m_doffset--; m_bits -= 8; } - m_bits = m_buffer = 0; + + if (m_dbitoffs > m_bits) + m_doffset++; + + m_bits = m_buffer = m_dbitoffs = 0; return m_doffset; } @@ -196,8 +225,11 @@ inline bitstream_out::bitstream_out(void *dest, uint32_t destlength) inline void bitstream_out::write(uint32_t newbits, int numbits) { + newbits <<= 32 - numbits; + // flush the buffer if we're going to overflow it - if (m_bits + numbits > 32) + while (m_bits + numbits >= 32 && numbits > 0) + { while (m_bits >= 8) { if (m_doffset < m_dlength) @@ -207,11 +239,19 @@ inline void bitstream_out::write(uint32_t newbits, int numbits) m_bits -= 8; } - // shift the bits to the top - if (numbits == 0) - newbits = 0; - else - newbits <<= 32 - numbits; + // offload more bits if it'll still overflow the buffer + if (m_bits + numbits >= 32) + { + const int rem = std::min(32 - m_bits, numbits); + m_buffer |= newbits >> m_bits; + m_bits += rem; + newbits <<= rem; + numbits -= rem; + } + } + + if (numbits <= 0) + return; // now shift it down to account for the number of bits we already have and OR them in m_buffer |= newbits >> m_bits; diff --git a/src/lib/util/cdrom.cpp b/src/lib/util/cdrom.cpp index 36a51104d3c..ab227dce1eb 100644 --- a/src/lib/util/cdrom.cpp +++ b/src/lib/util/cdrom.cpp @@ -21,10 +21,12 @@ #include "corestr.h" #include "multibyte.h" #include "osdfile.h" +#include "path.h" #include "strformat.h" #include <cassert> #include <cstdlib> +#include <tuple> /*************************************************************************** @@ -34,7 +36,6 @@ /** @brief The verbose. */ #define VERBOSE (0) #define EXTRA_VERBOSE (0) -#if VERBOSE /** * @def LOG(x) do @@ -44,31 +45,7 @@ * @param x The void to process. */ -#define LOG(x) do { if (VERBOSE) logerror x; } while (0) - -/** - * @fn void CLIB_DECL logerror(const char *text, ...) ATTR_PRINTF(1,2); - * - * @brief Logerrors the given text. - * - * @param text The text. - * - * @return A CLIB_DECL. - */ - -void CLIB_DECL logerror(const char *text, ...) ATTR_PRINTF(1,2); -#else - -/** - * @def LOG(x); - * - * @brief A macro that defines log. - * - * @param x The void to process. - */ - -#define LOG(x) -#endif +#define LOG(x) do { if (VERBOSE) { osd_printf_info x; } } while (0) @@ -159,7 +136,7 @@ cdrom_file::cdrom_file(std::string_view inputfile) std::error_condition err = parse_toc(inputfile, cdtoc, cdtrack_info); if (err) { - fprintf(stderr, "Error reading input file: %s\n", err.message().c_str()); + osd_printf_error("Error reading input file: %s\n", err.message()); throw nullptr; } @@ -175,13 +152,13 @@ cdrom_file::cdrom_file(std::string_view inputfile) std::error_condition const filerr = osd_file::open(cdtrack_info.track[i].fname, OPEN_FLAG_READ, file, length); if (filerr) { - fprintf(stderr, "Unable to open file: %s\n", cdtrack_info.track[i].fname.c_str()); + osd_printf_error("Unable to open file: %s\n", cdtrack_info.track[i].fname); throw nullptr; } fhandle[i] = util::osd_file_read(std::move(file)); if (!fhandle[i]) { - fprintf(stderr, "Unable to open file: %s\n", cdtrack_info.track[i].fname.c_str()); + osd_printf_error("Unable to open file: %s\n", cdtrack_info.track[i].fname); throw nullptr; } } @@ -204,6 +181,9 @@ cdrom_file::cdrom_file(std::string_view inputfile) track.logframeofs = track.pregap; } + if ((cdtoc.flags & CD_FLAG_MULTISESSION) && (cdtrack_info.track[i].leadin != -1)) + logofs += cdtrack_info.track[i].leadin; + track.physframeofs = physofs; track.chdframeofs = 0; track.logframeofs += logofs; @@ -215,22 +195,30 @@ cdrom_file::cdrom_file(std::string_view inputfile) physofs += track.frames; logofs += track.frames; + if ((cdtoc.flags & CD_FLAG_MULTISESSION) && cdtrack_info.track[i].leadout != -1) + logofs += cdtrack_info.track[i].leadout; + if (EXTRA_VERBOSE) - printf("Track %02d is format %d subtype %d datasize %d subsize %d frames %d extraframes %d pregap %d pgmode %d presize %d postgap %d logofs %d physofs %d chdofs %d logframes %d\n", i+1, - track.trktype, - track.subtype, - track.datasize, - track.subsize, - track.frames, - track.extraframes, - track.pregap, - track.pgtype, - track.pgdatasize, - track.postgap, - track.logframeofs, - track.physframeofs, - track.chdframeofs, - track.logframes); + { + osd_printf_verbose("session %d track %02d is format %d subtype %d datasize %d subsize %d frames %d extraframes %d pregap %d pgmode %d presize %d postgap %d logofs %d physofs %d chdofs %d logframes %d pad %d\n", + track.session + 1, + i + 1, + track.trktype, + track.subtype, + track.datasize, + track.subsize, + track.frames, + track.extraframes, + track.pregap, + track.pgtype, + track.pgdatasize, + track.postgap, + track.logframeofs, + track.physframeofs, + track.chdframeofs, + track.logframes, + track.padframes); + } } // fill out dummy entries for the last track to help our search @@ -313,21 +301,26 @@ cdrom_file::cdrom_file(chd_file *_chd) logofs += track.frames; if (EXTRA_VERBOSE) - printf("Track %02d is format %d subtype %d datasize %d subsize %d frames %d extraframes %d pregap %d pgmode %d presize %d postgap %d logofs %d physofs %d chdofs %d logframes %d\n", i+1, - track.trktype, - track.subtype, - track.datasize, - track.subsize, - track.frames, - track.extraframes, - track.pregap, - track.pgtype, - track.pgdatasize, - track.postgap, - track.logframeofs, - track.physframeofs, - track.chdframeofs, - track.logframes); + { + osd_printf_verbose("session %d track %02d is format %d subtype %d datasize %d subsize %d frames %d extraframes %d pregap %d pgmode %d presize %d postgap %d logofs %d physofs %d chdofs %d logframes %d pad %d\n", + track.session + 1, + i + 1, + track.trktype, + track.subtype, + track.datasize, + track.subsize, + track.frames, + track.extraframes, + track.pregap, + track.pgtype, + track.pgdatasize, + track.postgap, + track.logframeofs, + track.physframeofs, + track.chdframeofs, + track.logframes, + track.padframes); + } } // fill out dummy entries for the last track to help our search @@ -387,7 +380,7 @@ std::error_condition cdrom_file::read_partial_sector(void *dest, uint32_t lbasec if ((cdtoc.tracks[tracknum].pgdatasize == 0) && (lbasector < cdtoc.tracks[tracknum].logframeofs)) { if (EXTRA_VERBOSE) - printf("PG missing sector: LBA %d, trklog %d\n", lbasector, cdtoc.tracks[tracknum].logframeofs); + osd_printf_verbose("PG missing sector: LBA %d, trklog %d\n", lbasector, cdtoc.tracks[tracknum].logframeofs); memset(dest, 0, length); return result; } @@ -423,13 +416,13 @@ std::error_condition cdrom_file::read_partial_sector(void *dest, uint32_t lbasec sourcefileoffset += chdsector * bytespersector + startoffs; if (EXTRA_VERBOSE) - printf("Reading %u bytes from sector %d from track %d at offset %lu\n", (unsigned)length, chdsector, tracknum + 1, (unsigned long)sourcefileoffset); + osd_printf_verbose("Reading %u bytes from sector %d from track %d at offset %lu\n", (unsigned)length, chdsector, tracknum + 1, (unsigned long)sourcefileoffset); - size_t actual; result = srcfile.seek(sourcefileoffset, SEEK_SET); + size_t actual; if (!result) - result = srcfile.read(dest, length, actual); - // FIXME: if (actual < length) report error + std::tie(result, actual) = read(srcfile, dest, length); + // FIXME: if (!result && (actual < length)) report error needswap = cdtrack_info.track[tracknum].swap; } @@ -437,9 +430,10 @@ std::error_condition cdrom_file::read_partial_sector(void *dest, uint32_t lbasec if (needswap) { uint8_t *buffer = (uint8_t *)dest - startoffs; - for (int swapindex = startoffs; swapindex < 2352; swapindex += 2 ) + for (int swapindex = startoffs; swapindex < 2352; swapindex += 2) { - std::swap(buffer[ swapindex ], buffer[ swapindex + 1 ]); + using std::swap; + swap(buffer[ swapindex ], buffer[ swapindex + 1 ]); } } return result; @@ -604,6 +598,27 @@ uint32_t cdrom_file::get_track(uint32_t frame) const return track; } +uint32_t cdrom_file::get_track_index(uint32_t frame) const +{ + const uint32_t track = get_track(frame); + const uint32_t track_start = get_track_start(track); + const uint32_t index_offset = frame - track_start; + int index = 0; + + for (int i = 0; i < std::size(cdtrack_info.track[track].idx); i++) + { + if (index_offset >= cdtrack_info.track[track].idx[i]) + index = i; + else + break; + } + + if (cdtrack_info.track[track].idx[index] == -1) + index = 1; // valid index not found, default to index 1 + + return index; +} + /*************************************************************************** EXTRA UTILITIES @@ -682,11 +697,6 @@ void cdrom_file::get_info_from_type_string(const char *typestring, uint32_t *trk *trktype = CD_TRACK_MODE2_FORM_MIX; *datasize = 2336; } - else if (!strcmp(typestring, "MODE2/2336")) - { - *trktype = CD_TRACK_MODE2_FORM_MIX; - *datasize = 2336; - } else if (!strcmp(typestring, "MODE2_RAW")) { *trktype = CD_TRACK_MODE2_RAW; @@ -891,91 +901,78 @@ std::error_condition cdrom_file::parse_metadata(chd_file *chd, toc &toc) std::string metadata; std::error_condition err; - toc.flags = 0; + /* clear structures */ + memset(&toc, 0, sizeof(toc)); + + toc.numsessions = 1; /* start with no tracks */ for (toc.numtrks = 0; toc.numtrks < MAX_TRACKS; toc.numtrks++) { - int tracknum = -1, frames = 0, pregap, postgap, padframes; + int tracknum, frames, pregap, postgap, padframes; char type[16], subtype[16], pgtype[16], pgsub[16]; track_info *track; - pregap = postgap = padframes = 0; + tracknum = -1; + frames = pregap = postgap = padframes = 0; + std::fill(std::begin(type), std::end(type), 0); + std::fill(std::begin(subtype), std::end(subtype), 0); + std::fill(std::begin(pgtype), std::end(pgtype), 0); + std::fill(std::begin(pgsub), std::end(pgsub), 0); - /* fetch the metadata for this track */ - err = chd->read_metadata(CDROM_TRACK_METADATA_TAG, toc.numtrks, metadata); - if (!err) + // fetch the metadata for this track + if (!chd->read_metadata(CDROM_TRACK_METADATA_TAG, toc.numtrks, metadata)) { - /* parse the metadata */ - type[0] = subtype[0] = 0; - pgtype[0] = pgsub[0] = 0; if (sscanf(metadata.c_str(), CDROM_TRACK_METADATA_FORMAT, &tracknum, type, subtype, &frames) != 4) return chd_file::error::INVALID_DATA; - if (tracknum == 0 || tracknum > MAX_TRACKS) + } + else if (!chd->read_metadata(CDROM_TRACK_METADATA2_TAG, toc.numtrks, metadata)) + { + if (sscanf(metadata.c_str(), CDROM_TRACK_METADATA2_FORMAT, &tracknum, type, subtype, &frames, &pregap, pgtype, pgsub, &postgap) != 8) return chd_file::error::INVALID_DATA; - track = &toc.tracks[tracknum - 1]; } else { - err = chd->read_metadata(CDROM_TRACK_METADATA2_TAG, toc.numtrks, metadata); + // fall through to GD-ROM detection + err = chd->read_metadata(GDROM_OLD_METADATA_TAG, toc.numtrks, metadata); if (!err) - { - /* parse the metadata */ - type[0] = subtype[0] = 0; - pregap = postgap = 0; - if (sscanf(metadata.c_str(), CDROM_TRACK_METADATA2_FORMAT, &tracknum, type, subtype, &frames, &pregap, pgtype, pgsub, &postgap) != 8) - return chd_file::error::INVALID_DATA; - if (tracknum == 0 || tracknum > MAX_TRACKS) - return chd_file::error::INVALID_DATA; - track = &toc.tracks[tracknum - 1]; - } + toc.flags |= CD_FLAG_GDROMLE; // legacy GDROM track was detected else - { - err = chd->read_metadata(GDROM_OLD_METADATA_TAG, toc.numtrks, metadata); - if (!err) - /* legacy GDROM track was detected */ - toc.flags |= CD_FLAG_GDROMLE; - else - err = chd->read_metadata(GDROM_TRACK_METADATA_TAG, toc.numtrks, metadata); + err = chd->read_metadata(GDROM_TRACK_METADATA_TAG, toc.numtrks, metadata); - if (!err) - { - /* parse the metadata */ - type[0] = subtype[0] = 0; - pregap = postgap = 0; - if (sscanf(metadata.c_str(), GDROM_TRACK_METADATA_FORMAT, &tracknum, type, subtype, &frames, &padframes, &pregap, pgtype, pgsub, &postgap) != 9) - return chd_file::error::INVALID_DATA; - if (tracknum == 0 || tracknum > MAX_TRACKS) - return chd_file::error::INVALID_DATA; - track = &toc.tracks[tracknum - 1]; - toc.flags |= CD_FLAG_GDROM; - } - else - { - break; - } - } + if (err) + break; + + if (sscanf(metadata.c_str(), GDROM_TRACK_METADATA_FORMAT, &tracknum, type, subtype, &frames, &padframes, &pregap, pgtype, pgsub, &postgap) != 9) + return chd_file::error::INVALID_DATA; + + toc.flags |= CD_FLAG_GDROM; } - /* extract the track type and determine the data size */ + if (tracknum == 0 || tracknum > MAX_TRACKS) + return chd_file::error::INVALID_DATA; + + track = &toc.tracks[tracknum - 1]; + + // extract the track type and determine the data size track->trktype = CD_TRACK_MODE1; track->datasize = 0; convert_type_string_to_track_info(type, track); if (track->datasize == 0) return chd_file::error::INVALID_DATA; - /* extract the subtype and determine the subcode data size */ + // extract the subtype and determine the subcode data size track->subtype = CD_SUB_NONE; track->subsize = 0; convert_subtype_string_to_track_info(subtype, track); - /* set the frames and extra frames data */ + // set the frames and extra frames data track->frames = frames; track->padframes = padframes; int padded = (frames + TRACK_PADDING - 1) / TRACK_PADDING; track->extraframes = padded * TRACK_PADDING - frames; - /* set the pregap info */ + // set the pregap info track->pregap = pregap; track->pgtype = CD_TRACK_MODE1; track->pgsub = CD_SUB_NONE; @@ -999,7 +996,7 @@ std::error_condition cdrom_file::parse_metadata(chd_file *chd, toc &toc) if (toc.numtrks > 0) return std::error_condition(); - printf("toc.numtrks = %u?!\n", toc.numtrks); + osd_printf_info("toc.numtrks = %u?!\n", toc.numtrks); /* look for old-style metadata */ std::vector<uint8_t> oldmetadata; @@ -1011,8 +1008,11 @@ std::error_condition cdrom_file::parse_metadata(chd_file *chd, toc &toc) auto *mrp = reinterpret_cast<uint32_t *>(&oldmetadata[0]); toc.numtrks = *mrp++; + toc.numsessions = 1; + for (int i = 0; i < MAX_TRACKS; i++) { + toc.tracks[i].session = 0; toc.tracks[i].trktype = *mrp++; toc.tracks[i].subtype = *mrp++; toc.tracks[i].datasize = *mrp++; @@ -1070,7 +1070,16 @@ std::error_condition cdrom_file::write_metadata(chd_file *chd, const toc &toc) for (int i = 0; i < toc.numtrks; i++) { std::string metadata; - if (!(toc.flags & CD_FLAG_GDROM)) + if (toc.flags & CD_FLAG_GDROM) + { + metadata = util::string_format(GDROM_TRACK_METADATA_FORMAT, i + 1, get_type_string(toc.tracks[i].trktype), + get_subtype_string(toc.tracks[i].subtype), toc.tracks[i].frames, toc.tracks[i].padframes, + toc.tracks[i].pregap, get_type_string(toc.tracks[i].pgtype), + get_subtype_string(toc.tracks[i].pgsub), toc.tracks[i].postgap); + + err = chd->write_metadata(GDROM_TRACK_METADATA_TAG, i, metadata); + } + else { char submode[32]; @@ -1090,15 +1099,6 @@ std::error_condition cdrom_file::write_metadata(chd_file *chd, const toc &toc) toc.tracks[i].postgap); err = chd->write_metadata(CDROM_TRACK_METADATA2_TAG, i, metadata); } - else - { - metadata = util::string_format(GDROM_TRACK_METADATA_FORMAT, i + 1, get_type_string(toc.tracks[i].trktype), - get_subtype_string(toc.tracks[i].subtype), toc.tracks[i].frames, toc.tracks[i].padframes, - toc.tracks[i].pregap, get_type_string(toc.tracks[i].pgtype), - get_subtype_string(toc.tracks[i].pgsub), toc.tracks[i].postgap); - - err = chd->write_metadata(GDROM_TRACK_METADATA_TAG, i, metadata); - } if (err) return err; } @@ -1440,14 +1440,14 @@ void cdrom_file::ecc_clear(uint8_t *sector) * * @brief A macro that defines tokenize. * - * @param linebuffer The linebuffer. - * @param i Zero-based index of the. - * @param sizeof(linebuffer) The sizeof(linebuffer) - * @param token The token. - * @param sizeof(token) The sizeof(token) + * @param linebuffer The linebuffer. + * @param i Zero-based index of the. + * @param std::size(linebuffer) The std::size(linebuffer) + * @param token The token. + * @param std::size(token) The std::size(token) */ -#define TOKENIZE i = tokenize( linebuffer, i, sizeof(linebuffer), token, sizeof(token) ); +#define TOKENIZE i = tokenize( linebuffer, i, std::size(linebuffer), token, std::size(token) ); /*************************************************************************** @@ -1467,9 +1467,12 @@ void cdrom_file::ecc_clear(uint8_t *sector) std::string cdrom_file::get_file_path(std::string &path) { int pos = path.find_last_of('\\'); - if (pos!=-1) { + if (pos!=-1) + { path = path.substr(0,pos+1); - } else { + } + else + { pos = path.find_last_of('/'); path = path.substr(0,pos+1); } @@ -1518,20 +1521,20 @@ uint64_t cdrom_file::get_file_size(std::string_view filename) * @return An int. */ -int cdrom_file::tokenize( const char *linebuffer, int i, int linebuffersize, char *token, int tokensize ) +int cdrom_file::tokenize(const char *linebuffer, int i, int linebuffersize, char *token, int tokensize) { int j = 0; - int singlequote = 0; - int doublequote = 0; + bool singlequote = false; + bool doublequote = false; while ((i < linebuffersize) && isspace((uint8_t)linebuffer[i])) { i++; } - while ((i < linebuffersize) && (j < tokensize)) + while ((i < linebuffersize) && (j < tokensize) && (linebuffer[i] != '\0')) { - if (!singlequote && linebuffer[i] == '"' ) + if (!singlequote && linebuffer[i] == '"') { doublequote = !doublequote; } @@ -1572,19 +1575,19 @@ int cdrom_file::tokenize( const char *linebuffer, int i, int linebuffersize, cha * @return An int. */ -int cdrom_file::msf_to_frames( char *token ) +int cdrom_file::msf_to_frames(const char *token) { int m = 0; int s = 0; int f = 0; - if( sscanf( token, "%d:%d:%d", &m, &s, &f ) == 1 ) + if (sscanf(token, "%d:%d:%d", &m, &s, &f) == 1) { f = m; } else { - /* convert to just frames */ + // convert to just frames s += (m * 60); f += (s * 75); } @@ -1624,7 +1627,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao std::error_condition const filerr = osd_file::open(fname, OPEN_FLAG_READ, file, fsize); if (filerr) { - printf("ERROR: could not open (%s)\n", fname.c_str()); + osd_printf_error("ERROR: could not open (%s)\n", fname); return 0; } @@ -1633,12 +1636,12 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao offset += actual; if (offset < 4) { - printf("ERROR: unexpected RIFF offset %lu (%s)\n", offset, fname.c_str()); + osd_printf_error("ERROR: unexpected RIFF offset %lu (%s)\n", offset, fname); return 0; } if (memcmp(&buf[0], "RIFF", 4) != 0) { - printf("ERROR: could not find RIFF header (%s)\n", fname.c_str()); + osd_printf_error("ERROR: could not find RIFF header (%s)\n", fname); return 0; } @@ -1647,7 +1650,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao offset += actual; if (offset < 8) { - printf("ERROR: unexpected size offset %lu (%s)\n", offset, fname.c_str()); + osd_printf_error("ERROR: unexpected size offset %lu (%s)\n", offset, fname); return 0; } filesize = little_endianize_int32(filesize); @@ -1657,12 +1660,12 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao offset += actual; if (offset < 12) { - printf("ERROR: unexpected WAVE offset %lu (%s)\n", offset, fname.c_str()); + osd_printf_error("ERROR: unexpected WAVE offset %lu (%s)\n", offset, fname); return 0; } if (memcmp(&buf[0], "WAVE", 4) != 0) { - printf("ERROR: could not find WAVE header (%s)\n", fname.c_str()); + osd_printf_error("ERROR: could not find WAVE header (%s)\n", fname); return 0; } @@ -1681,7 +1684,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao offset += length; if (offset >= filesize) { - printf("ERROR: could not find fmt tag (%s)\n", fname.c_str()); + osd_printf_error("ERROR: could not find fmt tag (%s)\n", fname); return 0; } } @@ -1692,7 +1695,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao temp16 = little_endianize_int16(temp16); if (temp16 != 1) { - printf("ERROR: unsupported format %u - only PCM is supported (%s)\n", temp16, fname.c_str()); + osd_printf_error("ERROR: unsupported format %u - only PCM is supported (%s)\n", temp16, fname); return 0; } @@ -1702,7 +1705,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao temp16 = little_endianize_int16(temp16); if (temp16 != 2) { - printf("ERROR: unsupported number of channels %u - only stereo is supported (%s)\n", temp16, fname.c_str()); + osd_printf_error("ERROR: unsupported number of channels %u - only stereo is supported (%s)\n", temp16, fname); return 0; } @@ -1712,7 +1715,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao rate = little_endianize_int32(rate); if (rate != 44100) { - printf("ERROR: unsupported samplerate %u - only 44100 is supported (%s)\n", rate, fname.c_str()); + osd_printf_error("ERROR: unsupported samplerate %u - only 44100 is supported (%s)\n", rate, fname); return 0; } @@ -1726,7 +1729,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao bits = little_endianize_int16(bits); if (bits != 16) { - printf("ERROR: unsupported bits/sample %u - only 16 is supported (%s)\n", bits, fname.c_str()); + osd_printf_error("ERROR: unsupported bits/sample %u - only 16 is supported (%s)\n", bits, fname); return 0; } @@ -1748,7 +1751,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao offset += length; if (offset >= filesize) { - printf("ERROR: could not find data tag (%s)\n", fname.c_str()); + osd_printf_error("ERROR: could not find data tag (%s)\n", fname); return 0; } } @@ -1756,7 +1759,7 @@ uint32_t cdrom_file::parse_wav_sample(std::string_view filename, uint32_t *datao /* if there was a 0 length data block, we're done */ if (length == 0) { - printf("ERROR: empty data block (%s)\n", fname.c_str()); + osd_printf_error("ERROR: empty data block (%s)\n", fname); return 0; } @@ -1858,13 +1861,15 @@ std::error_condition cdrom_file::parse_nero(std::string_view tocfname, toc &outt memset(&outtoc, 0, sizeof(outtoc)); outinfo.reset(); + outtoc.numsessions = 1; + // seek to 12 bytes before the end fseek(infile, -12, SEEK_END); fread(buffer, 12, 1, infile); if (memcmp(buffer, "NER5", 4)) { - printf("ERROR: Not a Nero 5.5 or later image!\n"); + osd_printf_error("ERROR: Not a Nero 5.5 or later image!\n"); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; } @@ -1873,7 +1878,7 @@ std::error_condition cdrom_file::parse_nero(std::string_view tocfname, toc &outt if ((buffer[7] != 0) || (buffer[6] != 0) || (buffer[5] != 0) || (buffer[4] != 0)) { - printf("ERROR: File size is > 4GB, this version of CHDMAN cannot handle it."); + osd_printf_error("ERROR: File size is > 4GB, this version of CHDMAN cannot handle it."); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; } @@ -1920,8 +1925,7 @@ std::error_condition cdrom_file::parse_nero(std::string_view tocfname, toc &outt // printf("Track %d: sector size %d mode %x index0 %llx index1 %llx track_end %llx (pregap %d sectors, length %d sectors)\n", track, size, mode, index0, index1, track_end, (uint32_t)(index1-index0)/size, (uint32_t)(track_end-index1)/size); outinfo.track[track-1].fname.assign(tocfname); outinfo.track[track-1].offset = offset + (uint32_t)(index1-index0); - outinfo.track[track-1].idx0offs = 0; - outinfo.track[track-1].idx1offs = 0; + outinfo.track[track-1].idx[0] = outinfo.track[track-1].idx[1] = 0; switch (mode) { @@ -1931,12 +1935,12 @@ std::error_condition cdrom_file::parse_nero(std::string_view tocfname, toc &outt break; case 0x0300: // Mode 2 Form 1 - printf("ERROR: Mode 2 Form 1 tracks not supported\n"); + osd_printf_error("ERROR: Mode 2 Form 1 tracks not supported\n"); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; case 0x0500: // raw data - printf("ERROR: Raw data tracks not supported\n"); + osd_printf_error("ERROR: Raw data tracks not supported\n"); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; @@ -1951,22 +1955,22 @@ std::error_condition cdrom_file::parse_nero(std::string_view tocfname, toc &outt break; case 0x0f00: // raw data with sub-channel - printf("ERROR: Raw data tracks with sub-channel not supported\n"); + osd_printf_error("ERROR: Raw data tracks with sub-channel not supported\n"); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; case 0x1000: // audio with sub-channel - printf("ERROR: Audio tracks with sub-channel not supported\n"); + osd_printf_error("ERROR: Audio tracks with sub-channel not supported\n"); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; case 0x1100: // raw Mode 2 Form 1 with sub-channel - printf("ERROR: Raw Mode 2 Form 1 tracks with sub-channel not supported\n"); + osd_printf_error("ERROR: Raw Mode 2 Form 1 tracks with sub-channel not supported\n"); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; default: - printf("ERROR: Unknown track type %x, contact MAMEDEV!\n", mode); + osd_printf_error("ERROR: Unknown track type %x, contact MAMEDEV!\n", mode); fclose(infile); return chd_file::error::UNSUPPORTED_FORMAT; } @@ -2041,31 +2045,38 @@ std::error_condition cdrom_file::parse_iso(std::string_view tocfname, toc &outto outtoc.numtrks = 1; + outtoc.numsessions = 1; outinfo.track[0].fname = tocfname; outinfo.track[0].offset = 0; - outinfo.track[0].idx0offs = 0; - outinfo.track[0].idx1offs = 0; + outinfo.track[0].idx[0] = outinfo.track[0].idx[1] = 0; - if ((size % 2048)==0 ) { + if ((size % 2048) == 0) + { outtoc.tracks[0].trktype = CD_TRACK_MODE1; outtoc.tracks[0].frames = size / 2048; outtoc.tracks[0].datasize = 2048; outinfo.track[0].swap = false; - } else if ((size % 2336)==0 ) { + } + else if ((size % 2336) == 0) + { // 2352 byte mode 2 outtoc.tracks[0].trktype = CD_TRACK_MODE2; outtoc.tracks[0].frames = size / 2336; outtoc.tracks[0].datasize = 2336; outinfo.track[0].swap = false; - } else if ((size % 2352)==0 ) { + } + else if ((size % 2352) == 0) + { // 2352 byte mode 2 raw outtoc.tracks[0].trktype = CD_TRACK_MODE2_RAW; outtoc.tracks[0].frames = size / 2352; outtoc.tracks[0].datasize = 2352; outinfo.track[0].swap = false; - } else { - printf("ERROR: Unrecognized track type\n"); + } + else + { + osd_printf_error("ERROR: Unrecognized track type\n"); return chd_file::error::UNSUPPORTED_FORMAT; } @@ -2103,7 +2114,9 @@ std::error_condition cdrom_file::parse_iso(std::string_view tocfname, toc &outto std::error_condition cdrom_file::parse_gdi(std::string_view tocfname, toc &outtoc, track_input_info &outinfo) { - int i, numtracks; + char token[512]; + int i = 0; + int trackcnt = 0; std::string path = std::string(tocfname); @@ -2122,89 +2135,172 @@ std::error_condition cdrom_file::parse_gdi(std::string_view tocfname, toc &outto outtoc.flags = CD_FLAG_GDROM; char linebuffer[512]; - fgets(linebuffer,511,infile); - numtracks=atoi(linebuffer); + memset(linebuffer, 0, sizeof(linebuffer)); + + if (!fgets(linebuffer,511,infile)) + { + osd_printf_error("GDI doesn't have track count (blank file?)\n"); + return chd_file::error::INVALID_DATA; + } + + i = 0; + TOKENIZE + + const int numtracks = atoi(token); + + if (numtracks > 0 && numtracks - 1 > std::size(outinfo.track)) + { + osd_printf_error("GDI expects too many tracks. Expected %d tracks but only up to %zu tracks allowed\n", numtracks, std::size(outinfo.track) + 1); + return chd_file::error::INVALID_DATA; + } - for(i=0;i<numtracks;++i) + if (numtracks == 0) { - char *tok; - int trknum; - int trksize,trktype; - int sz; + osd_printf_error("GDI header specifies no tracks\n"); + return chd_file::error::INVALID_DATA; + } - fgets(linebuffer,511,infile); + while (!feof(infile)) + { + int paramcnt = 0; - tok=strtok(linebuffer," "); + if (!fgets(linebuffer,511,infile)) + break; - trknum=atoi(tok)-1; + i = 0; + TOKENIZE - outinfo.track[trknum].swap=false; - outinfo.track[trknum].offset=0; + // Ignore empty lines so they're not countered toward the total track count + if (!token[0]) + continue; + + paramcnt++; + const int trknum = atoi(token) - 1; + + if (trknum < 0 || trknum > std::size(outinfo.track) || trknum + 1 > numtracks) + { + osd_printf_error("Track %d is out of expected range of 1 to %d\n", trknum + 1, numtracks); + return chd_file::error::INVALID_DATA; + } + + if (outtoc.tracks[trknum].datasize != 0) + osd_printf_warning("Track %d defined multiple times?\n", trknum + 1); + else + trackcnt++; + + outinfo.track[trknum].swap = false; + outinfo.track[trknum].offset = 0; outtoc.tracks[trknum].datasize = 0; outtoc.tracks[trknum].subtype = CD_SUB_NONE; outtoc.tracks[trknum].subsize = 0; outtoc.tracks[trknum].pgsub = CD_SUB_NONE; - tok=strtok(nullptr," "); - outtoc.tracks[trknum].physframeofs=atoi(tok); + TOKENIZE + if (token[0]) + paramcnt++; + outtoc.tracks[trknum].physframeofs = atoi(token); - tok=strtok(nullptr," "); - trktype=atoi(tok); + TOKENIZE + if (token[0]) + paramcnt++; + const int trktype = atoi(token); - tok=strtok(nullptr," "); - trksize=atoi(tok); + TOKENIZE + if (token[0]) + paramcnt++; + const int trksize = atoi(token); - if(trktype==4 && trksize==2352) + 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) + else 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) + else if (trktype == 0) { - outtoc.tracks[trknum].trktype=CD_TRACK_AUDIO; - outtoc.tracks[trknum].datasize=2352; + outtoc.tracks[trknum].trktype = CD_TRACK_AUDIO; + outtoc.tracks[trknum].datasize = 2352; outinfo.track[trknum].swap = true; } + else + { + osd_printf_error("Unknown track type %d and track size %d combination encountered\n", trktype, trksize); + return chd_file::error::INVALID_DATA; + } - std::string name; + // skip to start of next token + int pi = i; + while (pi < std::size(linebuffer) && isspace((uint8_t)linebuffer[pi])) + pi++; - tok=strtok(nullptr," "); - name = tok; - if (tok[0]=='"') { - do { - tok=strtok(nullptr," "); - if (tok!=nullptr) { - name += " "; - name += tok; - } - } while(tok!=nullptr && (strrchr(tok,'"')-tok !=(strlen(tok)-1))); - strdelchr(name,'"'); + if (linebuffer[pi] == '"' && strchr(linebuffer + pi + 1, '"') == nullptr) + { + osd_printf_error("Track %d filename does not having closing quotation mark: '%s'\n", trknum + 1, linebuffer + pi); + return chd_file::error::INVALID_DATA; } - outinfo.track[trknum].fname.assign(path).append(name); - sz = get_file_size(outinfo.track[trknum].fname); + TOKENIZE + if (token[0]) + paramcnt++; + + outinfo.track[trknum].fname.assign(path).append(token); - outtoc.tracks[trknum].frames = sz/trksize; + const uint64_t sz = get_file_size(outinfo.track[trknum].fname); + outtoc.tracks[trknum].frames = sz / trksize; outtoc.tracks[trknum].padframes = 0; if (trknum != 0) { - int dif=outtoc.tracks[trknum].physframeofs-(outtoc.tracks[trknum-1].frames+outtoc.tracks[trknum-1].physframeofs); + const int dif = outtoc.tracks[trknum].physframeofs - (outtoc.tracks[trknum-1].frames + outtoc.tracks[trknum-1].physframeofs); outtoc.tracks[trknum-1].frames += dif; outtoc.tracks[trknum-1].padframes = dif; } + + TOKENIZE + // offset parameter, not used + if (token[0]) + paramcnt++; + + // check if there are any extra parameters that shouldn't be there + while (token[0]) + { + TOKENIZE + if (token[0]) + paramcnt++; + } + + if (paramcnt != 6) + { + osd_printf_error("GDI track entry should have 6 parameters, found %d\n", paramcnt); + return chd_file::error::INVALID_DATA; + } + } + + bool missing_tracks = trackcnt != numtracks; + for (int i = 0; i < numtracks; i++) + { + if (outtoc.tracks[i].datasize == 0) + { + osd_printf_warning("Could not find track %d\n", i + 1); + missing_tracks = true; + } + } + + if (missing_tracks) + { + osd_printf_error("GDI is missing tracks\n"); + return chd_file::error::INVALID_DATA; } if (EXTRA_VERBOSE) - for(i=0; i < numtracks; i++) + for (int i = 0; i < numtracks; i++) { - printf("%s %d %d %d (true %d)\n", outinfo.track[i].fname.c_str(), outtoc.tracks[i].frames, outtoc.tracks[i].padframes, outtoc.tracks[i].physframeofs, outtoc.tracks[i].frames - outtoc.tracks[i].padframes); + osd_printf_verbose("'%s' %d %d %d (true %d)\n", outinfo.track[i].fname, outtoc.tracks[i].frames, outtoc.tracks[i].padframes, outtoc.tracks[i].physframeofs, outtoc.tracks[i].frames - outtoc.tracks[i].padframes); } /* close the input TOC */ @@ -2212,12 +2308,13 @@ std::error_condition cdrom_file::parse_gdi(std::string_view tocfname, toc &outto /* store the number of tracks found */ outtoc.numtrks = numtracks; + outtoc.numsessions = 1; return std::error_condition(); } /*------------------------------------------------- - parse_cue - parse a CDRWin format CUE file + parse_cue - parse a .CUE file -------------------------------------------------*/ /** @@ -2230,15 +2327,23 @@ std::error_condition cdrom_file::parse_gdi(std::string_view tocfname, toc &outto * @param [in,out] outinfo The outinfo. * * @return A std::error_condition. + * + * Redump multi-CUE for Dreamcast GDI: + * Dreamcast discs have two images on a single disc. The first image is SINGLE-DENSITY and the second image + * is HIGH-DENSITY. The SINGLE-DENSITY area starts 0 LBA and HIGH-DENSITY area starts 45000 LBA. */ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo) { - int i, trknum; - static char token[512]; + int i, trknum, sessionnum, session_pregap; + char token[512]; std::string lastfname; uint32_t wavlen, wavoffs; std::string path = std::string(tocfname); + const bool is_gdrom = is_gdicue(tocfname); + enum gdi_area current_area = SINGLE_DENSITY; + bool is_multibin = false; + int leadin = -1; FILE *infile = fopen(path.c_str(), "rt"); if (!infile) @@ -2254,554 +2359,289 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto trknum = -1; wavoffs = wavlen = 0; + sessionnum = 0; + session_pregap = 0; + + if (is_gdrom) + { + outtoc.flags = CD_FLAG_GDROM; + } char linebuffer[512]; + memset(linebuffer, 0, sizeof(linebuffer)); + while (!feof(infile)) { /* get the next line */ - fgets(linebuffer, 511, infile); + if (!fgets(linebuffer, 511, infile)) + break; - /* if EOF didn't hit, keep going */ - if (!feof(infile)) - { - i = 0; + i = 0; - TOKENIZE + TOKENIZE + + if (!strcmp(token, "REM")) + { + /* skip to actual data of REM command */ + while (i < std::size(linebuffer) && isspace((uint8_t)linebuffer[i])) + i++; - if (!strcmp(token, "FILE")) + if (!strncmp(linebuffer+i, "SESSION", 7)) { - /* found the data file for a track */ + /* IsoBuster extension */ TOKENIZE - /* keep the filename */ - lastfname.assign(path).append(token); - - /* get the file type */ + /* get the session number */ TOKENIZE - if (!strcmp(token, "BINARY")) - { - outinfo.track[trknum+1].swap = false; - } - else if (!strcmp(token, "MOTOROLA")) - { - outinfo.track[trknum+1].swap = true; - } - else if (!strcmp(token, "WAVE")) - { - wavlen = parse_wav_sample(lastfname, &wavoffs); - if (!wavlen) - { - fclose(infile); - printf("ERROR: couldn't read [%s] or not a valid .WAV\n", lastfname.c_str()); - return chd_file::error::INVALID_DATA; - } - } - else - { - fclose(infile); - printf("ERROR: Unhandled track type %s\n", token); - return chd_file::error::UNSUPPORTED_FORMAT; - } + sessionnum = strtoul(token, nullptr, 10) - 1; + + if (sessionnum >= 1) /* don't consider it a multisession CD unless there's actually more than 1 session */ + outtoc.flags |= CD_FLAG_MULTISESSION; } - else if (!strcmp(token, "TRACK")) + else if ((outtoc.flags & CD_FLAG_MULTISESSION) && !strncmp(linebuffer+i, "PREGAP", 6)) { - /* get the track number */ - TOKENIZE - trknum = strtoul(token, nullptr, 10) - 1; + /* + Redump extension? PREGAP associated with the session instead of the track - /* next token on the line is the track type */ + DiscImageCreator - Older versions would write a bogus value here (and maybe session lead-in and lead-out). + These should be considered bad dumps and will not be supported. + */ TOKENIZE - if (wavlen != 0) - { - 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.track[trknum].offset = 0; - } - outtoc.tracks[trknum].subtype = CD_SUB_NONE; - outtoc.tracks[trknum].subsize = 0; - outtoc.tracks[trknum].pgsub = CD_SUB_NONE; - outtoc.tracks[trknum].pregap = 0; - outtoc.tracks[trknum].padframes = 0; - outinfo.track[trknum].idx0offs = -1; - outinfo.track[trknum].idx1offs = 0; - - outinfo.track[trknum].fname.assign(lastfname); // default filename to the last one - -// printf("trk %d: fname %s offset %d\n", trknum, outinfo.track[trknum].fname.c_str(), outinfo.track[trknum].offset); - - convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); - if (outtoc.tracks[trknum].datasize == 0) - { - fclose(infile); - printf("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); - return chd_file::error::UNSUPPORTED_FORMAT; - } - - /* next (optional) token on the line is the subcode type */ + /* get pregap time */ TOKENIZE - - convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); + session_pregap = msf_to_frames(token); } - else if (!strcmp(token, "INDEX")) /* only in bin/cue files */ + else if (!strncmp(linebuffer+i, "LEAD-OUT", 8)) { - int idx, frames; + /* + IsoBuster and ImgBurn (single bin file) - Lead-out time is the start of the lead-out + lead-out time - MSF of last track of session = size of last track - /* get index number */ + Redump and DiscImageCreator (multiple bins) - Lead-out time is the duration of just the lead-out + */ TOKENIZE - idx = strtoul(token, nullptr, 10); - /* get index */ + /* get lead-out time */ TOKENIZE - frames = msf_to_frames( token ); - - if (idx == 0) - { - outinfo.track[trknum].idx0offs = frames; - } - else if (idx == 1) - { - outinfo.track[trknum].idx1offs = frames; - if ((outtoc.tracks[trknum].pregap == 0) && (outinfo.track[trknum].idx0offs != -1)) - { - outtoc.tracks[trknum].pregap = frames - outinfo.track[trknum].idx0offs; - outtoc.tracks[trknum].pgtype = outtoc.tracks[trknum].trktype; - switch (outtoc.tracks[trknum].pgtype) - { - case CD_TRACK_MODE1: - case CD_TRACK_MODE2_FORM1: - outtoc.tracks[trknum].pgdatasize = 2048; - break; - - case CD_TRACK_MODE1_RAW: - case CD_TRACK_MODE2_RAW: - case CD_TRACK_AUDIO: - outtoc.tracks[trknum].pgdatasize = 2352; - break; - - case CD_TRACK_MODE2: - case CD_TRACK_MODE2_FORM_MIX: - outtoc.tracks[trknum].pgdatasize = 2336; - break; - - case CD_TRACK_MODE2_FORM2: - outtoc.tracks[trknum].pgdatasize = 2324; - break; - } - } - else // pregap sectors not in file, but we're always using idx0ofs for track length calc now - { - outinfo.track[trknum].idx0offs = frames; - } - } + int leadout_offset = msf_to_frames(token); + outinfo.track[trknum].leadout = leadout_offset; } - else if (!strcmp(token, "PREGAP")) + else if (!strncmp(linebuffer+i, "LEAD-IN", 7)) { - int frames; - - /* get index */ + /* + IsoBuster and ImgBurn (single bin file) - Not used? + Redump and DiscImageCreator (multiple bins) - Lead-in time is the duration of just the lead-in + */ TOKENIZE - frames = msf_to_frames( token ); - outtoc.tracks[trknum].pregap = frames; + /* get lead-in time */ + TOKENIZE + leadin = msf_to_frames(token); } - else if (!strcmp(token, "POSTGAP")) + else if (is_gdrom && !strncmp(linebuffer+i, "SINGLE-DENSITY AREA", 19)) { - int frames; - - /* get index */ - TOKENIZE - frames = msf_to_frames( token ); - - outtoc.tracks[trknum].postgap = frames; + /* single-density area starts LBA = 0 */ + current_area = SINGLE_DENSITY; + } + else if (is_gdrom && !strncmp(linebuffer+i, "HIGH-DENSITY AREA", 17)) + { + /* high-density area starts LBA = 45000 */ + current_area = HIGH_DENSITY; } } - } - - /* close the input CUE */ - fclose(infile); - - /* store the number of tracks found */ - outtoc.numtrks = trknum + 1; - - /* now go over the files again and set the lengths */ - for (trknum = 0; trknum < outtoc.numtrks; trknum++) - { - uint64_t 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) + else if (!strcmp(token, "FILE")) { - outinfo.track[trknum].swap = true; - } + /* found the data file for a track */ + TOKENIZE - // don't do this for .WAV tracks, we already have their length and offset filled out - if (outinfo.track[trknum].offset == 0) - { - // is this the last track? - if (trknum == (outtoc.numtrks-1)) + /* keep the filename */ + if (!is_multibin) { - /* if we have the same filename as the last track, do it that way */ - if (trknum != 0 && (outinfo.track[trknum].fname.compare(outinfo.track[trknum-1].fname)==0)) - { - tlen = get_file_size(outinfo.track[trknum].fname); - if (tlen == 0) - { - printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str()); - return std::errc::no_such_file_or_directory; - } - 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.track[trknum].fname); - if (tlen == 0) - { - printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str()); - return std::errc::no_such_file_or_directory; - } - tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outtoc.tracks[trknum].frames = tlen; - outinfo.track[trknum].offset = 0; - } + std::string prevfname(std::move(lastfname)); + lastfname.assign(path).append(token); + is_multibin = !prevfname.empty() && lastfname != prevfname; } else { - /* if we have the same filename as the next track, do it that way */ - if (outinfo.track[trknum].fname.compare(outinfo.track[trknum+1].fname)==0) - { - outtoc.tracks[trknum].frames = outinfo.track[trknum+1].idx0offs - outinfo.track[trknum].idx0offs; + lastfname.assign(path).append(token); + } - if (trknum == 0) // track 0 offset is 0 - { - outinfo.track[trknum].offset = 0; - } - else - { - outinfo.track[trknum].offset = outinfo.track[trknum-1].offset + outtoc.tracks[trknum-1].frames * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize); - } + /* get the file type */ + TOKENIZE - if (!outtoc.tracks[trknum].frames) - { - printf("ERROR: unable to determine size of track %d, missing INDEX 01 markers?\n", trknum+1); - return chd_file::error::INVALID_DATA; - } - } - else /* data files are different */ + if (!strcmp(token, "BINARY")) + { + outinfo.track[trknum+1].swap = false; + } + else if (!strcmp(token, "MOTOROLA")) + { + outinfo.track[trknum+1].swap = true; + } + else if (!strcmp(token, "WAVE")) + { + wavlen = parse_wav_sample(lastfname, &wavoffs); + if (!wavlen) { - tlen = get_file_size(outinfo.track[trknum].fname); - if (tlen == 0) - { - printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname.c_str()); - return std::errc::no_such_file_or_directory; - } - tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outtoc.tracks[trknum].frames = tlen; - outinfo.track[trknum].offset = 0; + fclose(infile); + osd_printf_error("ERROR: couldn't read [%s] or not a valid .WAV\n", lastfname); + return chd_file::error::INVALID_DATA; } } + else + { + fclose(infile); + osd_printf_error("ERROR: Unhandled track type %s\n", token); + return chd_file::error::UNSUPPORTED_FORMAT; + } } - //printf("trk %d: %d frames @ offset %d\n", trknum+1, outtoc.tracks[trknum].frames, outinfo.track[trknum].offset); - } - - return std::error_condition(); -} - -/*--------------------------------------------------------------------------------------- - is_gdicue - determine if CUE contains Redump multi-CUE format for Dreamcast GDI -----------------------------------------------------------------------------------------*/ - -/** - * Dreamcast GDI has two images on one disc, SINGLE-DENSITY and HIGH-DENSITY. - * - * Redump stores both images in a single .cue with a REM comment separating the images. - * This multi-cue format replaces the old flawed .gdi format. - * - * http://forum.redump.org/topic/19969/done-sega-dreamcast-multicue-gdi/ - * - * This function looks for strings "REM SINGLE-DENSITY AREA" & "REM HIGH-DENSITY AREA" - * indicating the Redump multi-cue format and therefore a Dreamcast GDI disc. - */ - -bool cdrom_file::is_gdicue(std::string_view tocfname) -{ - bool has_rem_singledensity = false; - bool has_rem_highdensity = false; - std::string path = std::string(tocfname); - - FILE *infile = fopen(path.c_str(), "rt"); - if (!infile) - { - return false; - } - - path = get_file_path(path); - - char linebuffer[512]; - while (!feof(infile)) - { - fgets(linebuffer, 511, infile); - - /* if EOF didn't hit, keep going */ - if (!feof(infile)) + else if (!strcmp(token, "TRACK")) { - has_rem_singledensity = has_rem_singledensity || !strncmp(linebuffer, "REM SINGLE-DENSITY AREA", 23); - has_rem_highdensity = has_rem_highdensity || !strncmp(linebuffer, "REM HIGH-DENSITY AREA", 21); - } - } - - fclose(infile); - - return has_rem_singledensity && has_rem_highdensity; -} - -/*----------------------------------------------------------------- - parse_gdicue - parse a Redump multi-CUE for Dreamcast GDI -------------------------------------------------------------------*/ - -/** - * @fn std::error_condition parse_gdicue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo) - * - * @brief Chdcd parse cue. - * - * @param tocfname The tocfname. - * @param [in,out] outtoc The outtoc. - * @param [in,out] outinfo The outinfo. - * - * @return A std::error_condition. - * - * Dreamcast discs have two images on a single disc. The first image is SINGLE-DENSITY and the second image - * is HIGH-DENSITY. The SINGLE-DENSITY area starts 0 LBA and HIGH-DENSITY area starts 45000 LBA. - * - * There are three Dreamcast disc patterns. - * - * Pattern I - (SD) DATA + AUDIO, (HD) DATA - * Pattern II - (SD) DATA + AUDIO, (HD) DATA + ... + AUDIO - * Pattern III - (SD) DATA + AUDIO, (HD) DATA + ... + DATA - * - * TOSEC layout is preferred and this code adjusts the TOC and INFO generated by a Redump .cue to match the - * layout from a TOSEC .gdi. - */ - -std::error_condition cdrom_file::parse_gdicue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo) -{ - int i, trknum; - static char token[512]; - std::string lastfname; - uint32_t wavlen, wavoffs; - std::string path = std::string(tocfname); - enum gdi_area current_area = SINGLE_DENSITY; - enum gdi_pattern disc_pattern = TYPE_UNKNOWN; - - FILE *infile = fopen(path.c_str(), "rt"); - if (!infile) - { - return std::error_condition(errno, std::generic_category()); - } - - path = get_file_path(path); - - /* clear structures */ - memset(&outtoc, 0, sizeof(outtoc)); - outinfo.reset(); - - trknum = -1; - wavoffs = wavlen = 0; - - outtoc.flags = CD_FLAG_GDROM; + /* get the track number */ + TOKENIZE + trknum = strtoul(token, nullptr, 10) - 1; - char linebuffer[512]; - while (!feof(infile)) - { - /* get the next line */ - fgets(linebuffer, 511, infile); + /* next token on the line is the track type */ + TOKENIZE - /* if EOF didn't hit, keep going */ - if (!feof(infile)) - { - /* single-density area starts LBA = 0 */ - if (!strncmp(linebuffer, "REM SINGLE-DENSITY AREA", 23)) + outtoc.tracks[trknum].session = sessionnum; + outtoc.tracks[trknum].subtype = CD_SUB_NONE; + outtoc.tracks[trknum].subsize = 0; + outtoc.tracks[trknum].pgsub = CD_SUB_NONE; + outtoc.tracks[trknum].pregap = 0; + outtoc.tracks[trknum].padframes = 0; + outtoc.tracks[trknum].datasize = 0; + outtoc.tracks[trknum].multicuearea = is_gdrom ? current_area : 0; + outinfo.track[trknum].offset = 0; + std::fill(std::begin(outinfo.track[trknum].idx), std::end(outinfo.track[trknum].idx), -1); + + outinfo.track[trknum].leadout = -1; + outinfo.track[trknum].leadin = leadin; /* use previously saved lead-in value */ + leadin = -1; + + if (session_pregap != 0) { - current_area = SINGLE_DENSITY; - continue; + /* + associated the pregap from the session transition with the lead-in to simplify things for now. + setting it as the proper pregap for the track causes logframeofs of the last dummy entry in the TOC + to become 2s later than it should. this might be an issue with how pgdatasize = 0 pregaps are handled. + */ + if (outinfo.track[trknum].leadin == -1) + outinfo.track[trknum].leadin = session_pregap; + else + outinfo.track[trknum].leadin += session_pregap; + session_pregap = 0; } - /* high-density area starts LBA = 45000 */ - if (!strncmp(linebuffer, "REM HIGH-DENSITY AREA", 21)) + if (wavlen != 0) { - current_area = HIGH_DENSITY; - continue; + outtoc.tracks[trknum].frames = wavlen/2352; + outinfo.track[trknum].offset = wavoffs; + wavoffs = wavlen = 0; } - i = 0; - - TOKENIZE + outinfo.track[trknum].fname.assign(lastfname); /* default filename to the last one */ - if (!strcmp(token, "FILE")) + if (EXTRA_VERBOSE) { - /* found the data file for a track */ - TOKENIZE - - /* keep the filename */ - lastfname.assign(path).append(token); - - /* get the file type */ - TOKENIZE - - if (!strcmp(token, "BINARY")) - { - outinfo.track[trknum+1].swap = false; - } - else if (!strcmp(token, "MOTOROLA")) + if (is_gdrom) { - outinfo.track[trknum+1].swap = true; - } - else if (!strcmp(token, "WAVE")) - { - wavlen = parse_wav_sample(lastfname, &wavoffs); - if (!wavlen) - { - fclose(infile); - printf("ERROR: couldn't read [%s] or not a valid .WAV\n", lastfname.c_str()); - return chd_file::error::INVALID_DATA; - } + osd_printf_verbose("trk %d: fname %s offset %d area %d\n", trknum, outinfo.track[trknum].fname, outinfo.track[trknum].offset, outtoc.tracks[trknum].multicuearea); } else { - fclose(infile); - printf("ERROR: Unhandled track type %s\n", token); - return chd_file::error::UNSUPPORTED_FORMAT; + osd_printf_verbose("trk %d: fname %s offset %d\n", trknum, outinfo.track[trknum].fname, outinfo.track[trknum].offset); } } - else if (!strcmp(token, "TRACK")) + + convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); + if (outtoc.tracks[trknum].datasize == 0) { - /* get the track number */ - TOKENIZE - trknum = strtoul(token, nullptr, 10) - 1; + fclose(infile); + osd_printf_error("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); + return chd_file::error::UNSUPPORTED_FORMAT; + } - /* next token on the line is the track type */ - TOKENIZE + /* next (optional) token on the line is the subcode type */ + TOKENIZE - if (wavlen != 0) - { - 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.track[trknum].offset = 0; - } - outtoc.tracks[trknum].subtype = CD_SUB_NONE; - outtoc.tracks[trknum].subsize = 0; - outtoc.tracks[trknum].pgsub = CD_SUB_NONE; - outtoc.tracks[trknum].pregap = 0; - outtoc.tracks[trknum].padframes = 0; - outtoc.tracks[trknum].multicuearea = current_area; - outinfo.track[trknum].idx0offs = -1; - outinfo.track[trknum].idx1offs = 0; - - outinfo.track[trknum].fname.assign(lastfname); // default filename to the last one - - if (EXTRA_VERBOSE) - printf("trk %d: fname %s offset %d area %d\n", trknum, outinfo.track[trknum].fname.c_str(), outinfo.track[trknum].offset, outtoc.tracks[trknum].multicuearea); - - convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); - if (outtoc.tracks[trknum].datasize == 0) - { - fclose(infile); - printf("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); - return chd_file::error::UNSUPPORTED_FORMAT; - } + convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); + } + else if (!strcmp(token, "INDEX")) + { + int idx, frames; - /* next (optional) token on the line is the subcode type */ - TOKENIZE + /* get index number */ + TOKENIZE + idx = strtoul(token, nullptr, 10); - convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); - } - else if (!strcmp(token, "INDEX")) /* only in bin/cue files */ - { - int idx, frames; + /* get index */ + TOKENIZE + frames = msf_to_frames(token); - /* get index number */ - TOKENIZE - idx = strtoul(token, nullptr, 10); + if (idx < 0 || idx > MAX_INDEX) + { + osd_printf_error("ERROR: encountered invalid index %d\n", idx); + return chd_file::error::INVALID_DATA; + } - /* get index */ - TOKENIZE - frames = msf_to_frames( token ); + outinfo.track[trknum].idx[idx] = frames; - if (idx == 0) + if (idx == 1) + { + if (outtoc.tracks[trknum].pregap == 0 && outinfo.track[trknum].idx[0] != -1) { - outinfo.track[trknum].idx0offs = frames; + outtoc.tracks[trknum].pregap = frames - outinfo.track[trknum].idx[0]; + outtoc.tracks[trknum].pgtype = outtoc.tracks[trknum].trktype; + outtoc.tracks[trknum].pgdatasize = outtoc.tracks[trknum].datasize; } - else if (idx == 1) + else if (outinfo.track[trknum].idx[0] == -1) /* pregap sectors not in file, but we're always using idx 0 for track length calc now */ { - outinfo.track[trknum].idx1offs = frames; - if ((outtoc.tracks[trknum].pregap == 0) && (outinfo.track[trknum].idx0offs != -1)) - { - outtoc.tracks[trknum].pregap = frames - outinfo.track[trknum].idx0offs; - outtoc.tracks[trknum].pgtype = outtoc.tracks[trknum].trktype; - switch (outtoc.tracks[trknum].pgtype) - { - case CD_TRACK_MODE1: - case CD_TRACK_MODE2_FORM1: - outtoc.tracks[trknum].pgdatasize = 2048; - break; - - case CD_TRACK_MODE1_RAW: - case CD_TRACK_MODE2_RAW: - case CD_TRACK_AUDIO: - outtoc.tracks[trknum].pgdatasize = 2352; - break; - - case CD_TRACK_MODE2: - case CD_TRACK_MODE2_FORM_MIX: - outtoc.tracks[trknum].pgdatasize = 2336; - break; - - case CD_TRACK_MODE2_FORM2: - outtoc.tracks[trknum].pgdatasize = 2324; - break; - } - } - else // pregap sectors not in file, but we're always using idx0ofs for track length calc now - { - outinfo.track[trknum].idx0offs = frames; - } + outinfo.track[trknum].idx[0] = frames; } } - else if (!strcmp(token, "PREGAP")) - { - int frames; + } + else if (!strcmp(token, "PREGAP")) + { + int frames; - /* get index */ - TOKENIZE - frames = msf_to_frames( token ); + /* get index */ + TOKENIZE + frames = msf_to_frames(token); - outtoc.tracks[trknum].pregap = frames; - } - else if (!strcmp(token, "POSTGAP")) + outtoc.tracks[trknum].pregap = frames; + } + else if (!strcmp(token, "POSTGAP")) + { + int frames; + + /* get index */ + TOKENIZE + frames = msf_to_frames(token); + + outtoc.tracks[trknum].postgap = frames; + } + else if (!strcmp(token, "FLAGS")) + { + outtoc.tracks[trknum].control_flags = 0; + + /* keep looping over remaining tokens in FLAGS line until there's no more to read */ + while (i < std::size(linebuffer)) { - int frames; + int last_idx = i; - /* get index */ TOKENIZE - frames = msf_to_frames( token ); - outtoc.tracks[trknum].postgap = frames; + if (i == last_idx) + break; + + if (!strcmp(token, "DCP")) + outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED; + else if (!strcmp(token, "4CH")) + outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_4CH; + else if (!strcmp(token, "PRE")) + outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_PREEMPHASIS; } } } @@ -2811,164 +2651,255 @@ std::error_condition cdrom_file::parse_gdicue(std::string_view tocfname, toc &ou /* store the number of tracks found */ outtoc.numtrks = trknum + 1; + outtoc.numsessions = sessionnum + 1; /* now go over the files again and set the lengths */ for (trknum = 0; trknum < outtoc.numtrks; trknum++) { uint64_t tlen = 0; - // this is true for cue/bin and cue/iso, and we need it for cue/wav since .WAV is little-endian + if (outinfo.track[trknum].idx[1] == -1) + { + /* index 1 should always be set */ + osd_printf_error("ERROR: track %d is missing INDEX 01 marker\n", trknum+1); + return chd_file::error::INVALID_DATA; + } + + /* 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) { outinfo.track[trknum].swap = true; } - // don't do this for .WAV tracks, we already have their length and offset filled out - if (outinfo.track[trknum].offset == 0) + /* don't do this for .WAV tracks, we already have their length and offset filled out */ + if (outinfo.track[trknum].offset != 0) + continue; + + if (trknum+1 >= outtoc.numtrks && trknum > 0 && (outinfo.track[trknum].fname.compare(outinfo.track[trknum-1].fname) == 0)) + { + /* if the last track's filename is the same as the previous track */ + tlen = get_file_size(outinfo.track[trknum].fname); + if (tlen == 0) + { + osd_printf_error("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname); + return std::errc::no_such_file_or_directory; + } + + 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 if (trknum+1 < outtoc.numtrks && outinfo.track[trknum].fname.compare(outinfo.track[trknum+1].fname) == 0) { - // is this the last track? - if (trknum == (outtoc.numtrks-1)) + /* if the current filename is the same as the next track */ + outtoc.tracks[trknum].frames = outinfo.track[trknum+1].idx[0] - outinfo.track[trknum].idx[0]; + + if (outtoc.tracks[trknum].frames == 0) { - /* if we have the same filename as the last track, do it that way */ - if (trknum != 0 && (outinfo.track[trknum].fname.compare(outinfo.track[trknum-1].fname)==0)) + osd_printf_error("ERROR: unable to determine size of track %d, missing INDEX 01 markers?\n", trknum+1); + return chd_file::error::INVALID_DATA; + } + + if (trknum > 0) + { + const uint32_t previous_track_raw_size = outtoc.tracks[trknum-1].frames * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize); + outinfo.track[trknum].offset = outinfo.track[trknum-1].offset + previous_track_raw_size; + } + } + else if (outtoc.tracks[trknum].frames == 0) + { + /* if the filenames between tracks are different */ + tlen = get_file_size(outinfo.track[trknum].fname); + if (tlen == 0) + { + osd_printf_error("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname); + return std::errc::no_such_file_or_directory; + } + + outtoc.tracks[trknum].frames = tlen / (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + outinfo.track[trknum].offset = 0; + } + + if (outtoc.flags & CD_FLAG_MULTISESSION) + { + if (is_multibin) + { + if (outinfo.track[trknum].leadout == -1 && trknum + 1 < outtoc.numtrks && outtoc.tracks[trknum].session != outtoc.tracks[trknum+1].session) { - tlen = get_file_size(outinfo.track[trknum].fname); - if (tlen == 0) - { - printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str()); - return std::errc::no_such_file_or_directory; - } - 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); + /* add a standard lead-out to the last track before changing sessions */ + outinfo.track[trknum].leadout = outtoc.tracks[trknum].session == 0 ? 6750 : 2250; /* first session lead-out (1m30s0f) is longer than the rest (0m30s0f) */ } - else /* data files are different */ + + if (outinfo.track[trknum].leadin == -1 && trknum > 0 && outtoc.tracks[trknum].session != outtoc.tracks[trknum-1].session) { - tlen = get_file_size(outinfo.track[trknum].fname); - if (tlen == 0) - { - printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str()); - return std::errc::no_such_file_or_directory; - } - tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outtoc.tracks[trknum].frames = tlen; - outinfo.track[trknum].offset = 0; + /* add a standard lead-in to the first track of a new session */ + outinfo.track[trknum].leadin = 4500; /* lead-in (1m0s0f) */ } } else { - /* if we have the same filename as the next track, do it that way */ - if (outinfo.track[trknum].fname.compare(outinfo.track[trknum+1].fname)==0) + if (outinfo.track[trknum].leadout != -1) { - outtoc.tracks[trknum].frames = outinfo.track[trknum+1].idx0offs - outinfo.track[trknum].idx0offs; - - if (trknum == 0) // track 0 offset is 0 + /* + if a lead-out time is specified in a multisession CD then the size of the previous track needs to be trimmed + to use the lead-out time instead of the idx 0 of the next track + */ + const int endframes = outinfo.track[trknum].leadout - outinfo.track[trknum].idx[0]; + if (outtoc.tracks[trknum].frames >= endframes) { - outinfo.track[trknum].offset = 0; - } - else - { - 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 = endframes; /* trim track length */ - if (!outtoc.tracks[trknum].frames) - { - printf("ERROR: unable to determine size of track %d, missing INDEX 01 markers?\n", trknum+1); - return chd_file::error::INVALID_DATA; + if (trknum + 1 < outtoc.numtrks) + { + /* lead-out value becomes just the duration between the lead-out to the pre-gap of the next track */ + outinfo.track[trknum].leadout = outinfo.track[trknum+1].idx[0] - outinfo.track[trknum].leadout; + } } } - else /* data files are different */ + + if (trknum > 0 && outinfo.track[trknum-1].leadout != -1) { - tlen = get_file_size(outinfo.track[trknum].fname); - if (tlen == 0) + /* + ImgBurn bin/cue have dummy data to pad between the lead-out and the start of the next track. + DiscImageCreator img/cue does not have any data between the lead-out and the start of the next track. + + Detecting by extension is an awful way to handle this but there's no other way to determine what format + the data will be in since we don't know the exact length of the last track just from the cue. + */ + if (!core_filename_ends_with(outinfo.track[trknum-1].fname, ".img")) { - printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname.c_str()); - return std::errc::no_such_file_or_directory; + outtoc.tracks[trknum-1].padframes += outinfo.track[trknum-1].leadout; + outtoc.tracks[trknum].frames -= outinfo.track[trknum-1].leadout; + outinfo.track[trknum].offset += outinfo.track[trknum-1].leadout * (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); } - tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outtoc.tracks[trknum].frames = tlen; - outinfo.track[trknum].offset = 0; } } } } - /* - * Dreamcast patterns are identified by track types and number of tracks - */ - if (outtoc.numtrks > 4 && outtoc.tracks[outtoc.numtrks-1].pgtype == CD_TRACK_MODE1_RAW) + if (is_gdrom) { - if (outtoc.tracks[outtoc.numtrks-2].pgtype == CD_TRACK_AUDIO) - disc_pattern = TYPE_III_SPLIT; - else - disc_pattern = TYPE_III; + /* + * Strip pregaps from Redump tracks and adjust the LBA offset to match TOSEC layout + */ + for (trknum = 1; trknum < outtoc.numtrks; trknum++) + { + uint32_t this_pregap = outtoc.tracks[trknum].pregap; + uint32_t this_offset = this_pregap * (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + + outtoc.tracks[trknum-1].frames += this_pregap; + outtoc.tracks[trknum-1].splitframes += this_pregap; + + outinfo.track[trknum].offset += this_offset; + outtoc.tracks[trknum].frames -= this_pregap; + outinfo.track[trknum].idx[1] -= this_pregap; + + outtoc.tracks[trknum].pregap = 0; + outtoc.tracks[trknum].pgtype = 0; + } + + /* + * TOC now matches TOSEC layout, set LBA for every track with HIGH-DENSITY area @ LBA 45000 + */ + for (trknum = 1; trknum < outtoc.numtrks; trknum++) + { + if (outtoc.tracks[trknum].multicuearea == HIGH_DENSITY && outtoc.tracks[trknum-1].multicuearea == SINGLE_DENSITY) + { + outtoc.tracks[trknum].physframeofs = 45000; + int dif=outtoc.tracks[trknum].physframeofs-(outtoc.tracks[trknum-1].frames+outtoc.tracks[trknum-1].physframeofs); + outtoc.tracks[trknum-1].frames += dif; + outtoc.tracks[trknum-1].padframes = dif; + } + else + { + outtoc.tracks[trknum].physframeofs = outtoc.tracks[trknum-1].physframeofs + outtoc.tracks[trknum-1].frames; + } + } } - else if (outtoc.numtrks > 3) + + if (EXTRA_VERBOSE) { - if (outtoc.tracks[outtoc.numtrks-1].pgtype == CD_TRACK_AUDIO) - disc_pattern = TYPE_II; - else - disc_pattern = TYPE_III; + for (trknum = 0; trknum < outtoc.numtrks; trknum++) + { + osd_printf_verbose("session %d trk %d: %d frames @ offset %d, pad=%d, split=%d, area=%d, phys=%d, pregap=%d, pgtype=%d, pgdatasize=%d, idx0=%d, idx1=%d, dataframes=%d\n", + outtoc.tracks[trknum].session + 1, + trknum + 1, + outtoc.tracks[trknum].frames, + outinfo.track[trknum].offset, + outtoc.tracks[trknum].padframes, + outtoc.tracks[trknum].splitframes, + outtoc.tracks[trknum].multicuearea, + outtoc.tracks[trknum].physframeofs, + outtoc.tracks[trknum].pregap, + outtoc.tracks[trknum].pgtype, + outtoc.tracks[trknum].pgdatasize, + outinfo.track[trknum].idx[0], + outinfo.track[trknum].idx[1], + outtoc.tracks[trknum].frames - outtoc.tracks[trknum].padframes); + } } - else if (outtoc.numtrks == 3) + + return std::error_condition(); +} + +/*--------------------------------------------------------------------------------------- + is_gdicue - determine if CUE contains Redump multi-CUE format for Dreamcast GDI +----------------------------------------------------------------------------------------*/ + +/** + * Dreamcast GDI has two images on one disc, SINGLE-DENSITY and HIGH-DENSITY. + * + * Redump stores both images in a single .cue with a REM comment separating the images. + * This multi-cue format replaces the old flawed .gdi format. + * + * http://forum.redump.org/topic/19969/done-sega-dreamcast-multicue-gdi/ + * + * This function looks for strings "REM SINGLE-DENSITY AREA" & "REM HIGH-DENSITY AREA" + * indicating the Redump multi-cue format and therefore a Dreamcast GDI disc. + */ + +bool cdrom_file::is_gdicue(std::string_view tocfname) +{ + char token[512]; + bool has_rem_singledensity = false; + bool has_rem_highdensity = false; + std::string path = std::string(tocfname); + + FILE *infile = fopen(path.c_str(), "rt"); + if (!infile) { - disc_pattern = TYPE_I; + return false; } - /* - * Special handling for TYPE_III_SPLIT, pregap in last track contains 75 frames audio and 150 frames data - */ - if (disc_pattern == TYPE_III_SPLIT) + path = get_file_path(path); + + char linebuffer[512]; + memset(linebuffer, 0, sizeof(linebuffer)); + + while (!feof(infile)) { - assert(outtoc.tracks[outtoc.numtrks-1].pregap == 225); + if (!fgets(linebuffer, 511, infile)) + break; - // grow the AUDIO track into DATA track by 75 frames as per Pattern III - outtoc.tracks[outtoc.numtrks-2].frames += 225; - outtoc.tracks[outtoc.numtrks-2].padframes += 150; - outinfo.track[outtoc.numtrks-2].offset = 150 * (outtoc.tracks[outtoc.numtrks-2].datasize+outtoc.tracks[outtoc.numtrks-2].subsize); - outtoc.tracks[outtoc.numtrks-2].splitframes = 75; + int i = 0; - // skip the pregap when reading the DATA track - outtoc.tracks[outtoc.numtrks-1].frames -= 225; - outinfo.track[outtoc.numtrks-1].offset += 225 * (outtoc.tracks[outtoc.numtrks-1].datasize+outtoc.tracks[outtoc.numtrks-1].subsize); - } + TOKENIZE - /* - * Set LBA for every track with HIGH-DENSITY area @ LBA 45000 - */ - for (trknum = 1; trknum < outtoc.numtrks; trknum++) - { - if (outtoc.tracks[trknum].multicuearea == HIGH_DENSITY && outtoc.tracks[trknum-1].multicuearea == SINGLE_DENSITY) + if (!strcmp(token, "REM")) { - outtoc.tracks[trknum].physframeofs = 45000; - int dif=outtoc.tracks[trknum].physframeofs-(outtoc.tracks[trknum-1].frames+outtoc.tracks[trknum-1].physframeofs); - outtoc.tracks[trknum-1].frames += dif; - outtoc.tracks[trknum-1].padframes = dif; - } - else - { - outtoc.tracks[trknum].physframeofs = outtoc.tracks[trknum-1].physframeofs + outtoc.tracks[trknum-1].frames; + /* skip to actual data of REM command */ + while (i < std::size(linebuffer) && isspace((uint8_t)linebuffer[i])) + i++; + + if (!strncmp(linebuffer+i, "SINGLE-DENSITY AREA", 19)) + has_rem_singledensity = true; + else if (!strncmp(linebuffer+i, "HIGH-DENSITY AREA", 17)) + has_rem_highdensity = true; } } - if (EXTRA_VERBOSE) - for (trknum = 0; trknum < outtoc.numtrks; trknum++) - { - printf("trk %d: %d frames @ offset %d, pad=%d, split=%d, area=%d, phys=%d, pregap=%d, pgtype=%d, idx0=%d, idx1=%d, (true %d)\n", - trknum+1, - outtoc.tracks[trknum].frames, - outinfo.track[trknum].offset, - outtoc.tracks[trknum].padframes, - outtoc.tracks[trknum].splitframes, - outtoc.tracks[trknum].multicuearea, - outtoc.tracks[trknum].physframeofs, - outtoc.tracks[trknum].pregap, - outtoc.tracks[trknum].pgtype, - outinfo.track[trknum].idx0offs, - outinfo.track[trknum].idx1offs, - outtoc.tracks[trknum].frames - outtoc.tracks[trknum].padframes); - } + fclose(infile); - return std::error_condition(); + return has_rem_singledensity && has_rem_highdensity; } /*------------------------------------------------- @@ -2989,7 +2920,7 @@ std::error_condition cdrom_file::parse_gdicue(std::string_view tocfname, toc &ou std::error_condition cdrom_file::parse_toc(std::string_view tocfname, toc &outtoc, track_input_info &outinfo) { - static char token[512]; + char token[512]; auto pos = tocfname.rfind('.'); std::string tocfext = pos == std::string_view::npos ? std::string() : strmakelower(tocfname.substr(pos + 1)); @@ -3001,10 +2932,7 @@ std::error_condition cdrom_file::parse_toc(std::string_view tocfname, toc &outto if (tocfext == "cue") { - if (is_gdicue(tocfname)) - return parse_gdicue(tocfname, outtoc, outinfo); - else - return parse_cue(tocfname, outtoc, outinfo); + return parse_cue(tocfname, outtoc, outinfo); } if (tocfext == "nrg") @@ -3034,132 +2962,167 @@ std::error_condition cdrom_file::parse_toc(std::string_view tocfname, toc &outto int trknum = -1; char linebuffer[512]; + memset(linebuffer, 0, sizeof(linebuffer)); + while (!feof(infile)) { /* get the next line */ - fgets(linebuffer, 511, infile); + if (!fgets(linebuffer, 511, infile)) + break; - /* if EOF didn't hit, keep going */ - if (!feof(infile)) - { - int i = 0; + int i = 0; + TOKENIZE + + /* + Samples: https://github.com/cdrdao/cdrdao/tree/master/testtocs + + Unimplemented: + CD_TEXT + SILENCE + ZERO + FIFO + PREGAP + CATALOG + ISRC + */ + if (!strcmp(token, "NO")) + { TOKENIZE + if (!strcmp(token, "COPY")) + outtoc.tracks[trknum].control_flags &= ~CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED; + else if (!strcmp(token, "PRE_EMPHASIS")) + outtoc.tracks[trknum].control_flags &= ~CD_FLAG_CONTROL_PREEMPHASIS; + } + else if (!strcmp(token, "COPY")) + { + outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED; + } + else if (!strcmp(token, "PRE_EMPHASIS")) + { + outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_PREEMPHASIS; + } + else if (!strcmp(token, "TWO_CHANNEL_AUDIO")) + { + outtoc.tracks[trknum].control_flags &= ~CD_FLAG_CONTROL_4CH; + } + else if (!strcmp(token, "FOUR_CHANNEL_AUDIO")) + { + outtoc.tracks[trknum].control_flags |= CD_FLAG_CONTROL_4CH; + } + else if ((!strcmp(token, "DATAFILE")) || (!strcmp(token, "AUDIOFILE")) || (!strcmp(token, "FILE"))) + { + int f; - if ((!strcmp(token, "DATAFILE")) || (!strcmp(token, "AUDIOFILE")) || (!strcmp(token, "FILE"))) - { - int f; + /* found the data file for a track */ + TOKENIZE - /* found the data file for a track */ - TOKENIZE + /* keep the filename */ + outinfo.track[trknum].fname.assign(path).append(token); - /* keep the filename */ - outinfo.track[trknum].fname.assign(path).append(token); + /* get either the offset or the length */ + TOKENIZE - /* get either the offset or the length */ + if (!strcmp(token, "SWAP")) + { TOKENIZE - if (!strcmp(token, "SWAP")) - { - TOKENIZE + outinfo.track[trknum].swap = true; + } + else + { + outinfo.track[trknum].swap = false; + } - outinfo.track[trknum].swap = true; - } - else - { - outinfo.track[trknum].swap = false; - } + if (token[0] == '#') + { + /* it's a decimal offset, use it */ + f = strtoul(&token[1], nullptr, 10); + } + else if (isdigit((uint8_t)token[0])) + { + /* convert the time to an offset */ + f = msf_to_frames(token); - if (token[0] == '#') - { - /* it's a decimal offset, use it */ - f = strtoul(&token[1], nullptr, 10); - } - else if (isdigit((uint8_t)token[0])) - { - /* convert the time to an offset */ - f = msf_to_frames( token ); + f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + } + else + { + f = 0; + } - f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - } - else - { - f = 0; - } + outinfo.track[trknum].offset = f; - outinfo.track[trknum].offset = f; + TOKENIZE + + if (isdigit((uint8_t)token[0])) + { + // this could be the length or an offset from the previous field. + f = msf_to_frames(token); TOKENIZE if (isdigit((uint8_t)token[0])) { - // this could be the length or an offset from the previous field. - f = msf_to_frames( token ); - - TOKENIZE - - if (isdigit((uint8_t)token[0])) - { - // it was an offset. - f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + // it was an offset. + f *= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outinfo.track[trknum].offset += f; + outinfo.track[trknum].offset += f; - // this is the length. - f = msf_to_frames( token ); - } - } - else if( trknum == 0 && outinfo.track[trknum].offset != 0 ) - { - /* the 1st track might have a length with no offset */ - f = outinfo.track[trknum].offset / (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); - outinfo.track[trknum].offset = 0; + // this is the length. + f = msf_to_frames(token); } - else - { - /* guesstimate the track length? */ - f = 0; - } - - outtoc.tracks[trknum].frames = f; } - else if (!strcmp(token, "TRACK")) + else if (trknum == 0 && outinfo.track[trknum].offset != 0) { - trknum++; - - /* next token on the line is the track type */ - TOKENIZE + /* the 1st track might have a length with no offset */ + f = outinfo.track[trknum].offset / (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize); + outinfo.track[trknum].offset = 0; + } + else + { + /* guesstimate the track length? */ + f = 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].pgsub = CD_SUB_NONE; - outtoc.tracks[trknum].padframes = 0; + outtoc.tracks[trknum].frames = f; + } + else if (!strcmp(token, "TRACK")) + { + trknum++; - convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); - if (outtoc.tracks[trknum].datasize == 0) - { - fclose(infile); - printf("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); - return chd_file::error::UNSUPPORTED_FORMAT; - } + /* next token on the line is the track type */ + TOKENIZE - /* next (optional) token on the line is the subcode 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].pgsub = CD_SUB_NONE; + outtoc.tracks[trknum].padframes = 0; - convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); - } - else if (!strcmp(token, "START")) + convert_type_string_to_track_info(token, &outtoc.tracks[trknum]); + if (outtoc.tracks[trknum].datasize == 0) { - int frames; + fclose(infile); + osd_printf_error("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token); + return chd_file::error::UNSUPPORTED_FORMAT; + } - /* get index */ - TOKENIZE - frames = msf_to_frames( token ); + /* next (optional) token on the line is the subcode type */ + TOKENIZE - outtoc.tracks[trknum].pregap = frames; - } + convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); + } + else if (!strcmp(token, "START")) + { + int frames; + + /* get index */ + TOKENIZE + frames = msf_to_frames(token); + + outtoc.tracks[trknum].pregap = frames; } } @@ -3168,6 +3131,7 @@ std::error_condition cdrom_file::parse_toc(std::string_view tocfname, toc &outto /* store the number of tracks found */ outtoc.numtrks = trknum + 1; + outtoc.numsessions = 1; return std::error_condition(); } diff --git a/src/lib/util/cdrom.h b/src/lib/util/cdrom.h index 214a7ed39d6..b11ecb5b3ba 100644 --- a/src/lib/util/cdrom.h +++ b/src/lib/util/cdrom.h @@ -16,6 +16,12 @@ #include "ioprocs.h" #include "osdcore.h" +#include <algorithm> +#include <string> +#include <string_view> +#include <system_error> + + class cdrom_file { public: // tracks are padded to a multiple of this many frames @@ -24,6 +30,7 @@ public: static constexpr uint32_t MAX_TRACKS = 99; /* AFAIK the theoretical limit */ static constexpr uint32_t MAX_SECTOR_DATA = 2352; static constexpr uint32_t MAX_SUBCODE_DATA = 96; + static constexpr uint32_t MAX_INDEX = 99; static constexpr uint32_t FRAME_SIZE = MAX_SECTOR_DATA + MAX_SUBCODE_DATA; static constexpr uint32_t FRAMES_PER_HUNK = 8; @@ -53,29 +60,47 @@ public: enum { - CD_FLAG_GDROM = 0x00000001, // disc is a GD-ROM, all tracks should be stored with GD-ROM metadata - CD_FLAG_GDROMLE = 0x00000002 // legacy GD-ROM, with little-endian CDDA data + CD_FLAG_GDROM = 0x00000001, // disc is a GD-ROM, all tracks should be stored with GD-ROM metadata + CD_FLAG_GDROMLE = 0x00000002, // legacy GD-ROM, with little-endian CDDA data + CD_FLAG_MULTISESSION = 0x00000004, // multisession CD-ROM + }; + + enum + { + CD_FLAG_CONTROL_PREEMPHASIS = 1, + CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED = 2, + CD_FLAG_CONTROL_DATA_TRACK = 4, + CD_FLAG_CONTROL_4CH = 8, + }; + + enum + { + CD_FLAG_ADR_START_TIME = 1, + CD_FLAG_ADR_CATALOG_CODE, + CD_FLAG_ADR_ISRC_CODE, }; struct track_info { /* fields used by CHDMAN and in MAME */ - uint32_t trktype; /* track type */ - uint32_t subtype; /* subcode data type */ - uint32_t datasize; /* size of data in each sector of this track */ - uint32_t subsize; /* size of subchannel data in each sector of this track */ - uint32_t frames; /* number of frames in this track */ - uint32_t extraframes; /* number of "spillage" frames in this track */ - uint32_t pregap; /* number of pregap frames */ - uint32_t postgap; /* number of postgap frames */ - uint32_t pgtype; /* type of sectors in pregap */ - uint32_t pgsub; /* type of subchannel data in pregap */ - uint32_t pgdatasize; /* size of data in each sector of the pregap */ - uint32_t pgsubsize; /* size of subchannel data in each sector of the pregap */ + uint32_t trktype; /* track type */ + uint32_t subtype; /* subcode data type */ + uint32_t datasize; /* size of data in each sector of this track */ + uint32_t subsize; /* size of subchannel data in each sector of this track */ + uint32_t frames; /* number of frames in this track */ + uint32_t extraframes; /* number of "spillage" frames in this track */ + uint32_t pregap; /* number of pregap frames */ + uint32_t postgap; /* number of postgap frames */ + uint32_t pgtype; /* type of sectors in pregap */ + uint32_t pgsub; /* type of subchannel data in pregap */ + uint32_t pgdatasize; /* size of data in each sector of the pregap */ + uint32_t pgsubsize; /* size of subchannel data in each sector of the pregap */ + uint32_t control_flags; /* metadata flags associated with each track */ + uint32_t session; /* session number */ /* fields used in CHDMAN only */ uint32_t padframes; /* number of frames of padding to add to the end of the track; needed for GDI */ - uint32_t splitframes; /* number of frames to read from the next file; needed for Redump split-bin GDI */ + uint32_t splitframes; /* number of frames from the next file to add to the end of the current track after padding; needed for Redump split-bin GDI */ /* fields used in MAME/MESS only */ uint32_t logframeofs; /* logical frame of actual track data - offset by pregap size if pregap not physically present */ @@ -91,20 +116,21 @@ public: struct toc { uint32_t numtrks; /* number of tracks */ + uint32_t numsessions; /* number of sessions */ uint32_t flags; /* see FLAG_ above */ - track_info tracks[MAX_TRACKS]; + track_info tracks[MAX_TRACKS + 1]; }; struct track_input_entry { track_input_entry() { reset(); } - void reset() { fname.clear(); offset = idx0offs = idx1offs = 0; swap = false; } + void reset() { fname.clear(); offset = 0; leadin = leadout = -1; swap = false; std::fill(std::begin(idx), std::end(idx), -1); } std::string fname; // filename for each track uint32_t offset; // offset in the data file for each track bool swap; // data needs to be byte swapped - uint32_t idx0offs; - uint32_t idx1offs; + int32_t idx[MAX_INDEX + 1]; + int32_t leadin, leadout; // TODO: these should probably be their own tracks entirely }; struct track_input_info @@ -128,6 +154,7 @@ public: uint32_t get_track(uint32_t frame) const; uint32_t get_track_start(uint32_t track) const {return cdtoc.tracks[track == 0xaa ? cdtoc.numtrks : track].logframeofs; } uint32_t get_track_start_phys(uint32_t track) const { return cdtoc.tracks[track == 0xaa ? cdtoc.numtrks : track].physframeofs; } + uint32_t get_track_index(uint32_t frame) const; /* TOC utilities */ static std::error_condition parse_nero(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); @@ -135,10 +162,18 @@ public: static std::error_condition parse_gdi(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); static std::error_condition parse_cue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); static bool is_gdicue(std::string_view tocfname); - static std::error_condition parse_gdicue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); static std::error_condition parse_toc(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); + int get_last_session() const { return cdtoc.numsessions ? cdtoc.numsessions : 1; } int get_last_track() const { return cdtoc.numtrks; } - int get_adr_control(int track) const { return track == 0xaa || cdtoc.tracks[track].trktype == CD_TRACK_AUDIO ? 0x10 : 0x14; } + int get_adr_control(int track) const + { + if (track == 0xaa) + track = get_last_track() - 1; // use last track's flags + int adrctl = (CD_FLAG_ADR_START_TIME << 4) | (cdtoc.tracks[track].control_flags & 0x0f); + if (cdtoc.tracks[track].trktype != CD_TRACK_AUDIO) + adrctl |= CD_FLAG_CONTROL_DATA_TRACK; + return adrctl; + } int get_track_type(int track) const { return cdtoc.tracks[track].trktype; } const toc &get_toc() const { return cdtoc; } @@ -254,8 +289,8 @@ private: static std::string get_file_path(std::string &path); static uint64_t get_file_size(std::string_view filename); - static int tokenize( const char *linebuffer, int i, int linebuffersize, char *token, int tokensize ); - static int msf_to_frames( char *token ); + static int tokenize(const char *linebuffer, int i, int linebuffersize, char *token, int tokensize); + static int msf_to_frames(const char *token); static uint32_t parse_wav_sample(std::string_view filename, uint32_t *dataoffs); static uint16_t read_uint16(FILE *infile); static uint32_t read_uint32(FILE *infile); diff --git a/src/lib/util/chd.cpp b/src/lib/util/chd.cpp index 194884bfd9a..c05977a84eb 100644 --- a/src/lib/util/chd.cpp +++ b/src/lib/util/chd.cpp @@ -2,8 +2,6 @@ // copyright-holders:Aaron Giles /*************************************************************************** - chd.c - MAME Compressed Hunks of Data file format ***************************************************************************/ @@ -22,11 +20,13 @@ #include <zlib.h> +#include <algorithm> #include <cassert> #include <cstddef> #include <cstdlib> #include <ctime> #include <new> +#include <tuple> //************************************************************************** @@ -134,7 +134,7 @@ struct chd_file::metadata_hash // stream in bigendian order //------------------------------------------------- -inline util::sha1_t chd_file::be_read_sha1(const uint8_t *base)const +inline util::sha1_t chd_file::be_read_sha1(const uint8_t *base) const noexcept { util::sha1_t result; memcpy(&result.m_raw[0], base, sizeof(result.m_raw)); @@ -147,7 +147,7 @@ inline util::sha1_t chd_file::be_read_sha1(const uint8_t *base)const // stream in bigendian order //------------------------------------------------- -inline void chd_file::be_write_sha1(uint8_t *base, util::sha1_t value) +inline void chd_file::be_write_sha1(uint8_t *base, util::sha1_t value) noexcept { memcpy(base, &value.m_raw[0], sizeof(value.m_raw)); } @@ -155,45 +155,45 @@ inline void chd_file::be_write_sha1(uint8_t *base, util::sha1_t value) //------------------------------------------------- // file_read - read from the file at the given -// offset; on failure throw an error +// offset. //------------------------------------------------- -inline void chd_file::file_read(uint64_t offset, void *dest, uint32_t length) const +inline std::error_condition chd_file::file_read(uint64_t offset, void *dest, uint32_t length) const noexcept { // no file = failure - if (!m_file) - throw std::error_condition(error::NOT_OPEN); + if (UNEXPECTED(!m_file)) + return std::error_condition(error::NOT_OPEN); // seek and read - m_file->seek(offset, SEEK_SET); + std::error_condition err; + err = m_file->seek(offset, SEEK_SET); + if (UNEXPECTED(err)) + return err; size_t count; - std::error_condition err = m_file->read(dest, length, count); - if (err) - throw err; - else if (count != length) - throw std::error_condition(std::errc::io_error); // TODO: revisit this error code (happens if file is cut off) + std::tie(err, count) = read(*m_file, dest, length); + if (UNEXPECTED(!err && (count != length))) + return std::error_condition(std::errc::io_error); // TODO: revisit this error code (happens if file is truncated) + return err; } //------------------------------------------------- // file_write - write to the file at the given -// offset; on failure throw an error +// offset. //------------------------------------------------- -inline void chd_file::file_write(uint64_t offset, const void *source, uint32_t length) +inline std::error_condition chd_file::file_write(uint64_t offset, const void *source, uint32_t length) noexcept { // no file = failure - if (!m_file) - throw std::error_condition(error::NOT_OPEN); + if (UNEXPECTED(!m_file)) + return std::error_condition(error::NOT_OPEN); // seek and write - m_file->seek(offset, SEEK_SET); - size_t count; - std::error_condition err = m_file->write(source, length, count); - if (err) - throw err; - else if (count != length) - throw std::error_condition(std::errc::interrupted); // can theoretically happen if write is inuterrupted by a signal + std::error_condition err; + err = m_file->seek(offset, SEEK_SET); + if (UNEXPECTED(err)) + return err; + return write(*m_file, source, length).first; } @@ -205,21 +205,20 @@ inline void chd_file::file_write(uint64_t offset, const void *source, uint32_t l inline uint64_t chd_file::file_append(const void *source, uint32_t length, uint32_t alignment) { - std::error_condition err; - // no file = failure - if (!m_file) + if (UNEXPECTED(!m_file)) throw std::error_condition(error::NOT_OPEN); // seek to the end and align if necessary + std::error_condition err; err = m_file->seek(0, SEEK_END); - if (err) + if (UNEXPECTED(err)) throw err; if (alignment != 0) { uint64_t offset; err = m_file->tell(offset); - if (err) + if (UNEXPECTED(err)) throw err; uint32_t delta = offset % alignment; if (delta != 0) @@ -232,8 +231,8 @@ inline uint64_t chd_file::file_append(const void *source, uint32_t length, uint3 { uint32_t bytes_to_write = std::min<std::size_t>(sizeof(buffer), delta); size_t count; - err = m_file->write(buffer, bytes_to_write, count); - if (err) + std::tie(err, count) = write(*m_file, buffer, bytes_to_write); + if (UNEXPECTED(err)) throw err; delta -= count; } @@ -243,14 +242,11 @@ inline uint64_t chd_file::file_append(const void *source, uint32_t length, uint3 // write the real data uint64_t offset; err = m_file->tell(offset); - if (err) + if (UNEXPECTED(err)) throw err; - size_t count; - err = m_file->write(source, length, count); - if (err) + std::tie(err, std::ignore) = write(*m_file, source, length); + if (UNEXPECTED(err)) throw err; - else if (count != length) - throw std::error_condition(std::errc::interrupted); // can theoretically happen if write is interrupted by a signal return offset; } @@ -260,11 +256,14 @@ inline uint64_t chd_file::file_append(const void *source, uint32_t length, uint3 // necessary to represent all numbers 0..value //------------------------------------------------- -inline uint8_t chd_file::bits_for_value(uint64_t value) +inline uint8_t chd_file::bits_for_value(uint64_t value) noexcept { uint8_t result = 0; while (value != 0) - value >>= 1, result++; + { + value >>= 1; + result++; + } return result; } @@ -338,20 +337,14 @@ bool chd_file::parent_missing() const noexcept * @return A sha1_t. */ -util::sha1_t chd_file::sha1() +util::sha1_t chd_file::sha1() const noexcept { - try - { - // read the big-endian version - uint8_t rawbuf[sizeof(util::sha1_t)]; - file_read(m_sha1_offset, rawbuf, sizeof(rawbuf)); - return be_read_sha1(rawbuf); - } - catch (std::error_condition const &) - { - // on failure, return nullptr - return util::sha1_t::null; - } + // read the big-endian version + uint8_t rawbuf[sizeof(util::sha1_t)]; + std::error_condition err = file_read(m_sha1_offset, rawbuf, sizeof(rawbuf)); + if (UNEXPECTED(err)) + return util::sha1_t::null; // on failure, return null + return be_read_sha1(rawbuf); } /** @@ -367,22 +360,24 @@ util::sha1_t chd_file::sha1() * @return A sha1_t. */ -util::sha1_t chd_file::raw_sha1() +util::sha1_t chd_file::raw_sha1() const noexcept { try { // determine offset within the file for data-only - if (!m_rawsha1_offset) + if (UNEXPECTED(!m_rawsha1_offset)) throw std::error_condition(error::UNSUPPORTED_VERSION); // read the big-endian version uint8_t rawbuf[sizeof(util::sha1_t)]; - file_read(m_rawsha1_offset, rawbuf, sizeof(rawbuf)); + std::error_condition err = file_read(m_rawsha1_offset, rawbuf, sizeof(rawbuf)); + if (UNEXPECTED(err)) + throw err; return be_read_sha1(rawbuf); } catch (std::error_condition const &) { - // on failure, return nullptr + // on failure, return null return util::sha1_t::null; } } @@ -400,17 +395,19 @@ util::sha1_t chd_file::raw_sha1() * @return A sha1_t. */ -util::sha1_t chd_file::parent_sha1() +util::sha1_t chd_file::parent_sha1() const noexcept { try { // determine offset within the file - if (!m_parentsha1_offset) + if (UNEXPECTED(!m_parentsha1_offset)) throw std::error_condition(error::UNSUPPORTED_VERSION); // read the big-endian version uint8_t rawbuf[sizeof(util::sha1_t)]; - file_read(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); + std::error_condition err = file_read(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); + if (UNEXPECTED(err)) + throw err; return be_read_sha1(rawbuf); } catch (std::error_condition const &) @@ -441,49 +438,51 @@ std::error_condition chd_file::hunk_info(uint32_t hunknum, chd_codec_type &compr return std::error_condition(error::HUNK_OUT_OF_RANGE); // get the map pointer - uint8_t *rawmap; switch (m_version) { - // v3/v4 map entries - case 3: - case 4: - rawmap = &m_rawmap[16 * hunknum]; + // v3/v4 map entries + case 3: + case 4: + { + uint8_t const *const 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 = get_u16be(&rawmap[12]) + (rawmap[14] << 16); - break; + case V34_MAP_ENTRY_TYPE_COMPRESSED: + compressor = CHD_CODEC_ZLIB; + compbytes = get_u16be(&rawmap[12]) + (rawmap[14] << 16); + break; - case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: - compressor = CHD_CODEC_NONE; - compbytes = m_hunkbytes; - 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_MINI: + compressor = CHD_CODEC_MINI; + compbytes = 0; + break; - case V34_MAP_ENTRY_TYPE_SELF_HUNK: - compressor = CHD_CODEC_SELF; - compbytes = 0; - break; + case V34_MAP_ENTRY_TYPE_SELF_HUNK: + compressor = CHD_CODEC_SELF; + compbytes = 0; + break; - case V34_MAP_ENTRY_TYPE_PARENT_HUNK: - compressor = CHD_CODEC_PARENT; - compbytes = 0; - break; + case V34_MAP_ENTRY_TYPE_PARENT_HUNK: + compressor = CHD_CODEC_PARENT; + compbytes = 0; + break; } - break; + } + break; - // v5 map entries - case 5: - rawmap = &m_rawmap[m_mapentrybytes * hunknum]; + // v5 map entries + case 5: + { + uint8_t const *const rawmap = &m_rawmap[m_mapentrybytes * hunknum]; - // uncompressed case if (!compressed()) { + // uncompressed case if (get_u32be(&rawmap[0]) == 0) { compressor = CHD_CODEC_PARENT; @@ -494,12 +493,12 @@ std::error_condition chd_file::hunk_info(uint32_t hunknum, chd_codec_type &compr compressor = CHD_CODEC_NONE; compbytes = m_hunkbytes; } - break; } - - // compressed case - switch (rawmap[0]) + else { + // compressed case + switch (rawmap[0]) + { case COMPRESSION_TYPE_0: case COMPRESSION_TYPE_1: case COMPRESSION_TYPE_2: @@ -525,9 +524,12 @@ std::error_condition chd_file::hunk_info(uint32_t hunknum, chd_codec_type &compr default: return error::UNKNOWN_COMPRESSION; + } } - break; + } + break; } + return std::error_condition(); } @@ -541,20 +543,36 @@ std::error_condition chd_file::hunk_info(uint32_t hunknum, chd_codec_type &compr * @param rawdata The rawdata. */ -void chd_file::set_raw_sha1(util::sha1_t rawdata) +std::error_condition chd_file::set_raw_sha1(util::sha1_t rawdata) noexcept { + uint64_t const offset = (m_rawsha1_offset != 0) ? m_rawsha1_offset : m_sha1_offset; + assert(offset != 0); + // create a big-endian version uint8_t rawbuf[sizeof(util::sha1_t)]; be_write_sha1(rawbuf, rawdata); // write to the header - uint64_t offset = (m_rawsha1_offset != 0) ? m_rawsha1_offset : m_sha1_offset; - assert(offset != 0); - file_write(offset, rawbuf, sizeof(rawbuf)); + std::error_condition err = file_write(offset, rawbuf, sizeof(rawbuf)); + if (UNEXPECTED(err)) + return err; - // if we have a separate rawsha1_offset, update the full sha1 as well - if (m_rawsha1_offset != 0) - metadata_update_hash(); + try + { + // if we have a separate rawsha1_offset, update the full sha1 as well + if (m_rawsha1_offset != 0) + metadata_update_hash(); + } + catch (std::error_condition const &err) + { + return err; + } + catch (std::bad_alloc const &) + { + return std::errc::not_enough_memory; + } + + return std::error_condition(); } /** @@ -569,23 +587,24 @@ void chd_file::set_raw_sha1(util::sha1_t rawdata) * @param parent The parent. */ -void chd_file::set_parent_sha1(util::sha1_t parent) +std::error_condition chd_file::set_parent_sha1(util::sha1_t parent) noexcept { // if no file, fail - if (!m_file) - throw std::error_condition(error::INVALID_FILE); + if (UNEXPECTED(!m_file)) + return std::error_condition(error::INVALID_FILE); + + assert(m_parentsha1_offset != 0); // create a big-endian version uint8_t rawbuf[sizeof(util::sha1_t)]; be_write_sha1(rawbuf, parent); // write to the header - assert(m_parentsha1_offset != 0); - file_write(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); + return file_write(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); } /** - * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]) + * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]) * * @brief ------------------------------------------------- * create - create a new file with no parent using an existing opened file handle @@ -605,12 +624,12 @@ std::error_condition chd_file::create( uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, - chd_codec_type compression[4]) + const chd_codec_type (&compression)[4]) { // make sure we don't already have a file open - if (m_file) + if (UNEXPECTED(m_file)) return error::ALREADY_OPEN; - else if (!file) + else if (UNEXPECTED(!file)) return std::errc::invalid_argument; // set the header parameters @@ -626,7 +645,7 @@ std::error_condition chd_file::create( } /** - * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent) + * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent) * * @brief ------------------------------------------------- * create - create a new file with a parent using an existing opened file handle @@ -645,13 +664,13 @@ std::error_condition chd_file::create( util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, - chd_codec_type compression[4], + const chd_codec_type (&compression)[4], chd_file &parent) { // make sure we don't already have a file open - if (m_file) + if (UNEXPECTED(m_file)) return error::ALREADY_OPEN; - else if (!file) + else if (UNEXPECTED(!file)) return std::errc::invalid_argument; // set the header parameters @@ -667,7 +686,7 @@ std::error_condition chd_file::create( } /** - * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]) + * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]) * * @brief ------------------------------------------------- * create - create a new file with no parent using a filename @@ -687,23 +706,23 @@ std::error_condition chd_file::create( uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, - chd_codec_type compression[4]) + const chd_codec_type (&compression)[4]) { // make sure we don't already have a file open - if (m_file) + if (UNEXPECTED(m_file)) return error::ALREADY_OPEN; // create the new file util::core_file::ptr file; std::error_condition filerr = util::core_file::open(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, file); - if (filerr) + if (UNEXPECTED(filerr)) return filerr; // create the file normally, then claim the file std::error_condition chderr = create(std::move(file), logicalbytes, hunkbytes, unitbytes, compression); // if an error happened, close and delete the file - if (chderr) + if (UNEXPECTED(chderr)) { file.reset(); osd_file::remove(std::string(filename)); // FIXME: allow osd_file to use std::string_view @@ -712,7 +731,7 @@ std::error_condition chd_file::create( } /** - * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent) + * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent) * * @brief ------------------------------------------------- * create - create a new file with a parent using a filename @@ -731,24 +750,24 @@ std::error_condition chd_file::create( std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, - chd_codec_type compression[4], + const chd_codec_type (&compression)[4], chd_file &parent) { // make sure we don't already have a file open - if (m_file) + if (UNEXPECTED(m_file)) return error::ALREADY_OPEN; // create the new file util::core_file::ptr file; std::error_condition filerr = util::core_file::open(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, file); - if (filerr) + if (UNEXPECTED(filerr)) return filerr; // create the file normally, then claim the file std::error_condition chderr = create(std::move(file), logicalbytes, hunkbytes, compression, parent); // if an error happened, close and delete the file - if (chderr) + if (UNEXPECTED(chderr)) { file.reset(); osd_file::remove(std::string(filename)); // FIXME: allow osd_file to use std::string_view @@ -777,14 +796,14 @@ std::error_condition chd_file::open( const open_parent_func &open_parent) { // make sure we don't already have a file open - if (m_file) + if (UNEXPECTED(m_file)) return error::ALREADY_OPEN; // open the file const uint32_t openflags = writeable ? (OPEN_FLAG_READ | OPEN_FLAG_WRITE) : OPEN_FLAG_READ; util::core_file::ptr file; std::error_condition filerr = util::core_file::open(filename, openflags, file); - if (filerr) + if (UNEXPECTED(filerr)) return filerr; // now open the CHD @@ -812,9 +831,9 @@ std::error_condition chd_file::open( const open_parent_func &open_parent) { // make sure we don't already have a file open - if (m_file) + if (UNEXPECTED(m_file)) return error::ALREADY_OPEN; - else if (!file) + else if (UNEXPECTED(!file)) return std::errc::invalid_argument; // open the file @@ -848,7 +867,7 @@ void chd_file::close() m_hunkcount = 0; m_unitbytes = 0; m_unitcount = 0; - memset(m_compression, 0, sizeof(m_compression)); + std::fill(std::begin(m_compression), std::end(m_compression), 0); m_parent.reset(); m_parent_missing = false; @@ -873,6 +892,105 @@ void chd_file::close() m_cachehunk = ~0; } +std::error_condition chd_file::codec_process_hunk(uint32_t hunknum) +{ + // punt if no file + if (UNEXPECTED(!m_file)) + return std::error_condition(error::NOT_OPEN); + + // return an error if out of range + if (UNEXPECTED(hunknum >= m_hunkcount)) + return std::error_condition(error::HUNK_OUT_OF_RANGE); + + // wrap this for clean reporting + try + { + // get a pointer to the map entry + switch (m_version) + { + // v3/v4 map entries + case 3: + case 4: + { + uint8_t const *const rawmap = &m_rawmap[16 * hunknum]; + uint64_t const blockoffs = get_u64be(&rawmap[0]); + switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK) + { + case V34_MAP_ENTRY_TYPE_COMPRESSED: + { + uint32_t const blocklen = get_u16be(&rawmap[12]) | (uint32_t(rawmap[14]) << 16); + std::error_condition err = file_read(blockoffs, &m_compressed[0], blocklen); + if (UNEXPECTED(err)) + return err; + m_decompressor[0]->process(&m_compressed[0], blocklen); + return std::error_condition(); + } + + case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: + case V34_MAP_ENTRY_TYPE_MINI: + return std::error_condition(error::UNSUPPORTED_FORMAT); + + case V34_MAP_ENTRY_TYPE_SELF_HUNK: + return codec_process_hunk(blockoffs); + + case V34_MAP_ENTRY_TYPE_PARENT_HUNK: + if (UNEXPECTED(m_parent_missing)) + return std::error_condition(error::REQUIRES_PARENT); + return m_parent->codec_process_hunk(blockoffs); + } + } + break; + + // v5 map entries + case 5: + { + if (UNEXPECTED(!compressed())) + return std::error_condition(error::UNSUPPORTED_FORMAT); + + // compressed case + uint8_t const *const rawmap = &m_rawmap[m_mapentrybytes * hunknum]; + uint32_t const blocklen = get_u24be(&rawmap[1]); + uint64_t const blockoffs = get_u48be(&rawmap[4]); + switch (rawmap[0]) + { + case COMPRESSION_TYPE_0: + case COMPRESSION_TYPE_1: + case COMPRESSION_TYPE_2: + case COMPRESSION_TYPE_3: + { + std::error_condition err = file_read(blockoffs, &m_compressed[0], blocklen); + if (UNEXPECTED(err)) + return err; + auto &decompressor = *m_decompressor[rawmap[0]]; + decompressor.process(&m_compressed[0], blocklen); + return std::error_condition(); + } + + case COMPRESSION_NONE: + return std::error_condition(error::UNSUPPORTED_FORMAT); + + case COMPRESSION_SELF: + return codec_process_hunk(blockoffs); + + case COMPRESSION_PARENT: + if (UNEXPECTED(m_parent_missing)) + return std::error_condition(error::REQUIRES_PARENT); + return m_parent->codec_process_hunk(blockoffs / (m_parent->hunk_bytes() / m_parent->unit_bytes())); + } + break; + } + } + + // if we get here, the map contained an unsupported block type + return std::error_condition(error::INVALID_DATA); + } + catch (std::error_condition const &err) + { + // just return errors + return err; + } +} + /** * @fn std::error_condition chd_file::read_hunk(uint32_t hunknum, void *buffer) * @@ -893,126 +1011,148 @@ void chd_file::close() * @param hunknum The hunknum. * @param [in,out] buffer If non-null, the buffer. * - * @return The hunk. + * @return An error condition. */ std::error_condition chd_file::read_hunk(uint32_t hunknum, void *buffer) { + // punt if no file + if (UNEXPECTED(!m_file)) + return std::error_condition(error::NOT_OPEN); + + // return an error if out of range + if (UNEXPECTED(hunknum >= m_hunkcount)) + return std::error_condition(error::HUNK_OUT_OF_RANGE); + + auto *const dest = reinterpret_cast<uint8_t *>(buffer); + // wrap this for clean reporting try { - // punt if no file - if (!m_file) - throw std::error_condition(error::NOT_OPEN); - - // return an error if out of range - if (hunknum >= m_hunkcount) - throw std::error_condition(error::HUNK_OUT_OF_RANGE); - // get a pointer to the map entry - uint64_t blockoffs; - uint32_t blocklen; - util::crc32_t blockcrc; - uint8_t *rawmap; - auto *dest = reinterpret_cast<uint8_t *>(buffer); switch (m_version) { - // v3/v4 map entries - case 3: - case 4: - rawmap = &m_rawmap[16 * hunknum]; - blockoffs = get_u64be(&rawmap[0]); - blockcrc = get_u32be(&rawmap[8]); + // v3/v4 map entries + case 3: + case 4: + { + uint8_t const *const rawmap = &m_rawmap[16 * hunknum]; + uint64_t const blockoffs = get_u64be(&rawmap[0]); + util::crc32_t const blockcrc = get_u32be(&rawmap[8]); + bool const nocrc = rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC; switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK) { - case V34_MAP_ENTRY_TYPE_COMPRESSED: - blocklen = get_u16be(&rawmap[12]) + (rawmap[14] << 16); - file_read(blockoffs, &m_compressed[0], blocklen); + case V34_MAP_ENTRY_TYPE_COMPRESSED: + { + uint32_t const blocklen = get_u16be(&rawmap[12]) | (uint32_t(rawmap[14]) << 16); + std::error_condition err = file_read(blockoffs, &m_compressed[0], blocklen); + if (UNEXPECTED(err)) + return err; m_decompressor[0]->decompress(&m_compressed[0], blocklen, dest, m_hunkbytes); - if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && dest != nullptr && util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc) - throw std::error_condition(error::DECOMPRESSION_ERROR); + if (UNEXPECTED(!nocrc && (util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc))) + return std::error_condition(error::DECOMPRESSION_ERROR); return std::error_condition(); + } - case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: - file_read(blockoffs, dest, m_hunkbytes); - if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc) - throw std::error_condition(error::DECOMPRESSION_ERROR); + case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: + { + std::error_condition err = file_read(blockoffs, dest, m_hunkbytes); + if (UNEXPECTED(err)) + return err; + if (UNEXPECTED(!nocrc && (util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc))) + return std::error_condition(error::DECOMPRESSION_ERROR); return std::error_condition(); + } - case V34_MAP_ENTRY_TYPE_MINI: - put_u64be(dest, blockoffs); - for (uint32_t bytes = 8; bytes < m_hunkbytes; bytes++) - dest[bytes] = dest[bytes - 8]; - if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc) - throw std::error_condition(error::DECOMPRESSION_ERROR); - return std::error_condition(); + case V34_MAP_ENTRY_TYPE_MINI: + put_u64be(dest, blockoffs); + for (uint32_t bytes = 8; bytes < m_hunkbytes; bytes++) + dest[bytes] = dest[bytes - 8]; + if (UNEXPECTED(!nocrc && (util::crc32_creator::simple(dest, m_hunkbytes) != blockcrc))) + return std::error_condition(error::DECOMPRESSION_ERROR); + return std::error_condition(); - case V34_MAP_ENTRY_TYPE_SELF_HUNK: - return read_hunk(blockoffs, dest); + case V34_MAP_ENTRY_TYPE_SELF_HUNK: + return read_hunk(blockoffs, dest); - case V34_MAP_ENTRY_TYPE_PARENT_HUNK: - if (m_parent_missing) - throw std::error_condition(error::REQUIRES_PARENT); - return m_parent->read_hunk(blockoffs, dest); + case V34_MAP_ENTRY_TYPE_PARENT_HUNK: + if (UNEXPECTED(m_parent_missing)) + return std::error_condition(error::REQUIRES_PARENT); + return m_parent->read_hunk(blockoffs, dest); } - break; + } + break; - // v5 map entries - case 5: - rawmap = &m_rawmap[m_mapentrybytes * hunknum]; + // v5 map entries + case 5: + { + uint8_t const *const rawmap = &m_rawmap[m_mapentrybytes * hunknum]; - // uncompressed case if (!compressed()) { - blockoffs = mulu_32x32(get_u32be(rawmap), m_hunkbytes); + // uncompressed case + uint64_t const blockoffs = mulu_32x32(get_u32be(rawmap), m_hunkbytes); if (blockoffs != 0) - file_read(blockoffs, dest, m_hunkbytes); - else if (m_parent_missing) - throw std::error_condition(error::REQUIRES_PARENT); + return file_read(blockoffs, dest, m_hunkbytes); + else if (UNEXPECTED(m_parent_missing)) + return std::error_condition(error::REQUIRES_PARENT); else if (m_parent) - m_parent->read_hunk(hunknum, dest); + return m_parent->read_hunk(hunknum, dest); else memset(dest, 0, m_hunkbytes); return std::error_condition(); } - - // compressed case - blocklen = get_u24be(&rawmap[1]); - blockoffs = get_u48be(&rawmap[4]); - blockcrc = get_u16be(&rawmap[10]); - switch (rawmap[0]) + else { + // compressed case + uint32_t const blocklen = get_u24be(&rawmap[1]); + uint64_t const blockoffs = get_u48be(&rawmap[4]); + util::crc16_t const blockcrc = get_u16be(&rawmap[10]); + switch (rawmap[0]) + { case COMPRESSION_TYPE_0: case COMPRESSION_TYPE_1: case COMPRESSION_TYPE_2: case COMPRESSION_TYPE_3: - file_read(blockoffs, &m_compressed[0], blocklen); - m_decompressor[rawmap[0]]->decompress(&m_compressed[0], blocklen, dest, m_hunkbytes); - if (!m_decompressor[rawmap[0]]->lossy() && dest != nullptr && util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc) - throw std::error_condition(error::DECOMPRESSION_ERROR); - if (m_decompressor[rawmap[0]]->lossy() && util::crc16_creator::simple(&m_compressed[0], blocklen) != blockcrc) - throw std::error_condition(error::DECOMPRESSION_ERROR); - return std::error_condition(); + { + std::error_condition err = file_read(blockoffs, &m_compressed[0], blocklen); + if (UNEXPECTED(err)) + return err; + auto &decompressor = *m_decompressor[rawmap[0]]; + decompressor.decompress(&m_compressed[0], blocklen, dest, m_hunkbytes); + util::crc16_t const calculated = !decompressor.lossy() + ? util::crc16_creator::simple(dest, m_hunkbytes) + : util::crc16_creator::simple(&m_compressed[0], blocklen); + if (UNEXPECTED(calculated != blockcrc)) + return std::error_condition(error::DECOMPRESSION_ERROR); + return std::error_condition(); + } case COMPRESSION_NONE: - file_read(blockoffs, dest, m_hunkbytes); - if (util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc) - throw std::error_condition(error::DECOMPRESSION_ERROR); - return std::error_condition(); + { + std::error_condition err = file_read(blockoffs, dest, m_hunkbytes); + if (UNEXPECTED(err)) + return err; + if (UNEXPECTED(util::crc16_creator::simple(dest, m_hunkbytes) != blockcrc)) + return std::error_condition(error::DECOMPRESSION_ERROR); + return std::error_condition(); + } case COMPRESSION_SELF: return read_hunk(blockoffs, dest); case COMPRESSION_PARENT: - if (m_parent_missing) - throw std::error_condition(error::REQUIRES_PARENT); - return m_parent->read_bytes(uint64_t(blockoffs) * uint64_t(m_parent->unit_bytes()), dest, m_hunkbytes); + if (UNEXPECTED(m_parent_missing)) + return std::error_condition(error::REQUIRES_PARENT); + return m_parent->read_bytes(blockoffs * m_parent->unit_bytes(), dest, m_hunkbytes); + } } break; + } } - // if we get here, something was wrong - throw std::error_condition(std::errc::io_error); + // if we get here, the map contained an unsupported block type + return std::error_condition(error::INVALID_DATA); } catch (std::error_condition const &err) { @@ -1042,68 +1182,73 @@ std::error_condition chd_file::read_hunk(uint32_t hunknum, void *buffer) std::error_condition chd_file::write_hunk(uint32_t hunknum, const void *buffer) { - // wrap this for clean reporting - try - { - // punt if no file - if (!m_file) - throw std::error_condition(error::NOT_OPEN); + // punt if no file + if (UNEXPECTED(!m_file)) + return std::error_condition(error::NOT_OPEN); - // return an error if out of range - if (hunknum >= m_hunkcount) - throw std::error_condition(error::HUNK_OUT_OF_RANGE); + // return an error if out of range + if (UNEXPECTED(hunknum >= m_hunkcount)) + return std::error_condition(error::HUNK_OUT_OF_RANGE); - // if not writeable, fail - if (!m_allow_writes) - throw std::error_condition(error::FILE_NOT_WRITEABLE); + // if not writeable, fail + if (UNEXPECTED(!m_allow_writes)) + return std::error_condition(error::FILE_NOT_WRITEABLE); - // uncompressed writes only via this interface - if (compressed()) - throw std::error_condition(error::FILE_NOT_WRITEABLE); + // uncompressed writes only via this interface + if (UNEXPECTED(compressed())) + return std::error_condition(error::FILE_NOT_WRITEABLE); - // see if we have allocated the space on disk for this hunk - uint8_t *rawmap = &m_rawmap[hunknum * 4]; - uint32_t rawentry = get_u32be(rawmap); + // see if we have allocated the space on disk for this hunk + uint8_t *const rawmap = &m_rawmap[hunknum * 4]; + uint32_t rawentry = get_u32be(rawmap); - // if not, allocate one now - if (rawentry == 0) + // if not, allocate one now + if (rawentry == 0) + { + // first make sure we need to allocate it + bool all_zeros = true; + const auto *scan = reinterpret_cast<const uint32_t *>(buffer); + for (uint32_t index = 0; index < m_hunkbytes / 4; index++) { - // first make sure we need to allocate it - bool all_zeros = true; - const auto *scan = reinterpret_cast<const uint32_t *>(buffer); - for (uint32_t index = 0; index < m_hunkbytes / 4; index++) - if (scan[index] != 0) - { - all_zeros = false; - break; - } + if (scan[index] != 0) + { + all_zeros = false; + break; + } + } - // if it's all zeros, do nothing more - if (all_zeros) - return std::error_condition(); + // if it's all zeros, do nothing more + if (all_zeros) + return std::error_condition(); + // wrap this for clean reporting + try + { // 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 - put_u32be(rawmap, rawentry); - file_write(m_mapoffset + hunknum * 4, rawmap, 4); - - // update the cached hunk if we just wrote it - if (hunknum == m_cachehunk && buffer != &m_cache[0]) - memcpy(&m_cache[0], buffer, m_hunkbytes); } - else + catch (std::error_condition const &err) { - // otherwise, just overwrite - file_write(uint64_t(rawentry) * uint64_t(m_hunkbytes), buffer, m_hunkbytes); + // just return errors + return err; } + + // write the map entry back + put_u32be(rawmap, rawentry); + std::error_condition err = file_write(m_mapoffset + hunknum * 4, rawmap, 4); + if (UNEXPECTED(err)) + return err; + + // update the cached hunk if we just wrote it + if (hunknum == m_cachehunk && buffer != &m_cache[0]) + memcpy(&m_cache[0], buffer, m_hunkbytes); + return std::error_condition(); } - catch (std::error_condition const &err) + else { - // just return errors - return err; + // otherwise, just overwrite + return file_write(uint64_t(rawentry) * uint64_t(m_hunkbytes), buffer, m_hunkbytes); } } @@ -1163,36 +1308,36 @@ std::error_condition chd_file::write_units(uint64_t unitnum, const void *buffer, std::error_condition chd_file::read_bytes(uint64_t offset, void *buffer, uint32_t bytes) { // iterate over hunks - uint32_t first_hunk = offset / m_hunkbytes; - uint32_t last_hunk = (offset + bytes - 1) / m_hunkbytes; + uint32_t const first_hunk = offset / m_hunkbytes; + uint32_t const last_hunk = (offset + bytes - 1) / m_hunkbytes; auto *dest = reinterpret_cast<uint8_t *>(buffer); for (uint32_t curhunk = first_hunk; curhunk <= last_hunk; curhunk++) { // determine start/end boundaries - uint32_t startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; - uint32_t 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 - std::error_condition err; - if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk) - err = read_hunk(curhunk, dest); + uint32_t const startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; + uint32_t const endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1); - // otherwise, read from the cache + if ((startoffs == 0) && (endoffs == m_hunkbytes - 1) && (curhunk != m_cachehunk)) + { + // if it's a full block, just read directly from disk unless it's the cached hunk + std::error_condition err = read_hunk(curhunk, dest); + if (UNEXPECTED(err)) + return err; + } else { + // otherwise, read from the cache if (curhunk != m_cachehunk) { - err = read_hunk(curhunk, &m_cache[0]); - if (err) + std::error_condition err = read_hunk(curhunk, &m_cache[0]); + if (UNEXPECTED(err)) return err; m_cachehunk = curhunk; } memcpy(dest, &m_cache[startoffs], endoffs + 1 - startoffs); } - // handle errors and advance - if (err) - return err; + // advance dest += endoffs + 1 - startoffs; } return std::error_condition(); @@ -1216,27 +1361,28 @@ std::error_condition chd_file::read_bytes(uint64_t offset, void *buffer, uint32_ std::error_condition chd_file::write_bytes(uint64_t offset, const void *buffer, uint32_t bytes) { // iterate over hunks - uint32_t first_hunk = offset / m_hunkbytes; - uint32_t last_hunk = (offset + bytes - 1) / m_hunkbytes; - const auto *source = reinterpret_cast<const uint8_t *>(buffer); + uint32_t const first_hunk = offset / m_hunkbytes; + uint32_t const last_hunk = (offset + bytes - 1) / m_hunkbytes; + auto const *source = reinterpret_cast<uint8_t const *>(buffer); for (uint32_t curhunk = first_hunk; curhunk <= last_hunk; curhunk++) { // determine start/end boundaries - uint32_t startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; - uint32_t endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1); + uint32_t const startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; + uint32_t const 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 std::error_condition err; - if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk) + if ((startoffs == 0) && (endoffs == m_hunkbytes - 1) && (curhunk != m_cachehunk)) + { + // if it's a full block, just write directly to disk unless it's the cached hunk err = write_hunk(curhunk, source); - - // otherwise, write from the cache + } else { + // otherwise, write from the cache if (curhunk != m_cachehunk) { err = read_hunk(curhunk, &m_cache[0]); - if (err) + if (UNEXPECTED(err)) return err; m_cachehunk = curhunk; } @@ -1245,7 +1391,7 @@ std::error_condition chd_file::write_bytes(uint64_t offset, const void *buffer, } // handle errors and advance - if (err) + if (UNEXPECTED(err)) return err; source += endoffs + 1 - startoffs; } @@ -1266,29 +1412,20 @@ std::error_condition chd_file::write_bytes(uint64_t offset, const void *buffer, * @param searchindex The searchindex. * @param [in,out] output The output. * - * @return The metadata. + * @return An error condition. */ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_t searchindex, std::string &output) { - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - return std::error_condition(error::METADATA_NOT_FOUND); - - // read the metadata - output.assign(metaentry.length, '\0'); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); - return std::error_condition(); - } - catch (std::error_condition const &err) - { - // just return errors + // if we didn't find it, just return + metadata_entry metaentry; + if (std::error_condition err = metadata_find(searchtag, searchindex, metaentry)) return err; - } + + // read the metadata + try { output.assign(metaentry.length, '\0'); } + catch (std::bad_alloc const &) { return std::errc::not_enough_memory; } + return file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); } /** @@ -1303,29 +1440,20 @@ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_ * @param searchindex The searchindex. * @param [in,out] output The output. * - * @return The metadata. + * @return An error condition. */ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_t searchindex, std::vector<uint8_t> &output) { - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw std::error_condition(error::METADATA_NOT_FOUND); - - // read the metadata - output.resize(metaentry.length); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); - return std::error_condition(); - } - catch (std::error_condition const &err) - { - // just return errors + // if we didn't find it, just return + metadata_entry metaentry; + if (std::error_condition err = metadata_find(searchtag, searchindex, metaentry)) return err; - } + + // read the metadata + try { output.resize(metaentry.length); } + catch (std::bad_alloc const &) { return std::errc::not_enough_memory; } + return file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); } /** @@ -1342,29 +1470,19 @@ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_ * @param outputlen The outputlen. * @param [in,out] resultlen The resultlen. * - * @return The metadata. + * @return An error condition. */ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_t searchindex, void *output, uint32_t outputlen, uint32_t &resultlen) { - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw std::error_condition(error::METADATA_NOT_FOUND); - - // read the metadata - resultlen = metaentry.length; - file_read(metaentry.offset + METADATA_HEADER_SIZE, output, std::min(outputlen, resultlen)); - return std::error_condition(); - } - catch (std::error_condition const &err) - { - // just return errors + // if we didn't find it, just return + metadata_entry metaentry; + if (std::error_condition err = metadata_find(searchtag, searchindex, metaentry)) return err; - } + + // read the metadata + resultlen = metaentry.length; + return file_read(metaentry.offset + METADATA_HEADER_SIZE, output, std::min(outputlen, resultlen)); } /** @@ -1381,31 +1499,28 @@ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_ * @param [in,out] resulttag The resulttag. * @param [in,out] resultflags The resultflags. * - * @return The metadata. + * @return An error condition. */ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_t searchindex, std::vector<uint8_t> &output, chd_metadata_tag &resulttag, uint8_t &resultflags) { - // wrap this for clean reporting - try - { - // if we didn't find it, just return - metadata_entry metaentry; - if (!metadata_find(searchtag, searchindex, metaentry)) - throw std::error_condition(error::METADATA_NOT_FOUND); - - // read the metadata - output.resize(metaentry.length); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); - resulttag = metaentry.metatag; - resultflags = metaentry.flags; - return std::error_condition(); - } - catch (std::error_condition const &err) - { - // just return errors + std::error_condition err; + + // if we didn't find it, just return + metadata_entry metaentry; + err = metadata_find(searchtag, searchindex, metaentry); + if (err) return err; - } + + // read the metadata + try { output.resize(metaentry.length); } + catch (std::bad_alloc const &) { return std::errc::not_enough_memory; } + err = file_read(metaentry.offset + METADATA_HEADER_SIZE, &output[0], metaentry.length); + if (UNEXPECTED(err)) + return err; + resulttag = metaentry.metatag; + resultflags = metaentry.flags; + return std::error_condition(); } /** @@ -1426,40 +1541,52 @@ std::error_condition chd_file::read_metadata(chd_metadata_tag searchtag, uint32_ std::error_condition chd_file::write_metadata(chd_metadata_tag metatag, uint32_t metaindex, const void *inputbuf, uint32_t inputlen, uint8_t flags) { - // wrap this for clean reporting - try + // must write at least 1 byte and no more than 16MB + if (UNEXPECTED((inputlen < 1) || (inputlen >= 16 * 1024 * 1024))) + return std::error_condition(std::errc::invalid_argument); + + // find the entry if it already exists + metadata_entry metaentry; + bool finished = false; + std::error_condition err = metadata_find(metatag, metaindex, metaentry); + if (!err) { - // must write at least 1 byte and no more than 16MB - if (inputlen < 1 || inputlen >= 16 * 1024 * 1024) - return std::error_condition(std::errc::invalid_argument); - - // find the entry if it already exists - metadata_entry metaentry; - bool finished = false; - if (metadata_find(metatag, metaindex, metaentry)) + if (inputlen <= metaentry.length) { // if the new data fits over the old data, just overwrite - if (inputlen <= metaentry.length) - { - file_write(metaentry.offset + METADATA_HEADER_SIZE, inputbuf, inputlen); - - // if the lengths don't match, we need to update the length in our header - if (inputlen != metaentry.length) - { - uint8_t length[3]; - put_u24be(length, inputlen); - file_write(metaentry.offset + 5, length, sizeof(length)); - } + err = file_write(metaentry.offset + METADATA_HEADER_SIZE, inputbuf, inputlen); + if (UNEXPECTED(err)) + return err; - // indicate we did everything - finished = true; + // if the lengths don't match, we need to update the length in our header + if (inputlen != metaentry.length) + { + uint8_t length[3]; + put_u24be(length, inputlen); + err = file_write(metaentry.offset + 5, length, sizeof(length)); + if (UNEXPECTED(err)) + return err; } + // indicate we did everything + finished = true; + } + else + { // if it doesn't fit, unlink the current entry - else - metadata_set_previous_next(metaentry.prev, metaentry.next); + err = metadata_set_previous_next(metaentry.prev, metaentry.next); + if (UNEXPECTED(err)) + return err; } + } + else if (UNEXPECTED(err != error::METADATA_NOT_FOUND)) + { + return err; + } + // wrap this for clean reporting + try + { // if not yet done, create a new entry and append if (!finished) { @@ -1475,7 +1602,9 @@ std::error_condition chd_file::write_metadata(chd_metadata_tag metatag, uint32_t file_append(inputbuf, inputlen); // set the previous entry to point to us - metadata_set_previous_next(metaentry.prev, offset); + err = metadata_set_previous_next(metaentry.prev, offset); + if (UNEXPECTED(err)) + return err; } // update the hash @@ -1487,6 +1616,10 @@ std::error_condition chd_file::write_metadata(chd_metadata_tag metatag, uint32_t // return any errors return err; } + catch (std::bad_alloc const &) + { + return std::errc::not_enough_memory; + } } /** @@ -1507,23 +1640,13 @@ std::error_condition chd_file::write_metadata(chd_metadata_tag metatag, uint32_t std::error_condition chd_file::delete_metadata(chd_metadata_tag metatag, uint32_t metaindex) { - // wrap this for clean reporting - try - { - // find the entry - metadata_entry metaentry; - if (!metadata_find(metatag, metaindex, metaentry)) - throw std::error_condition(error::METADATA_NOT_FOUND); - - // point the previous to the next, unlinking us - metadata_set_previous_next(metaentry.prev, metaentry.next); - return std::error_condition(); - } - catch (std::error_condition const &err) - { - // return any errors + // find the entry + metadata_entry metaentry; + if (std::error_condition err = metadata_find(metatag, metaindex, metaentry)) return err; - } + + // point the previous to the next, unlinking us + return metadata_set_previous_next(metaentry.prev, metaentry.next); } /** @@ -1542,34 +1665,32 @@ std::error_condition chd_file::delete_metadata(chd_metadata_tag metatag, uint32_ std::error_condition chd_file::clone_all_metadata(chd_file &source) { - // wrap this for clean reporting - try + // iterate over metadata entries in the source + std::vector<uint8_t> filedata; + metadata_entry metaentry; + metaentry.metatag = 0; + metaentry.length = 0; + metaentry.next = 0; + metaentry.flags = 0; + std::error_condition err; + for (err = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); !err; err = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) { - // iterate over metadata entries in the source - std::vector<uint8_t> filedata; - metadata_entry metaentry; - metaentry.metatag = 0; - metaentry.length = 0; - metaentry.next = 0; - metaentry.flags = 0; - for (bool has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) - { - // read the metadata item - filedata.resize(metaentry.length); - source.file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length); - - // write it to the destination - std::error_condition err = write_metadata(metaentry.metatag, (uint32_t)-1, &filedata[0], metaentry.length, metaentry.flags); - if (err) - throw err; - } - return std::error_condition(); + // read the metadata item + try { filedata.resize(metaentry.length); } + catch (std::bad_alloc const &) { return std::errc::not_enough_memory; } + err = source.file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length); + if (UNEXPECTED(err)) + return err; + + // write it to the destination + err = write_metadata(metaentry.metatag, (uint32_t)-1, &filedata[0], metaentry.length, metaentry.flags); + if (UNEXPECTED(err)) + return err; } - catch (std::error_condition const &err) - { - // return any errors + if (err == error::METADATA_NOT_FOUND) + return std::error_condition(); + else return err; - } } /** @@ -1595,15 +1716,18 @@ util::sha1_t chd_file::compute_overall_sha1(util::sha1_t rawsha1) std::vector<uint8_t> filedata; std::vector<metadata_hash> hasharray; metadata_entry metaentry; - for (bool has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) + std::error_condition err; + for (err = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); !err; err = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) { // if not checksumming, continue - if ((metaentry.flags & CHD_MDFLAGS_CHECKSUM) == 0) + if (!(metaentry.flags & CHD_MDFLAGS_CHECKSUM)) continue; // allocate memory and read the data filedata.resize(metaentry.length); - file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length); + err = file_read(metaentry.offset + METADATA_HEADER_SIZE, &filedata[0], metaentry.length); + if (UNEXPECTED(err)) + throw err; // create an entry for this metadata and add it metadata_hash hashentry; @@ -1611,6 +1735,8 @@ util::sha1_t chd_file::compute_overall_sha1(util::sha1_t rawsha1) hashentry.sha1 = util::sha1_creator::simple(&filedata[0], metaentry.length); hasharray.push_back(hashentry); } + if (err != error::METADATA_NOT_FOUND) + throw err; // sort the array if (!hasharray.empty()) @@ -1772,7 +1898,7 @@ uint32_t chd_file::guess_unitbytes() void chd_file::parse_v3_header(uint8_t *rawheader, util::sha1_t &parentsha1) { // verify header length - if (get_u32be(&rawheader[8]) != V3_HEADER_SIZE) + if (UNEXPECTED(get_u32be(&rawheader[8]) != V3_HEADER_SIZE)) throw std::error_condition(error::INVALID_FILE); // extract core info @@ -1835,7 +1961,7 @@ void chd_file::parse_v3_header(uint8_t *rawheader, util::sha1_t &parentsha1) void chd_file::parse_v4_header(uint8_t *rawheader, util::sha1_t &parentsha1) { // verify header length - if (get_u32be(&rawheader[8]) != V4_HEADER_SIZE) + if (UNEXPECTED(get_u32be(&rawheader[8]) != V4_HEADER_SIZE)) throw std::error_condition(error::INVALID_FILE); // extract core info @@ -1895,7 +2021,7 @@ void chd_file::parse_v4_header(uint8_t *rawheader, util::sha1_t &parentsha1) void chd_file::parse_v5_header(uint8_t *rawheader, util::sha1_t &parentsha1) { // verify header length - if (get_u32be(&rawheader[8]) != V5_HEADER_SIZE) + if (UNEXPECTED(get_u32be(&rawheader[8]) != V5_HEADER_SIZE)) throw std::error_condition(error::INVALID_FILE); // extract core info @@ -1969,9 +2095,9 @@ std::error_condition chd_file::compress_v5_map() { uint8_t curcomp = m_rawmap[hunknum * 12 + 0]; - // promote self block references to more compact forms if (curcomp == COMPRESSION_SELF) { + // promote self block references to more compact forms uint32_t refhunk = get_u48be(&m_rawmap[hunknum * 12 + 4]); if (refhunk == last_self) curcomp = COMPRESSION_SELF_0; @@ -1981,10 +2107,9 @@ std::error_condition chd_file::compress_v5_map() max_self = std::max(max_self, refhunk); last_self = refhunk; } - - // promote parent block references to more compact forms else if (curcomp == COMPRESSION_PARENT) { + // promote parent block references to more compact forms uint32_t refunit = get_u48be(&m_rawmap[hunknum * 12 + 4]); if (refunit == mulu_32x32(hunknum, m_hunkbytes) / m_unitbytes) curcomp = COMPRESSION_PARENT_SELF; @@ -2032,26 +2157,37 @@ std::error_condition chd_file::compress_v5_map() } } - // compute a tree and export it to the buffer - std::vector<uint8_t> compressed(m_hunkcount * 6); + // determine the number of bits we need to hold the a length and a hunk index + const uint8_t lengthbits = bits_for_value(max_complen); + const uint8_t selfbits = bits_for_value(max_self); + const uint8_t parentbits = bits_for_value(max_parent); + + // determine the needed size of the output buffer + // 16 bytes is required for the header + // max len per entry given to huffman encoder at instantiation is 8 bits + // this corresponds to worst-case max 12 bits per entry when RLE encoded. + // max additional bits per entry after RLE encoded tree is + // for COMPRESSION_TYPE_0-3: lengthbits+16 + // for COMPRESSION_NONE: 16 + // for COMPRESSION_SELF: selfbits + // for COMPRESSION_PARENT: parentbits + // the overall size is clamped later with bitbuf.flush() + int nbits_needed = (8*16) + (12 + std::max<int>({lengthbits+16, selfbits, parentbits}))*m_hunkcount; + std::vector<uint8_t> compressed(nbits_needed / 8 + 1); bitstream_out bitbuf(&compressed[16], compressed.size() - 16); + + // compute a tree and export it to the buffer huffman_error err = encoder.compute_tree_from_histo(); - if (err != HUFFERR_NONE) + if (UNEXPECTED(err != HUFFERR_NONE)) throw std::error_condition(error::COMPRESSION_ERROR); err = encoder.export_tree_rle(bitbuf); - if (err != HUFFERR_NONE) + if (UNEXPECTED(err != HUFFERR_NONE)) throw std::error_condition(error::COMPRESSION_ERROR); // encode the data for (uint8_t *src = &compression_rle[0]; src < dest; src++) encoder.encode_one(bitbuf, *src); - // determine the number of bits we need to hold the a length - // and a hunk index - uint8_t lengthbits = bits_for_value(max_complen); - uint8_t selfbits = bits_for_value(max_self); - uint8_t parentbits = bits_for_value(max_parent); - // for each compression type, output the relevant data lastcomp = 0; count = 0; @@ -2134,13 +2270,16 @@ std::error_condition chd_file::compress_v5_map() // then write the map offset uint8_t rawbuf[sizeof(uint64_t)]; put_u64be(rawbuf, m_mapoffset); - file_write(m_mapoffset_offset, rawbuf, sizeof(rawbuf)); - return std::error_condition(); + return file_write(m_mapoffset_offset, rawbuf, sizeof(rawbuf)); } catch (std::error_condition const &err) { return err; } + catch (std::bad_alloc const &) + { + return std::errc::not_enough_memory; + } } /** @@ -2165,7 +2304,10 @@ void chd_file::decompress_v5_map() // read the reader uint8_t rawbuf[16]; - file_read(m_mapoffset, rawbuf, sizeof(rawbuf)); + std::error_condition ioerr; + ioerr = file_read(m_mapoffset, rawbuf, sizeof(rawbuf)); + if (UNEXPECTED(ioerr)) + throw ioerr; uint32_t const mapbytes = get_u32be(&rawbuf[0]); uint64_t const firstoffs = get_u48be(&rawbuf[4]); util::crc16_t const mapcrc = get_u16be(&rawbuf[10]); @@ -2175,13 +2317,15 @@ void chd_file::decompress_v5_map() // now read the map std::vector<uint8_t> compressed(mapbytes); - file_read(m_mapoffset + 16, &compressed[0], mapbytes); + ioerr = file_read(m_mapoffset + 16, &compressed[0], mapbytes); + if (UNEXPECTED(ioerr)) + throw ioerr; bitstream_in bitbuf(&compressed[0], compressed.size()); // first decode the compression types huffman_decoder<16, 8> decoder; - huffman_error err = decoder.import_tree_rle(bitbuf); - if (err != HUFFERR_NONE) + huffman_error const huferr = decoder.import_tree_rle(bitbuf); + if (UNEXPECTED(huferr != HUFFERR_NONE)) throw std::error_condition(error::DECOMPRESSION_ERROR); uint8_t lastcomp = 0; int repcount = 0; @@ -2265,7 +2409,7 @@ void chd_file::decompress_v5_map() } // verify the final CRC - if (util::crc16_creator::simple(&m_rawmap[0], m_hunkcount * 12) != mapcrc) + if (UNEXPECTED(util::crc16_creator::simple(&m_rawmap[0], m_hunkcount * 12) != mapcrc)) throw std::error_condition(error::DECOMPRESSION_ERROR); } @@ -2295,13 +2439,13 @@ std::error_condition chd_file::create_common() m_metaoffset = 0; // if we have a parent, it must be V3 or later - if (m_parent && m_parent->version() < 3) + if (UNEXPECTED(m_parent && m_parent->version() < 3)) throw std::error_condition(error::UNSUPPORTED_VERSION); // must be an even number of units per hunk - if (m_hunkbytes % m_unitbytes != 0) + if (UNEXPECTED(m_hunkbytes % m_unitbytes != 0)) throw std::error_condition(std::errc::invalid_argument); - if (m_parent && m_unitbytes != m_parent->unit_bytes()) + if (UNEXPECTED(m_parent && m_unitbytes != m_parent->unit_bytes())) throw std::error_condition(std::errc::invalid_argument); // verify the compression types @@ -2336,7 +2480,9 @@ std::error_condition chd_file::create_common() be_write_sha1(&rawheader[104], m_parent ? m_parent->sha1() : util::sha1_t::null); // write the resulting header - file_write(0, rawheader, sizeof(rawheader)); + std::error_condition err = file_write(0, rawheader, sizeof(rawheader)); + if (UNEXPECTED(err)) + throw err; // parse it back out to set up fields appropriately util::sha1_t parentsha1; @@ -2354,8 +2500,10 @@ std::error_condition chd_file::create_common() uint64_t offset = m_mapoffset; while (mapsize != 0) { - uint32_t bytes_to_write = (std::min<size_t>)(mapsize, sizeof(buffer)); - file_write(offset, buffer, bytes_to_write); + uint32_t const bytes_to_write = std::min<size_t>(mapsize, sizeof(buffer)); + err = file_write(offset, buffer, bytes_to_write); + if (UNEXPECTED(err)) + throw err; offset += bytes_to_write; mapsize -= bytes_to_write; } @@ -2411,10 +2559,12 @@ std::error_condition chd_file::open_common(bool writeable, const open_parent_fun // read the raw header uint8_t rawheader[MAX_HEADER_SIZE]; - file_read(0, rawheader, sizeof(rawheader)); + std::error_condition err = file_read(0, rawheader, sizeof(rawheader)); + if (UNEXPECTED(err)) + throw err; // verify the signature - if (memcmp(rawheader, "MComprHD", 8) != 0) + if (UNEXPECTED(memcmp(rawheader, "MComprHD", 8) != 0)) throw std::error_condition(error::INVALID_FILE); m_version = get_u32be(&rawheader[12]); @@ -2433,7 +2583,7 @@ std::error_condition chd_file::open_common(bool writeable, const open_parent_fun if (m_version < HEADER_VERSION) m_allow_writes = false; - if (writeable && !m_allow_writes) + if (UNEXPECTED(writeable && !m_allow_writes)) throw std::error_condition(error::FILE_NOT_WRITEABLE); // make sure we have a parent if we need one (and don't if we don't) @@ -2447,7 +2597,7 @@ std::error_condition chd_file::open_common(bool writeable, const open_parent_fun else if (m_parent->sha1() != parentsha1) throw std::error_condition(error::INVALID_PARENT); } - else if (m_parent) + else if (UNEXPECTED(m_parent)) { throw std::error_condition(std::errc::invalid_argument); } @@ -2481,16 +2631,22 @@ void chd_file::create_open_common() for (int decompnum = 0; decompnum < std::size(m_compression); decompnum++) { m_decompressor[decompnum] = chd_codec_list::new_decompressor(m_compression[decompnum], *this); - if (m_decompressor[decompnum] == nullptr && m_compression[decompnum] != 0) + if (UNEXPECTED(!m_decompressor[decompnum] && (m_compression[decompnum] != 0))) throw std::error_condition(error::UNKNOWN_COMPRESSION); } // read the map; v5+ compressed drives need to read and decompress their map m_rawmap.resize(m_hunkcount * m_mapentrybytes); if (m_version >= 5 && compressed()) + { decompress_v5_map(); + } else - file_read(m_mapoffset, &m_rawmap[0], m_rawmap.size()); + { + std::error_condition err = file_read(m_mapoffset, &m_rawmap[0], m_rawmap.size()); + if (UNEXPECTED(err)) + throw err; + } // allocate the temporary compressed buffer and a buffer for caching m_compressed.resize(m_hunkbytes); @@ -2505,44 +2661,37 @@ void chd_file::create_open_common() * for appending to a compressed CHD * -------------------------------------------------. * - * @exception CHDERR_NOT_OPEN Thrown when a chderr not open error condition occurs. - * @exception CHDERR_HUNK_OUT_OF_RANGE Thrown when a chderr hunk out of range error - * condition occurs. - * @exception CHDERR_FILE_NOT_WRITEABLE Thrown when a chderr file not writeable error - * condition occurs. - * @exception CHDERR_COMPRESSION_ERROR Thrown when a chderr compression error error - * condition occurs. - * * @param hunknum The hunknum. */ -void chd_file::verify_proper_compression_append(uint32_t hunknum) +std::error_condition chd_file::verify_proper_compression_append(uint32_t hunknum) const noexcept { // punt if no file - if (!m_file) - throw std::error_condition(error::NOT_OPEN); + if (UNEXPECTED(!m_file)) + return std::error_condition(error::NOT_OPEN); // return an error if out of range - if (hunknum >= m_hunkcount) - throw std::error_condition(error::HUNK_OUT_OF_RANGE); + if (UNEXPECTED(hunknum >= m_hunkcount)) + return std::error_condition(error::HUNK_OUT_OF_RANGE); // if not writeable, fail - if (!m_allow_writes) - throw std::error_condition(error::FILE_NOT_WRITEABLE); + if (UNEXPECTED(!m_allow_writes)) + return std::error_condition(error::FILE_NOT_WRITEABLE); // compressed writes only via this interface - if (!compressed()) - throw std::error_condition(error::FILE_NOT_WRITEABLE); + if (UNEXPECTED(!compressed())) + return std::error_condition(error::FILE_NOT_WRITEABLE); // only permitted to write new blocks - uint8_t *rawmap = &m_rawmap[hunknum * 12]; - if (rawmap[0] != 0xff) - throw std::error_condition(error::COMPRESSION_ERROR); + uint8_t const *const rawmap = &m_rawmap[hunknum * 12]; + if (UNEXPECTED(rawmap[0] != 0xff)) + return std::error_condition(error::COMPRESSION_ERROR); + + // if this isn't the first block, only permitted to write immediately after the previous one + if (UNEXPECTED((hunknum != 0) && (rawmap[-12] == 0xff))) + return std::error_condition(error::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 std::error_condition(error::COMPRESSION_ERROR); + return std::error_condition(); } /** @@ -2563,7 +2712,9 @@ void chd_file::verify_proper_compression_append(uint32_t hunknum) void chd_file::hunk_write_compressed(uint32_t hunknum, int8_t compression, const uint8_t *compressed, uint32_t complength, util::crc16_t crc16) { // verify that we are appending properly to a compressed file - verify_proper_compression_append(hunknum); + std::error_condition err = verify_proper_compression_append(hunknum); + if (UNEXPECTED(err)) + throw err; // write the final result uint64_t offset = file_append(compressed, complength); @@ -2593,11 +2744,13 @@ void chd_file::hunk_write_compressed(uint32_t hunknum, int8_t compression, const void chd_file::hunk_copy_from_self(uint32_t hunknum, uint32_t otherhunk) { // verify that we are appending properly to a compressed file - verify_proper_compression_append(hunknum); + std::error_condition err = verify_proper_compression_append(hunknum); + if (UNEXPECTED(err)) + throw err; // only permitted to reference prior hunks - if (otherhunk >= hunknum) - throw std::error_condition(std::errc::invalid_argument); + if (UNEXPECTED(otherhunk >= hunknum)) + throw std::error_condition(error::HUNK_OUT_OF_RANGE); // update the map entry uint8_t *rawmap = &m_rawmap[hunknum * 12]; @@ -2621,10 +2774,12 @@ void chd_file::hunk_copy_from_self(uint32_t hunknum, uint32_t otherhunk) void chd_file::hunk_copy_from_parent(uint32_t hunknum, uint64_t parentunit) { // verify that we are appending properly to a compressed file - verify_proper_compression_append(hunknum); + std::error_condition err = verify_proper_compression_append(hunknum); + if (UNEXPECTED(err)) + throw err; // update the map entry - uint8_t *rawmap = &m_rawmap[hunknum * 12]; + uint8_t *const rawmap = &m_rawmap[hunknum * 12]; rawmap[0] = COMPRESSION_PARENT; put_u24be(&rawmap[1], 0); put_u48be(&rawmap[4], parentunit); @@ -2632,7 +2787,7 @@ void chd_file::hunk_copy_from_parent(uint32_t hunknum, uint64_t parentunit) } /** - * @fn bool chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metadata_entry &metaentry, bool resume) + * @fn std::error_condition chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metadata_entry &metaentry, bool resume) * * @brief ------------------------------------------------- * metadata_find - find a metadata entry @@ -2643,10 +2798,10 @@ void chd_file::hunk_copy_from_parent(uint32_t hunknum, uint64_t parentunit) * @param [in,out] metaentry The metaentry. * @param resume true to resume. * - * @return true if it succeeds, false if it fails. + * @return A std::error_condition (error::METADATA_NOT_FOUND if the search fails). */ -bool chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metadata_entry &metaentry, bool resume) const +std::error_condition chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metadata_entry &metaentry, bool resume) const noexcept { // start at the beginning unless we're resuming a previous search if (!resume) @@ -2665,7 +2820,9 @@ bool chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metada { // read the raw header uint8_t raw_meta_header[METADATA_HEADER_SIZE]; - file_read(metaentry.offset, raw_meta_header, sizeof(raw_meta_header)); + std::error_condition err = file_read(metaentry.offset, raw_meta_header, sizeof(raw_meta_header)); + if (UNEXPECTED(err)) + return err; // extract the data metaentry.metatag = get_u32be(&raw_meta_header[0]); @@ -2676,7 +2833,7 @@ bool chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metada // if we got a match, proceed if (metatag == CHDMETATAG_WILDCARD || metaentry.metatag == metatag) if (metaindex-- == 0) - return true; + return std::error_condition(); // no match, fetch the next link metaentry.prev = metaentry.offset; @@ -2684,7 +2841,7 @@ bool chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metada } // if we get here, we didn't find it - return false; + return error::METADATA_NOT_FOUND; } /** @@ -2698,27 +2855,28 @@ bool chd_file::metadata_find(chd_metadata_tag metatag, int32_t metaindex, metada * @param nextoffset The nextoffset. */ -void chd_file::metadata_set_previous_next(uint64_t prevoffset, uint64_t nextoffset) +std::error_condition chd_file::metadata_set_previous_next(uint64_t prevoffset, uint64_t nextoffset) noexcept { uint64_t offset = 0; - // if we were the first entry, make the next entry the first if (prevoffset == 0) { + // if we were the first entry, make the next entry the first offset = m_metaoffset_offset; m_metaoffset = nextoffset; } - - // otherwise, update the link in the previous header else + { + // otherwise, update the link in the previous header offset = prevoffset + 8; + } // create a big-endian version uint8_t rawbuf[sizeof(uint64_t)]; put_u64be(rawbuf, nextoffset); // write to the header and update our local copy - file_write(offset, rawbuf, sizeof(rawbuf)); + return file_write(offset, rawbuf, sizeof(rawbuf)); } /** @@ -2732,7 +2890,7 @@ void chd_file::metadata_set_previous_next(uint64_t prevoffset, uint64_t nextoffs void chd_file::metadata_update_hash() { // only works for V4 and above, and only for compressed CHDs - if (m_version < 4 || !compressed()) + if ((m_version < 4) || !compressed()) return; // compute the new overall hash @@ -2743,7 +2901,9 @@ void chd_file::metadata_update_hash() be_write_sha1(&rawbuf[0], fullsha1); // write to the header - file_write(m_sha1_offset, rawbuf, sizeof(rawbuf)); + std::error_condition err = file_write(m_sha1_offset, rawbuf, sizeof(rawbuf)); + if (UNEXPECTED(err)) + throw err; } /** @@ -2778,19 +2938,18 @@ int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *ele * -------------------------------------------------. */ -chd_file_compressor::chd_file_compressor() - : m_walking_parent(false), - m_total_in(0), - m_total_out(0), - m_read_queue(nullptr), - m_read_queue_offset(0), - m_read_done_offset(0), - m_read_error(false), - m_work_queue(nullptr), - m_write_hunk(0) +chd_file_compressor::chd_file_compressor() : + m_walking_parent(false), + m_total_in(0), + m_total_out(0), + m_read_queue(nullptr), + m_read_queue_offset(0), + m_read_done_offset(0), + m_work_queue(nullptr), + m_write_hunk(0) { // zap arrays - memset(m_codecs, 0, sizeof(m_codecs)); + std::fill(std::begin(m_codecs), std::end(m_codecs), nullptr); // allocate work queues m_read_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO); @@ -2839,7 +2998,7 @@ void chd_file_compressor::compress_begin() // reset read state m_read_queue_offset = 0; m_read_done_offset = 0; - m_read_error = false; + m_read_error.clear(); // reset work item state m_work_buffer.resize(hunk_bytes() * (WORK_BUFFER_HUNKS + 1)); @@ -2880,20 +3039,19 @@ void chd_file_compressor::compress_begin() std::error_condition chd_file_compressor::compress_continue(double &progress, double &ratio) { - // if we got an error, return an error - if (m_read_error) - return std::errc::io_error; + // if we got an error, return the error + if (UNEXPECTED(m_read_error)) + return m_read_error; // if done reading, queue some more while (m_read_queue_offset < m_logicalbytes && osd_work_queue_items(m_read_queue) < 2) { // see if we have enough free work items to read the next half of a buffer - uint32_t startitem = m_read_queue_offset / hunk_bytes(); - uint32_t enditem = startitem + WORK_BUFFER_HUNKS / 2; - uint32_t curitem; - for (curitem = startitem; curitem < enditem; curitem++) - if (m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY) - break; + uint32_t const startitem = m_read_queue_offset / hunk_bytes(); + uint32_t const enditem = startitem + WORK_BUFFER_HUNKS / 2; + uint32_t curitem = startitem; + while ((curitem < enditem) && (m_work_item[curitem % WORK_BUFFER_HUNKS].m_status == WS_READY)) + ++curitem; // if it's not all clear, defer if (curitem != enditem) @@ -2917,64 +3075,60 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do work_item &item = m_work_item[m_write_hunk % WORK_BUFFER_HUNKS]; // free any OSD work item - if (item.m_osd != nullptr) + if (item.m_osd) + { osd_work_item_release(item.m_osd); - item.m_osd = nullptr; + item.m_osd = nullptr; + } - // for parent walking, just add to the hashmap if (m_walking_parent) { - uint32_t uph = hunk_bytes() / unit_bytes(); + // for parent walking, just add to the hashmap + uint32_t const uph = hunk_bytes() / unit_bytes(); uint32_t units = uph; if (item.m_hunknum == hunk_count() - 1 || !compressed()) units = 1; for (uint32_t 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()) { + // if we're uncompressed, use regular writes std::error_condition err = write_hunk(item.m_hunknum, item.m_data); - if (err) + if (UNEXPECTED(err)) return err; // writes of all-0 data don't actually take space, so see if we count this chd_codec_type codec = CHD_CODEC_NONE; uint32_t complen; - hunk_info(item.m_hunknum, codec, complen); - if (codec == CHD_CODEC_NONE) + err = hunk_info(item.m_hunknum, codec, complen); + if (!err && codec == CHD_CODEC_NONE) // TODO: report error? m_total_out += m_hunkbytes; } - - // for compressing, process the result - else do + else if (uint64_t const selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); selfhunk != hashmap::NOT_FOUND) + { + // the hunk is in the self map + hunk_copy_from_self(item.m_hunknum, selfhunk); + } + else { - // first see if the hunk is in the parent or self maps - uint64_t selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); - if (selfhunk != hashmap::NOT_FOUND) + // if not, see if it's in the parent map + uint64_t const parentunit = m_parent ? m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) : hashmap::NOT_FOUND; + if (parentunit != hashmap::NOT_FOUND) { - hunk_copy_from_self(item.m_hunknum, selfhunk); - break; + hunk_copy_from_parent(item.m_hunknum, parentunit); } - - // if not, see if it's in the parent map - if (m_parent) + else { - uint64_t 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); } - - // 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 (false); + } // reset the item and advance item.m_status = WS_READY; @@ -2983,23 +3137,24 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do // 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) { + // if this is just walking the parent, reset and get ready for compression m_walking_parent = false; m_read_queue_offset = m_read_done_offset = 0; m_write_hunk = 0; - for (auto & elem : m_work_item) + for (auto &elem : m_work_item) elem.m_status = WS_READY; } - - // wait for all reads to finish and if we're compressed, write the final SHA1 and map else { + // wait for all reads to finish and if we're compressed, write the final SHA1 and map osd_work_queue_wait(m_read_queue, 30 * osd_ticks_per_second()); if (!compressed()) return std::error_condition(); - set_raw_sha1(m_compsha1.finish()); + std::error_condition err = set_raw_sha1(m_compsha1.finish()); + if (UNEXPECTED(err)) + return err; return compress_v5_map(); } } @@ -3014,9 +3169,9 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do // if we're waiting for work, wait // sometimes code can get here with .m_status == WS_READY and .m_osd != nullptr, TODO find out why this happens - while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_READY && - m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_COMPLETE && - m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd != nullptr) + while ((m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_READY) && + (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_COMPLETE) && + m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd) osd_work_item_wait(m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd, osd_ticks_per_second()); return m_walking_parent ? error::WALKING_PARENT : error::COMPRESSING; @@ -3037,7 +3192,7 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do void *chd_file_compressor::async_walk_parent_static(void *param, int threadid) { - auto *item = reinterpret_cast<work_item *>(param); + auto *const item = reinterpret_cast<work_item *>(param); item->m_compressor->async_walk_parent(*item); return nullptr; } @@ -3079,7 +3234,7 @@ void chd_file_compressor::async_walk_parent(work_item &item) void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid) { - auto *item = reinterpret_cast<work_item *>(param); + auto *const item = reinterpret_cast<work_item *>(param); item->m_compressor->async_compress_hunk(*item, threadid); return nullptr; } @@ -3106,8 +3261,8 @@ void chd_file_compressor::async_compress_hunk(work_item &item, int threadid) // find the best compression scheme, unless we already have a self or parent match // (note we may miss a self match from blocks not yet added, but this just results in extra work) // TODO: data race - if (m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND && - m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND) + if ((m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND) && + (m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND)) item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen); // mark us complete @@ -3142,37 +3297,45 @@ void *chd_file_compressor::async_read_static(void *param, int threadid) void chd_file_compressor::async_read() { // if in the error or complete state, stop - if (m_read_error) + if (UNEXPECTED(m_read_error)) return; // determine parameters for the read - uint32_t work_buffer_bytes = WORK_BUFFER_HUNKS * hunk_bytes(); + uint32_t const work_buffer_bytes = WORK_BUFFER_HUNKS * hunk_bytes(); uint32_t numbytes = work_buffer_bytes / 2; - if (m_read_done_offset + numbytes > logical_bytes()) + if ((m_read_done_offset + numbytes) > logical_bytes()) numbytes = logical_bytes() - m_read_done_offset; + uint8_t *const dest = &m_work_buffer[0] + (m_read_done_offset % work_buffer_bytes); + assert((&m_work_buffer[0] == dest) || (&m_work_buffer[work_buffer_bytes / 2] == dest)); + assert(!(m_read_done_offset % hunk_bytes())); + uint64_t const end_offset = m_read_done_offset + numbytes; + // catch any exceptions coming out of here try { // do the read - uint8_t *dest = &m_work_buffer[0] + (m_read_done_offset % work_buffer_bytes); - assert(dest == &m_work_buffer[0] || dest == &m_work_buffer[work_buffer_bytes/2]); - uint64_t end_offset = m_read_done_offset + numbytes; - - // if walking the parent, read in hunks from the parent CHD if (m_walking_parent) { + // if walking the parent, read in hunks from the parent CHD + uint64_t curoffs = m_read_done_offset; uint8_t *curdest = dest; - for (uint64_t curoffs = m_read_done_offset; curoffs < end_offset + 1; curoffs += hunk_bytes()) + uint32_t curhunk = m_read_done_offset / hunk_bytes(); + while (curoffs < end_offset + 1) { - m_parent->read_hunk(curoffs / hunk_bytes(), curdest); + std::error_condition err = m_parent->read_hunk(curhunk, curdest); + if (err && (error::HUNK_OUT_OF_RANGE != err)) // FIXME: fix the code so it doesn't depend on trying to read past the end of the parent CHD + throw err; + curoffs += hunk_bytes(); curdest += hunk_bytes(); + ++curhunk; } } - - // otherwise, call the virtual function else + { + // otherwise, call the virtual function read_data(dest, m_read_done_offset, numbytes); + } // spawn off work for each hunk for (uint64_t curoffs = m_read_done_offset; curoffs < end_offset; curoffs += hunk_bytes()) @@ -3199,12 +3362,12 @@ void chd_file_compressor::async_read() catch (std::error_condition const &err) { fprintf(stderr, "CHD error occurred: %s\n", err.message().c_str()); - m_read_error = true; + m_read_error = err; } catch (std::exception const &ex) { fprintf(stderr, "exception occurred: %s\n", ex.what()); - m_read_error = true; + m_read_error = std::errc::io_error; // TODO: revisit this error code } } @@ -3222,8 +3385,8 @@ void chd_file_compressor::async_read() * -------------------------------------------------. */ -chd_file_compressor::hashmap::hashmap() - : m_block_list(new entry_block(nullptr)) +chd_file_compressor::hashmap::hashmap() : + m_block_list(new entry_block(nullptr)) { // initialize the map to empty memset(m_map, 0, sizeof(m_map)); @@ -3279,10 +3442,10 @@ void chd_file_compressor::hashmap::reset() * @return An uint64_t. */ -uint64_t chd_file_compressor::hashmap::find(util::crc16_t crc16, util::sha1_t sha1) +uint64_t chd_file_compressor::hashmap::find(util::crc16_t crc16, util::sha1_t sha1) const noexcept { // look up the entry in the map - for (entry_t *entry = m_map[crc16]; entry != nullptr; entry = entry->m_next) + for (entry_t *entry = m_map[crc16]; entry; entry = entry->m_next) if (entry->m_sha1 == sha1) return entry->m_itemnum; return NOT_FOUND; @@ -3312,36 +3475,40 @@ void chd_file_compressor::hashmap::add(uint64_t itemnum, util::crc16_t crc16, ut m_map[crc16] = entry; } -bool chd_file::is_hd() const +std::error_condition chd_file::check_is_hd() const noexcept { metadata_entry metaentry; return metadata_find(HARD_DISK_METADATA_TAG, 0, metaentry); } -bool chd_file::is_cd() const +std::error_condition chd_file::check_is_cd() const noexcept { metadata_entry metaentry; - return metadata_find(CDROM_OLD_METADATA_TAG, 0, metaentry) - || metadata_find(CDROM_TRACK_METADATA_TAG, 0, metaentry) - || metadata_find(CDROM_TRACK_METADATA2_TAG, 0, metaentry); + std::error_condition err = metadata_find(CDROM_OLD_METADATA_TAG, 0, metaentry); + if (err == error::METADATA_NOT_FOUND) + err = metadata_find(CDROM_TRACK_METADATA_TAG, 0, metaentry); + if (err == error::METADATA_NOT_FOUND) + err = metadata_find(CDROM_TRACK_METADATA2_TAG, 0, metaentry); + return err; } -bool chd_file::is_gd() const +std::error_condition chd_file::check_is_gd() const noexcept { metadata_entry metaentry; - return metadata_find(GDROM_OLD_METADATA_TAG, 0, metaentry) - || metadata_find(GDROM_TRACK_METADATA_TAG, 0, metaentry); + std::error_condition err = metadata_find(GDROM_OLD_METADATA_TAG, 0, metaentry); + if (err == error::METADATA_NOT_FOUND) + err = metadata_find(GDROM_TRACK_METADATA_TAG, 0, metaentry); + return err; } -bool chd_file::is_dvd() const +std::error_condition chd_file::check_is_dvd() const noexcept { metadata_entry metaentry; return metadata_find(DVD_METADATA_TAG, 0, metaentry); } -bool chd_file::is_av() const +std::error_condition chd_file::check_is_av() const noexcept { metadata_entry metaentry; return metadata_find(AV_METADATA_TAG, 0, metaentry); } - diff --git a/src/lib/util/chd.h b/src/lib/util/chd.h index 3e52cab4f9b..671f4bdbd01 100644 --- a/src/lib/util/chd.h +++ b/src/lib/util/chd.h @@ -2,8 +2,6 @@ // copyright-holders:Aaron Giles /*************************************************************************** - chd.h - MAME Compressed Hunks of Data file format ***************************************************************************/ @@ -309,24 +307,24 @@ public: uint32_t hunk_count() const noexcept { return m_hunkcount; } uint32_t unit_bytes() const noexcept { return m_unitbytes; } uint64_t unit_count() const noexcept { return m_unitcount; } - bool compressed() const { return (m_compression[0] != CHD_CODEC_NONE); } + bool compressed() const noexcept { return (m_compression[0] != CHD_CODEC_NONE); } chd_codec_type compression(int index) const noexcept { return m_compression[index]; } chd_file *parent() const noexcept { return m_parent.get(); } bool parent_missing() const noexcept; - util::sha1_t sha1(); - util::sha1_t raw_sha1(); - util::sha1_t parent_sha1(); + util::sha1_t sha1() const noexcept; + util::sha1_t raw_sha1() const noexcept; + util::sha1_t parent_sha1() const noexcept; std::error_condition hunk_info(uint32_t hunknum, chd_codec_type &compressor, uint32_t &compbytes); // setters - void set_raw_sha1(util::sha1_t rawdata); - void set_parent_sha1(util::sha1_t parent); + std::error_condition set_raw_sha1(util::sha1_t rawdata) noexcept; + std::error_condition set_parent_sha1(util::sha1_t parent) noexcept; // file create - std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]); - std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]); - std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent); - std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent); + std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]); + std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]); + std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent); + std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent); // file open std::error_condition open(std::string_view filename, bool writeable = false, chd_file *parent = nullptr, const open_parent_func &open_parent = nullptr); @@ -336,6 +334,7 @@ public: void close(); // read/write + std::error_condition codec_process_hunk(uint32_t hunknum); std::error_condition read_hunk(uint32_t hunknum, void *buffer); std::error_condition write_hunk(uint32_t hunknum, const void *buffer); std::error_condition read_units(uint64_t unitnum, void *buffer, uint32_t count = 1); @@ -361,23 +360,23 @@ public: std::error_condition codec_configure(chd_codec_type codec, int param, void *config); // typing - bool is_hd() const; - bool is_cd() const; - bool is_gd() const; - bool is_dvd() const; - bool is_av() const; + std::error_condition check_is_hd() const noexcept; + std::error_condition check_is_cd() const noexcept; + std::error_condition check_is_gd() const noexcept; + std::error_condition check_is_dvd() const noexcept; + std::error_condition check_is_av() const noexcept; private: struct metadata_entry; struct metadata_hash; // inline helpers - util::sha1_t be_read_sha1(const uint8_t *base) const; - void be_write_sha1(uint8_t *base, util::sha1_t value); - void file_read(uint64_t offset, void *dest, uint32_t length) const; - void file_write(uint64_t offset, const void *source, uint32_t length); + util::sha1_t be_read_sha1(const uint8_t *base) const noexcept; + void be_write_sha1(uint8_t *base, util::sha1_t value) noexcept; + std::error_condition file_read(uint64_t offset, void *dest, uint32_t length) const noexcept; + std::error_condition file_write(uint64_t offset, const void *source, uint32_t length) noexcept; uint64_t file_append(const void *source, uint32_t length, uint32_t alignment = 0); - uint8_t bits_for_value(uint64_t value); + static uint8_t bits_for_value(uint64_t value) noexcept; // internal helpers uint32_t guess_unitbytes(); @@ -389,12 +388,12 @@ private: std::error_condition create_common(); std::error_condition open_common(bool writeable, const open_parent_func &open_parent); void create_open_common(); - void verify_proper_compression_append(uint32_t hunknum); + std::error_condition verify_proper_compression_append(uint32_t hunknum) const noexcept; void hunk_write_compressed(uint32_t hunknum, int8_t compression, const uint8_t *compressed, uint32_t complength, util::crc16_t crc16); void hunk_copy_from_self(uint32_t hunknum, uint32_t otherhunk); void hunk_copy_from_parent(uint32_t hunknum, uint64_t parentunit); - bool metadata_find(chd_metadata_tag metatag, int32_t metaindex, metadata_entry &metaentry, bool resume = false) const; - void metadata_set_previous_next(uint64_t prevoffset, uint64_t nextoffset); + std::error_condition metadata_find(chd_metadata_tag metatag, int32_t metaindex, metadata_entry &metaentry, bool resume = false) const noexcept; + std::error_condition metadata_set_previous_next(uint64_t prevoffset, uint64_t nextoffset) noexcept; void metadata_update_hash(); static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2); @@ -466,7 +465,7 @@ private: // operations void reset(); - uint64_t find(util::crc16_t crc16, util::sha1_t sha1); + uint64_t find(util::crc16_t crc16, util::sha1_t sha1) const noexcept; void add(uint64_t itemnum, util::crc16_t crc16, util::sha1_t sha1); // constants @@ -563,7 +562,7 @@ private: osd_work_queue * m_read_queue; // work queue for reading uint64_t m_read_queue_offset;// next offset to enqueue uint64_t m_read_done_offset; // next offset that will complete - bool m_read_error; // error during reading? + std::error_condition m_read_error; // error during reading, if any // work item thread static constexpr int WORK_BUFFER_HUNKS = 256; diff --git a/src/lib/util/chdcodec.cpp b/src/lib/util/chdcodec.cpp index c59d880374c..c8a1f573bd0 100644 --- a/src/lib/util/chdcodec.cpp +++ b/src/lib/util/chdcodec.cpp @@ -2,8 +2,6 @@ // copyright-holders:Aaron Giles /*************************************************************************** - chdcodec.c - Codecs used by the CHD format ***************************************************************************/ @@ -333,16 +331,16 @@ private: // ======================> chd_cd_compressor -template<class BaseCompressor, class SubcodeCompressor> +template <class BaseCompressor, class SubcodeCompressor> class chd_cd_compressor : public chd_compressor { public: // construction/destruction chd_cd_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy) - : chd_compressor(chd, hunkbytes, lossy), - m_base_compressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SECTOR_DATA, lossy), - m_subcode_compressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SUBCODE_DATA, lossy), - m_buffer(hunkbytes + (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SUBCODE_DATA) + : chd_compressor(chd, hunkbytes, lossy) + , m_base_compressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SECTOR_DATA, lossy) + , m_subcode_compressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SUBCODE_DATA, lossy) + , m_buffer(hunkbytes + (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SUBCODE_DATA) { // make sure the CHD's hunk size is an even multiple of the frame size if (hunkbytes % cdrom_file::FRAME_SIZE != 0) @@ -402,16 +400,16 @@ private: // ======================> chd_cd_decompressor -template<class BaseDecompressor, class SubcodeDecompressor> +template <class BaseDecompressor, class SubcodeDecompressor> class chd_cd_decompressor : public chd_decompressor { public: // construction/destruction chd_cd_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy) - : chd_decompressor(chd, hunkbytes, lossy), - m_base_decompressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SECTOR_DATA, lossy), - m_subcode_decompressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SUBCODE_DATA, lossy), - m_buffer(hunkbytes) + : chd_decompressor(chd, hunkbytes, lossy) + , m_base_decompressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SECTOR_DATA, lossy) + , m_subcode_decompressor(chd, (hunkbytes / cdrom_file::FRAME_SIZE) * cdrom_file::MAX_SUBCODE_DATA, lossy) + , m_buffer(hunkbytes) { // make sure the CHD's hunk size is an even multiple of the frame size if (hunkbytes % cdrom_file::FRAME_SIZE != 0) @@ -490,6 +488,7 @@ public: chd_avhuff_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy); // core functionality + virtual void process(const uint8_t *src, uint32_t complen) override; virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) override; virtual void configure(int param, void *config) override; @@ -553,7 +552,7 @@ const codec_entry f_codec_list[] = // instance of the given type //------------------------------------------------- -const codec_entry *find_in_list(chd_codec_type type) +const codec_entry *find_in_list(chd_codec_type type) noexcept { // find in the list and construct the class for (auto & elem : f_codec_list) @@ -575,9 +574,9 @@ const codec_entry *find_in_list(chd_codec_type type) //------------------------------------------------- chd_codec::chd_codec(chd_file &chd, uint32_t hunkbytes, bool lossy) - : m_chd(chd), - m_hunkbytes(hunkbytes), - m_lossy(lossy) + : m_chd(chd) + , m_hunkbytes(hunkbytes) + , m_lossy(lossy) { } @@ -607,10 +606,6 @@ void chd_codec::configure(int param, void *config) // CHD COMPRESSOR //************************************************************************** -//------------------------------------------------- -// chd_compressor - constructor -//------------------------------------------------- - chd_compressor::chd_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy) : chd_codec(chd, hunkbytes, lossy) { @@ -622,15 +617,16 @@ chd_compressor::chd_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy) // CHD DECOMPRESSOR //************************************************************************** -//------------------------------------------------- -// chd_decompressor - constructor -//------------------------------------------------- - chd_decompressor::chd_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy) : chd_codec(chd, hunkbytes, lossy) { } +void chd_decompressor::process(const uint8_t *src, uint32_t complen) +{ + throw std::error_condition(chd_file::error::UNSUPPORTED_FORMAT); +} + //************************************************************************** @@ -668,7 +664,7 @@ chd_decompressor::ptr chd_codec_list::new_decompressor(chd_codec_type type, chd_ // corresponds to a supported codec //------------------------------------------------- -bool chd_codec_list::codec_exists(chd_codec_type type) +bool chd_codec_list::codec_exists(chd_codec_type type) noexcept { // find in the list and construct the class return bool(find_in_list(type)); @@ -680,7 +676,7 @@ bool chd_codec_list::codec_exists(chd_codec_type type) // codec //------------------------------------------------- -const char *chd_codec_list::codec_name(chd_codec_type type) +const char *chd_codec_list::codec_name(chd_codec_type type) noexcept { // find in the list and construct the class const codec_entry *entry = find_in_list(type); @@ -1588,8 +1584,8 @@ void chd_flac_decompressor::decompress(const uint8_t *src, uint32_t complen, uin //------------------------------------------------- chd_cd_flac_compressor::chd_cd_flac_compressor(chd_file &chd, uint32_t hunkbytes, bool lossy) - : chd_compressor(chd, hunkbytes, lossy), - m_buffer(hunkbytes) + : chd_compressor(chd, hunkbytes, lossy) + , m_buffer(hunkbytes) { // make sure the CHD's hunk size is an even multiple of the frame size if (hunkbytes % cdrom_file::FRAME_SIZE != 0) @@ -1717,8 +1713,8 @@ uint32_t chd_cd_flac_compressor::blocksize(uint32_t bytes) */ chd_cd_flac_decompressor::chd_cd_flac_decompressor(chd_file &chd, uint32_t hunkbytes, bool lossy) - : chd_decompressor(chd, hunkbytes, lossy), - m_buffer(hunkbytes) + : chd_decompressor(chd, hunkbytes, lossy) + , m_buffer(hunkbytes) { // make sure the CHD's hunk size is an even multiple of the frame size if (hunkbytes % cdrom_file::FRAME_SIZE != 0) @@ -1943,21 +1939,13 @@ chd_avhuff_decompressor::chd_avhuff_decompressor(chd_file &chd, uint32_t hunkbyt { } -/** - * @fn void chd_avhuff_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) - * - * @brief ------------------------------------------------- - * decompress - decompress data using the A/V codec - * -------------------------------------------------. - * - * @exception CHDERR_DECOMPRESSION_ERROR Thrown when a chderr decompression error error - * condition occurs. - * - * @param src Source for the. - * @param complen The complen. - * @param [in,out] dest If non-null, destination for the. - * @param destlen The destlen. - */ +void chd_avhuff_decompressor::process(const uint8_t *src, uint32_t complen) +{ + // decode the audio and video + avhuff_error averr = m_decoder.decode_data(src, complen, nullptr); + if (averr != AVHERR_NONE) + throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR); +} void chd_avhuff_decompressor::decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) { @@ -1967,12 +1955,9 @@ void chd_avhuff_decompressor::decompress(const uint8_t *src, uint32_t complen, u throw std::error_condition(chd_file::error::DECOMPRESSION_ERROR); // pad short frames with 0 - if (dest != nullptr) - { - int size = avhuff_encoder::raw_data_size(dest); - if (size < destlen) - memset(dest + size, 0, destlen - size); - } + auto const size = avhuff_encoder::raw_data_size(dest); + if (size < destlen) + memset(dest + size, 0, destlen - size); } /** diff --git a/src/lib/util/chdcodec.h b/src/lib/util/chdcodec.h index c3234ea600d..c477333544f 100644 --- a/src/lib/util/chdcodec.h +++ b/src/lib/util/chdcodec.h @@ -2,8 +2,6 @@ // copyright-holders:Aaron Giles /*************************************************************************** - chdcodec.h - Codecs used by the CHD format ***************************************************************************/ @@ -89,6 +87,7 @@ public: using ptr = std::unique_ptr<chd_decompressor>; // implementation + virtual void process(const uint8_t *src, uint32_t complen); virtual void decompress(const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen) = 0; }; @@ -104,8 +103,8 @@ public: static chd_decompressor::ptr new_decompressor(chd_codec_type type, chd_file &file); // utilities - static bool codec_exists(chd_codec_type type); - static const char *codec_name(chd_codec_type type); + static bool codec_exists(chd_codec_type type) noexcept; + static const char *codec_name(chd_codec_type type) noexcept; }; diff --git a/src/lib/util/corefile.cpp b/src/lib/util/corefile.cpp index 3d5c705ab86..74a742b0306 100644 --- a/src/lib/util/corefile.cpp +++ b/src/lib/util/corefile.cpp @@ -48,13 +48,13 @@ public: virtual std::error_condition tell(std::uint64_t &result) noexcept override { return m_file.tell(result); } virtual std::error_condition length(std::uint64_t &result) noexcept override { return m_file.length(result); } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read(buffer, length, actual); } - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read_at(offset, buffer, length, actual); } + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read_some(buffer, length, actual); } + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read_some_at(offset, buffer, length, actual); } virtual std::error_condition finalize() noexcept override { return m_file.finalize(); } virtual std::error_condition flush() noexcept override { return m_file.flush(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.write(buffer, length, actual); } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.write_at(offset, buffer, length, actual); } + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.write_some(buffer, length, actual); } + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.write_some_at(offset, buffer, length, actual); } virtual bool eof() const override { return m_file.eof(); } @@ -167,13 +167,13 @@ public: ~core_in_memory_file() override { purge(); } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override; - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition finalize() noexcept override { return std::error_condition(); } virtual std::error_condition flush() noexcept override { clear_putback(); return std::error_condition(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0; return std::errc::bad_file_descriptor; } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0; return std::errc::bad_file_descriptor; } + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0; return std::errc::bad_file_descriptor; } + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0; return std::errc::bad_file_descriptor; } void const *buffer() const { return m_data; } @@ -217,13 +217,13 @@ public: } ~core_osd_file() override; - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override; - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition finalize() noexcept override; virtual std::error_condition flush() noexcept override; - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override; - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition truncate(std::uint64_t offset) override; @@ -260,9 +260,8 @@ int core_text_file::getc() { if (!pos) { - std::size_t readlen; std::uint8_t bom[4]; - read(bom, 4, readlen); + auto const [err, readlen] = read(*this, bom, 4); // FIXME: check for errors if (readlen == 4) { if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) @@ -303,15 +302,14 @@ int core_text_file::getc() // fetch the next character // FIXME: all of this plays fast and loose with error checking and seeks backwards far too frequently char16_t utf16_buffer[UTF16_CHAR_MAX]; - auto uchar = char32_t(~0); + char32_t uchar = ~char32_t(0); switch (m_text_type) { default: case text_file_type::OSD: { char default_buffer[16]; - std::size_t readlen; - read(default_buffer, sizeof(default_buffer), readlen); + auto const [err, readlen] = read(*this, default_buffer, sizeof(default_buffer)); if (readlen > 0) { auto const charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0])); @@ -323,8 +321,7 @@ int core_text_file::getc() case text_file_type::UTF8: { char utf8_buffer[UTF8_CHAR_MAX]; - std::size_t readlen; - read(utf8_buffer, sizeof(utf8_buffer), readlen); + auto const [err, readlen] = read(*this, utf8_buffer, sizeof(utf8_buffer)); if (readlen > 0) { auto const charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0])); @@ -335,8 +332,7 @@ int core_text_file::getc() case text_file_type::UTF16BE: { - std::size_t readlen; - read(utf16_buffer, sizeof(utf16_buffer), readlen); + auto const [err, readlen] = read(*this, utf16_buffer, sizeof(utf16_buffer)); if (readlen > 0) { auto const charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); @@ -347,8 +343,7 @@ int core_text_file::getc() case text_file_type::UTF16LE: { - std::size_t readlen; - read(utf16_buffer, sizeof(utf16_buffer), readlen); + auto const [err, readlen] = read(*this, utf16_buffer, sizeof(utf16_buffer)); if (readlen > 0) { auto const charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); @@ -360,8 +355,7 @@ int core_text_file::getc() case text_file_type::UTF32BE: { // FIXME: deal with read returning short - std::size_t readlen; - read(&uchar, sizeof(uchar), readlen); + auto const [err, readlen] = read(*this, &uchar, sizeof(uchar)); if (sizeof(uchar) == readlen) uchar = big_endianize_int32(uchar); } @@ -370,8 +364,7 @@ int core_text_file::getc() case text_file_type::UTF32LE: { // FIXME: deal with read returning short - std::size_t readlen; - read(&uchar, sizeof(uchar), readlen); + auto const [err, readlen] = read(*this, &uchar, sizeof(uchar)); if (sizeof(uchar) == readlen) uchar = little_endianize_int32(uchar); } @@ -467,7 +460,7 @@ char *core_text_file::gets(char *s, int n) int core_text_file::puts(std::string_view s) { - // TODO: what to do about write errors or short writes (interrupted)? + // TODO: what to do about write errors? // The API doesn't lend itself to reporting the error as the return // value includes extra bytes inserted like the UTF-8 marker and // carriage returns. @@ -511,8 +504,7 @@ int core_text_file::puts(std::string_view s) // if we overflow, break into chunks if (pconvbuf >= convbuf + std::size(convbuf) - 10) { - std::size_t written; - write(convbuf, pconvbuf - convbuf, written); // FIXME: error ignored here + auto const [err, written] = write(*this, convbuf, pconvbuf - convbuf); // FIXME: error ignored here count += written; pconvbuf = convbuf; } @@ -521,8 +513,7 @@ int core_text_file::puts(std::string_view s) // final flush if (pconvbuf != convbuf) { - std::size_t written; - write(convbuf, pconvbuf - convbuf, written); // FIXME: error ignored here + auto const [err, written] = write(*this, convbuf, pconvbuf - convbuf); // FIXME: error ignored here count += written; } @@ -646,7 +637,7 @@ std::size_t core_basic_file::safe_buffer_copy( // read - read from a file //------------------------------------------------- -std::error_condition core_in_memory_file::read(void *buffer, std::size_t length, std::size_t &actual) noexcept +std::error_condition core_in_memory_file::read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept { clear_putback(); @@ -659,7 +650,7 @@ std::error_condition core_in_memory_file::read(void *buffer, std::size_t length, return std::error_condition(); } -std::error_condition core_in_memory_file::read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept +std::error_condition core_in_memory_file::read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept { clear_putback(); @@ -705,17 +696,17 @@ core_osd_file::~core_osd_file() // read - read from a file //------------------------------------------------- -std::error_condition core_osd_file::read(void *buffer, std::size_t length, std::size_t &actual) noexcept +std::error_condition core_osd_file::read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept { // since osd_file works like pread/pwrite, implement in terms of read_at // core_osd_file is declared final, so a derived class can't interfere - std::error_condition err = read_at(index(), buffer, length, actual); + std::error_condition err = read_some_at(index(), buffer, length, actual); add_offset(actual); return err; } -std::error_condition core_osd_file::read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept +std::error_condition core_osd_file::read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept { if (!m_file) { @@ -750,18 +741,12 @@ std::error_condition core_osd_file::read_at(std::uint64_t offset, void *buffer, } else { - // read the remainder directly from the file - do - { - // may need to split into chunks if size_t is larger than 32 bits - std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length - actual); - std::uint32_t bytes_read; - err = m_file->read(reinterpret_cast<std::uint8_t *>(buffer) + actual, offset + actual, chunk, bytes_read); - if (err || !bytes_read) - break; + // read the remainder directly from the file - may need to return short if size_t is larger than 32 bits + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length - actual); + std::uint32_t bytes_read; + err = m_file->read(reinterpret_cast<std::uint8_t *>(buffer) + actual, offset + actual, chunk, bytes_read); + if (!err) actual += bytes_read; - } - while (actual < length); } } @@ -774,17 +759,17 @@ std::error_condition core_osd_file::read_at(std::uint64_t offset, void *buffer, // write - write to a file //------------------------------------------------- -std::error_condition core_osd_file::write(void const *buffer, std::size_t length, std::size_t &actual) noexcept +std::error_condition core_osd_file::write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept { // since osd_file works like pread/pwrite, implement in terms of write_at // core_osd_file is declared final, so a derived class can't interfere - std::error_condition err = write_at(index(), buffer, length, actual); + std::error_condition err = write_some_at(index(), buffer, length, actual); add_offset(actual); return err; } -std::error_condition core_osd_file::write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept +std::error_condition core_osd_file::write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept { // flush any buffered char clear_putback(); @@ -792,24 +777,23 @@ std::error_condition core_osd_file::write_at(std::uint64_t offset, void const *b // invalidate any buffered data m_bufferbytes = 0U; - // do the write - may need to split into chunks if size_t is larger than 32 bits - actual = 0U; - while (length) + // do the write - may need to return short if size_t is larger than 32 bits + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); + std::uint32_t bytes_written; + std::error_condition err = m_file->write(buffer, offset, chunk, bytes_written); + if (err) { // bytes written not valid on error - std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); - std::uint32_t bytes_written; - std::error_condition err = m_file->write(buffer, offset, chunk, bytes_written); - if (err) - return err; + actual = 0U; + } + else + { assert(chunk >= bytes_written); offset += bytes_written; - buffer = reinterpret_cast<std::uint8_t const *>(buffer) + bytes_written; - length -= bytes_written; - actual += bytes_written; + actual = bytes_written; set_size((std::max)(size(), offset)); } - return std::error_condition(); + return err; } @@ -968,7 +952,7 @@ core_file::~core_file() // pointer //------------------------------------------------- -std::error_condition core_file::load(std::string_view filename, void **data, std::uint32_t &length) noexcept +std::error_condition core_file::load(std::string_view filename, void **data, std::size_t &length) noexcept { std::error_condition err; @@ -983,20 +967,20 @@ std::error_condition core_file::load(std::string_view filename, void **data, std err = file->length(size); if (err) return err; - else if (std::uint32_t(size) != size) // TODO: change interface to use size_t rather than uint32_t for output size + else if (std::size_t(size) != size) return std::errc::file_too_large; // allocate memory *data = std::malloc(std::size_t(size)); if (!*data) return std::errc::not_enough_memory; - length = std::uint32_t(size); + length = std::size_t(size); // read the data if (size) { std::size_t actual; - err = file->read(*data, std::size_t(size), actual); + std::tie(err, actual) = read(*file, *data, std::size_t(size)); if (err || (size != actual)) { std::free(*data); @@ -1004,7 +988,7 @@ std::error_condition core_file::load(std::string_view filename, void **data, std if (err) return err; else - return std::errc::io_error; // TODO: revisit this error code - either interrupted by an async signal or file truncated out from under us + return std::errc::io_error; // TODO: revisit this error code - file truncated out from under us } } @@ -1038,14 +1022,14 @@ std::error_condition core_file::load(std::string_view filename, std::vector<uint if (size) { std::size_t actual; - err = file->read(&data[0], std::size_t(size), actual); + std::tie(err, actual) = read(*file, &data[0], std::size_t(size)); if (err || (size != actual)) { data.clear(); if (err) return err; else - return std::errc::io_error; // TODO: revisit this error code - either interrupted by an async signal or file truncated out from under us + return std::errc::io_error; // TODO: revisit this error code - file truncated out from under us } } diff --git a/src/lib/util/corefile.h b/src/lib/util/corefile.h index 315645d4e2c..fda5015d8ba 100644 --- a/src/lib/util/corefile.h +++ b/src/lib/util/corefile.h @@ -77,7 +77,7 @@ public: virtual char *gets(char *s, int n) = 0; // open a file with the specified filename, read it into memory, and return a pointer - static std::error_condition load(std::string_view filename, void **data, std::uint32_t &length) noexcept; + static std::error_condition load(std::string_view filename, void **data, std::size_t &length) noexcept; static std::error_condition load(std::string_view filename, std::vector<uint8_t> &data) noexcept; diff --git a/src/lib/util/corestr.cpp b/src/lib/util/corestr.cpp index 586f56c1aa3..9fa0a3990a2 100644 --- a/src/lib/util/corestr.cpp +++ b/src/lib/util/corestr.cpp @@ -15,6 +15,7 @@ #include <memory> #include <cctype> +#include <cstdint> #include <cstdlib> diff --git a/src/lib/util/coretmpl.h b/src/lib/util/coretmpl.h index 55313a4a0f5..87f8cfbf09e 100644 --- a/src/lib/util/coretmpl.h +++ b/src/lib/util/coretmpl.h @@ -654,7 +654,7 @@ template <typename T, typename U, typename... V> constexpr T bitswap(T val, U b, /// bit of the input. Specify bits in the order they should appear in /// the output field, from most significant to least significant. /// \return The extracted bits packed into a right-aligned field. -template <unsigned B, typename T, typename... U> T bitswap(T val, U... b) noexcept +template <unsigned B, typename T, typename... U> constexpr T bitswap(T val, U... b) noexcept { static_assert(sizeof...(b) == B, "wrong number of bits"); static_assert((sizeof(std::remove_reference_t<T>) * 8) >= B, "return type too small for result"); diff --git a/src/lib/util/coreutil.cpp b/src/lib/util/coreutil.cpp index 94ec6dec273..cebbceb037e 100644 --- a/src/lib/util/coreutil.cpp +++ b/src/lib/util/coreutil.cpp @@ -10,7 +10,6 @@ #include "coreutil.h" #include <cassert> -#include <zlib.h> /*************************************************************************** @@ -55,14 +54,3 @@ uint32_t bcd_2_dec(uint32_t a) } return result; } - - - -/*************************************************************************** - MISC -***************************************************************************/ - -uint32_t core_crc32(uint32_t crc, const uint8_t *buf, uint32_t len) -{ - return crc32(crc, buf, len); -} diff --git a/src/lib/util/coreutil.h b/src/lib/util/coreutil.h index 1f487232619..813deb27a83 100644 --- a/src/lib/util/coreutil.h +++ b/src/lib/util/coreutil.h @@ -72,11 +72,4 @@ inline int gregorian_days_in_month(int month, int year) return result; } - -/*************************************************************************** - MISC -***************************************************************************/ - -uint32_t core_crc32(uint32_t crc, const uint8_t *buf, uint32_t len); - #endif // MAME_UTIL_COREUTIL_H diff --git a/src/lib/util/delegate.cpp b/src/lib/util/delegate.cpp index 8949f90f146..fb2fd4e2091 100644 --- a/src/lib/util/delegate.cpp +++ b/src/lib/util/delegate.cpp @@ -10,6 +10,8 @@ #include "delegate.h" +#include "mfpresolve.h" + #include <cstdio> #include <sstream> @@ -70,31 +72,9 @@ const delegate_mfp_compatible::raw_mfp_data delegate_mfp_compatible::s_null_mfp delegate_generic_function delegate_mfp_itanium::convert_to_generic(delegate_generic_class *&object) const { - // apply the "this" delta to the object first - the value is shifted to the left one bit position for the ARM-like variant - LOG("Input this=%p ptr=%p adj=%ld ", reinterpret_cast<void const *>(object), reinterpret_cast<void const *>(m_function), long(m_this_delta)); - object = reinterpret_cast<delegate_generic_class *>( - reinterpret_cast<std::uint8_t *>(object) + (m_this_delta >> ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 1 : 0))); - LOG("Calculated this=%p ", reinterpret_cast<void const *>(object)); - - // test the virtual member function flag - it's the low bit of either the ptr or adj field, depending on the variant - if ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? !(m_this_delta & 1) : !(m_function & 1)) - { - // conventional function pointer - LOG("ptr=%p\n", reinterpret_cast<void const *>(m_function)); - return reinterpret_cast<delegate_generic_function>(m_function); - } - else - { - // byte index into the vtable to the function - std::uint8_t const *const vtable_ptr = *reinterpret_cast<std::uint8_t const *const *>(object) + m_function - ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 0 : 1); - delegate_generic_function result; - if (MAME_ABI_CXX_VTABLE_FNDESC) - result = reinterpret_cast<delegate_generic_function>(uintptr_t(vtable_ptr)); - else - result = *reinterpret_cast<delegate_generic_function const *>(vtable_ptr); - LOG("ptr=%p (vtable)\n", reinterpret_cast<void const *>(result)); - return result; - } + auto const [entrypoint, adjusted] = resolve_member_function_itanium(m_function, m_this_delta, object); + object = reinterpret_cast<delegate_generic_class *>(adjusted); + return reinterpret_cast<delegate_generic_function>(entrypoint); } @@ -107,181 +87,9 @@ delegate_generic_function delegate_mfp_itanium::convert_to_generic(delegate_gene delegate_generic_function delegate_mfp_msvc::adjust_this_pointer(delegate_generic_class *&object) const { - LOG("Input this=%p ", reinterpret_cast<void const *>(object)); - if (sizeof(single_base_equiv) < m_size) - LOG("thisdelta=%d ", m_this_delta); - if (sizeof(unknown_base_equiv) == m_size) - LOG("vptrdelta=%d vindex=%d ", m_vptr_offs, m_vt_index); - std::uint8_t *byteptr = reinterpret_cast<std::uint8_t *>(object); - - // test for pointer to member function cast across virtual inheritance relationship - if ((sizeof(unknown_base_equiv) == m_size) && m_vt_index) - { - // add offset from "this" pointer to location of vptr, and add offset to virtual base from vtable - byteptr += m_vptr_offs; - std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(byteptr); - byteptr += *reinterpret_cast<int const *>(vptr + m_vt_index); - } - - // add "this" pointer displacement if present in the pointer to member function - if (sizeof(single_base_equiv) < m_size) - byteptr += m_this_delta; - LOG("Calculated this=%p\n", reinterpret_cast<void const *>(byteptr)); - object = reinterpret_cast<delegate_generic_class *>(byteptr); - - // walk past recognisable thunks -#if defined(__x86_64__) || defined(_M_X64) - std::uint8_t const *func = reinterpret_cast<std::uint8_t const *>(m_function); - while (true) - { - // Assumes Windows calling convention, and doesn't consider that - // the "this" pointer could be in RDX if RCX is a pointer to - // space for an oversize scalar result. Since the result area - // is uninitialised on entry, you won't see something that looks - // like a vtable dispatch through RCX in this case - it won't - // behave badly, it just won't bypass virtual call thunks in the - // rare situations where the return type is an oversize scalar. - if (0xe9 == func[0]) - { - // relative jump with 32-bit displacement (typically a resolved PLT entry) - LOG("Found relative jump at %p ", func); - func += std::ptrdiff_t(5) + *reinterpret_cast<std::int32_t const *>(func + 1); - LOG("redirecting to %p\n", func); - continue; - } - else if ((0x48 == func[0]) && (0x8b == func[1]) && (0x01 == func[2])) - { - if ((0xff == func[3]) && ((0x20 == func[4]) || (0x60 == func[4]) || (0xa0 == func[4]))) - { - // MSVC virtual function call thunk - mov rax,QWORD PTR [rcx] ; jmp QWORD PTR [rax+...] - LOG("Found virtual member function thunk at %p ", func); - std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); - if (0x20 == func[4]) // no displacement - func = *reinterpret_cast<std::uint8_t const *const *>(vptr); - else if (0x60 == func[4]) // 8-bit displacement - func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int8_t const *>(func + 5)); - else // 32-bit displacement - func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int32_t const *>(func + 5)); - LOG("redirecting to %p\n", func); - continue; - } - else if ((0x48 == func[3]) && (0x8b == func[4])) - { - // clang virtual function call thunk - mov rax,QWORD PTR [rcx] ; mov rax,QWORD PTR [rax+...] ; jmp rax - if ((0x00 == func[5]) && (0x48 == func[6]) && (0xff == func[7]) && (0xe0 == func[8])) - { - // no displacement - LOG("Found virtual member function thunk at %p ", func); - std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); - func = *reinterpret_cast<std::uint8_t const *const *>(vptr); - LOG("redirecting to %p\n", func); - continue; - } - else if ((0x40 == func[5]) && (0x48 == func[7]) && (0xff == func[8]) && (0xe0 == func[9])) - { - // 8-bit displacement - LOG("Found virtual member function thunk at %p ", func); - std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); - func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int8_t const *>(func + 6)); - LOG("redirecting to %p\n", func); - continue; - } - else if ((0x80 == func[5]) && (0x48 == func[10]) && (0xff == func[11]) && (0xe0 == func[12])) - { - // 32-bit displacement - LOG("Found virtual member function thunk at %p ", func); - std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); - func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int32_t const *>(func + 6)); - LOG("redirecting to %p\n", func); - continue; - } - } - } - - // clang uses unoptimised thunks if optimisation is disabled - // Without optimisation, clang produces thunks like: - // 50 push rax - // 48 89 0c 24 mov QWORD PTR [rsp],rcx - // 48 8b 0c 24 mov rcx,QWORD PTR [rsp] - // 48 8b 01 mov rax,QWORD PTR [rcx] - // 48 8b 80 xx xx xx xx mov rax,QWORD PTR [rax+...] - // 41 5a pop r10 - // 48 ff e0 jmp rax - // Trying to decode these thunks likely isn't worth the effort. - // Chasing performance in unoptimised builds isn't very useful, - // and the format of these thunks may be fragile. - - // not something we can easily bypass - break; - } - return reinterpret_cast<delegate_generic_function>(std::uintptr_t(func)); -#elif defined(__aarch64__) || defined(_M_ARM64) - std::uint32_t const *func = reinterpret_cast<std::uint32_t const *>(m_function); - while (true) - { - // Assumes little Endian mode. Instructions are always stored - // in little Endian format on AArch64, so if big Endian mode is - // to be supported, the values need to be swapped. - if ((0x90000010 == (func[0] & 0x9f00001f)) && (0x91000210 == (func[1] & 0xffc003ff)) && (0xd61f0200 == func[2])) - { - // page-relative jump with +/-4GB reach - adrp xip0,... ; add xip0,xip0,#... ; br xip0 - LOG("Found page-relative jump at %p ", func); - std::int64_t const page = - (std::uint64_t(func[0] & 0x60000000) >> 17) | - (std::uint64_t(func[0] & 0x00ffffe0) << 9) | - ((func[0] & 0x00800000) ? (~std::uint64_t(0) << 33) : 0); - std::uint32_t const offset = (func[1] & 0x003ffc00) >> 10; - func = reinterpret_cast<std::uint32_t const *>(((std::uintptr_t(func) + page) & (~std::uintptr_t(0) << 12)) + offset); - LOG("redirecting to %p\n", func); - } - else if ((0xf9400010 == func[0]) && (0xf9400210 == (func[1] & 0xffc003ff)) && (0xd61f0200 == func[2])) - { - // virtual function call thunk - ldr xip0,[x0] ; ldr xip0,[x0,#...] ; br xip0 - LOG("Found virtual member function thunk at %p ", func); - std::uint32_t const *const *const vptr = *reinterpret_cast<std::uint32_t const *const *const *>(object); - func = vptr[(func[1] & 0x003ffc00) >> 10]; - LOG("redirecting to %p\n", func); - } - else - { - // not something we can easily bypass - break; - } - - // clang uses horribly sub-optimal thunks for AArch64 - // Without optimisation, clang produces thunks like: - // d10143ff sub sp,sp,#80 - // f90027e7 str x7,[sp,#72] - // f90023e6 str x6,[sp,#64] - // f9001fe5 str x5,[sp,#56] - // f9001be4 str x4,[sp,#48] - // f90017e3 str x3,[sp,#40] - // f90013e2 str x2,[sp,#32] - // f9000fe1 str x1,[sp,#24] - // f90007e0 str x0,[sp,#8] - // f94007e0 ldr x0,[sp,#8] - // f9400009 ldr x9,[x0] - // f9400129 ldr x9,[x9,#...] - // 910143ff add sp,sp,#80 - // d61f0120 br x9 - // With optimisation, clang produces thunks like: - // d10103ff sub sp,sp,#64 - // a9008be1 stp x1,x2,[sp,#8] - // a90193e3 stp x3,x4,[sp,#24] - // a9029be5 stp x5,x6,[sp,#40] - // f9001fe7 str x7,[sp,#56] - // f9400009 ldr x9,[x0] - // f9400129 ldr x9,[x9,#...] - // 910103ff add sp,sp,#64 - // d61f0120 br x9 - // It's more effort than it's worth to try decoding these - // thunks. - - } - return reinterpret_cast<delegate_generic_function>(std::uintptr_t(func)); -#else - return reinterpret_cast<delegate_generic_function>(m_function); -#endif + auto const [entrypoint, adjusted] = resolve_member_function_msvc(&m_function, m_size, object); + object = reinterpret_cast<delegate_generic_class *>(adjusted); + return reinterpret_cast<delegate_generic_function>(entrypoint); } } // namespace util::detail diff --git a/src/lib/util/dvdrom.cpp b/src/lib/util/dvdrom.cpp index 36dc29e7215..0b2379ef7e6 100644 --- a/src/lib/util/dvdrom.cpp +++ b/src/lib/util/dvdrom.cpp @@ -58,8 +58,8 @@ dvdrom_file::dvdrom_file(chd_file *_chd) throw nullptr; /* check it's actually a DVD-ROM */ - if (!chd->is_dvd()) - throw nullptr; + if (std::error_condition err = chd->check_is_dvd()) + throw err; sector_count = chd->unit_count(); } diff --git a/src/lib/util/flac.cpp b/src/lib/util/flac.cpp index 4d6a3bca81b..d3110ca0d38 100644 --- a/src/lib/util/flac.cpp +++ b/src/lib/util/flac.cpp @@ -18,6 +18,7 @@ #include <cstring> #include <iterator> #include <new> +#include <tuple> //************************************************************************** @@ -84,6 +85,8 @@ bool flac_encoder::reset() FLAC__stream_encoder_set_blocksize(m_encoder, m_block_size); // re-start processing + if (m_file) + return (FLAC__stream_encoder_init_stream(m_encoder, write_callback_static, seek_callback_static, tell_callback_static, nullptr, this) == FLAC__STREAM_ENCODER_INIT_STATUS_OK); return (FLAC__stream_encoder_init_stream(m_encoder, write_callback_static, nullptr, nullptr, nullptr, this) == FLAC__STREAM_ENCODER_INIT_STATUS_OK); } @@ -266,8 +269,7 @@ FLAC__StreamEncoderWriteStatus flac_encoder::write_callback(const FLAC__byte buf int count = bytes - offset; if (m_file) { - size_t actual; - m_file->write(buffer, count, actual); // TODO: check for errors + /*auto const [err, actual] =*/ write(*m_file, buffer, count); // FIXME: check for errors } else { @@ -281,6 +283,38 @@ FLAC__StreamEncoderWriteStatus flac_encoder::write_callback(const FLAC__byte buf return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; } +FLAC__StreamEncoderSeekStatus flac_encoder::seek_callback_static(const FLAC__StreamEncoder *encoder, FLAC__uint64 absolute_byte_offset, void *client_data) +{ + return reinterpret_cast<flac_encoder *>(client_data)->seek_callback(absolute_byte_offset); +} + +FLAC__StreamEncoderSeekStatus flac_encoder::seek_callback(FLAC__uint64 absolute_byte_offset) +{ + if (m_file) + { + if (!m_file->seek(absolute_byte_offset, SEEK_SET)) + return FLAC__STREAM_ENCODER_SEEK_STATUS_OK; + return FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR; + } + return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED; +} + +FLAC__StreamEncoderTellStatus flac_encoder::tell_callback_static(const FLAC__StreamEncoder *encoder, FLAC__uint64 *absolute_byte_offset, void *client_data) +{ + return reinterpret_cast<flac_encoder *>(client_data)->tell_callback(absolute_byte_offset); +} + +FLAC__StreamEncoderTellStatus flac_encoder::tell_callback(FLAC__uint64 *absolute_byte_offset) +{ + if (m_file) + { + if (!m_file->tell(*absolute_byte_offset)) + return FLAC__STREAM_ENCODER_TELL_STATUS_OK; + return FLAC__STREAM_ENCODER_TELL_STATUS_ERROR; + } + return FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED; +} + //************************************************************************** @@ -537,7 +571,8 @@ FLAC__StreamDecoderReadStatus flac_decoder::read_callback(FLAC__byte buffer[], s if (m_file) // if a file, just read { - m_file->read(buffer, expected, *bytes); // TODO: check for errors + std::error_condition err; + std::tie(err, *bytes) = read(*m_file, buffer, expected); // FIXME: check for errors } else // otherwise, copy from memory { @@ -601,12 +636,12 @@ FLAC__StreamDecoderTellStatus flac_decoder::tell_callback_static(const FLAC__Str // stream //------------------------------------------------- -FLAC__StreamDecoderWriteStatus flac_decoder::write_callback_static(const FLAC__StreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) +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[]) +FLAC__StreamDecoderWriteStatus flac_decoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[]) { assert(frame->header.channels == channels()); @@ -633,7 +668,7 @@ FLAC__StreamDecoderWriteStatus flac_decoder::write_callback(const ::FLAC__Frame } } -template <flac_decoder::DECODE_MODE Mode, bool SwapEndian> FLAC__StreamDecoderWriteStatus flac_decoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) +template <flac_decoder::DECODE_MODE Mode, bool SwapEndian> FLAC__StreamDecoderWriteStatus flac_decoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[]) { const int blocksize = frame->header.blocksize; const int shift = (Mode == SCALE_DOWN) ? frame->header.bits_per_sample - m_bits_per_sample : (Mode == SCALE_UP) ? m_bits_per_sample - frame->header.bits_per_sample : 0; diff --git a/src/lib/util/flac.h b/src/lib/util/flac.h index c3fee4b574b..be3a0a8ac96 100644 --- a/src/lib/util/flac.h +++ b/src/lib/util/flac.h @@ -62,6 +62,10 @@ private: 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); + static FLAC__StreamEncoderSeekStatus seek_callback_static(const FLAC__StreamEncoder *encoder, FLAC__uint64 absolute_byte_offset, void *client_data); + FLAC__StreamEncoderSeekStatus seek_callback(FLAC__uint64 absolute_byte_offset); + static FLAC__StreamEncoderTellStatus tell_callback_static(const FLAC__StreamEncoder *encoder, FLAC__uint64 *absolute_byte_offset, void *client_data); + FLAC__StreamEncoderTellStatus tell_callback(FLAC__uint64 *absolute_byte_offset); // internal state FLAC__StreamEncoder * m_encoder; // actual encoder @@ -123,9 +127,9 @@ private: FLAC__StreamDecoderReadStatus read_callback(FLAC__byte buffer[], size_t *bytes); static void metadata_callback_static(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data); static FLAC__StreamDecoderTellStatus tell_callback_static(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset, void *client_data); - 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[]); - template <DECODE_MODE Mode, bool SwapEndian> FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + 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[]); + template <DECODE_MODE Mode, bool SwapEndian> 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 diff --git a/src/lib/util/harddisk.cpp b/src/lib/util/harddisk.cpp index 6eb12b38091..266e5d25a2b 100644 --- a/src/lib/util/harddisk.cpp +++ b/src/lib/util/harddisk.cpp @@ -16,6 +16,7 @@ #include "osdcore.h" #include <cstdlib> +#include <tuple> /*------------------------------------------------- @@ -118,7 +119,7 @@ bool hard_disk_file::read(uint32_t lbasector, void *buffer) size_t actual = 0; std::error_condition err = fhandle->seek(fileoffset + (lbasector * hdinfo.sectorbytes), SEEK_SET); if (!err) - err = fhandle->read(buffer, hdinfo.sectorbytes, actual); + std::tie(err, actual) = util::read(*fhandle, buffer, hdinfo.sectorbytes); return !err && (actual == hdinfo.sectorbytes); } } @@ -151,8 +152,8 @@ bool hard_disk_file::write(uint32_t lbasector, const void *buffer) size_t actual = 0; std::error_condition err = fhandle->seek(fileoffset + (lbasector * hdinfo.sectorbytes), SEEK_SET); if (!err) - err = fhandle->write(buffer, hdinfo.sectorbytes, actual); - return !err && (actual == hdinfo.sectorbytes); + std::tie(err, actual) = util::write(*fhandle, buffer, hdinfo.sectorbytes); + return !err; } } diff --git a/src/lib/util/hash.cpp b/src/lib/util/hash.cpp index 9100ff6abde..f2c528e9b22 100644 --- a/src/lib/util/hash.cpp +++ b/src/lib/util/hash.cpp @@ -410,14 +410,13 @@ std::error_condition hash_collection::compute(random_read &stream, uint64_t offs unsigned const chunk_length = std::min(length, sizeof(buffer)); // read one chunk - std::size_t bytes_read; - std::error_condition err = stream.read_at(offset, buffer, chunk_length, bytes_read); + auto const [err, bytes_read] = read_at(stream, offset, buffer, chunk_length); if (err) return err; if (!bytes_read) // EOF? break; offset += bytes_read; - length -= chunk_length; + length -= bytes_read; // append the chunk creator->append(buffer, bytes_read); diff --git a/src/lib/util/ioprocs.cpp b/src/lib/util/ioprocs.cpp index 0477f1efae1..687968f85ee 100644 --- a/src/lib/util/ioprocs.cpp +++ b/src/lib/util/ioprocs.cpp @@ -20,6 +20,7 @@ #include <cstring> #include <iterator> #include <limits> +#include <new> #include <type_traits> @@ -120,14 +121,14 @@ public: { } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { do_read(this->m_pointer, buffer, length, actual); this->m_pointer += actual; return std::error_condition(); } - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { do_read(offset, buffer, length, actual); return std::error_condition(); @@ -239,7 +240,7 @@ public: { } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { if (m_dangling_write) { @@ -270,7 +271,7 @@ public: return std::error_condition(); } - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0U; @@ -338,7 +339,7 @@ public: return std::error_condition(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { if (m_dangling_read) { @@ -361,7 +362,7 @@ public: return std::error_condition(); } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0U; @@ -409,7 +410,7 @@ public: set_filler(fill); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0U; @@ -473,7 +474,7 @@ public: return std::error_condition(); } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0U; @@ -634,18 +635,12 @@ public: { } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { - // TODO: should the client have to deal with reading less than expected even if EOF isn't hit? - if (std::numeric_limits<std::uint32_t>::max() < length) - { - actual = 0U; - return std::errc::invalid_argument; - } - // actual length not valid on error + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); std::uint32_t count; - std::error_condition err = file().read(buffer, m_pointer, std::uint32_t(length), count); + std::error_condition err = file().read(buffer, m_pointer, chunk, count); if (!err) { m_pointer += count; @@ -658,18 +653,12 @@ public: return err; } - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { - // TODO: should the client have to deal with reading less than expected even if EOF isn't hit? - if (std::numeric_limits<std::uint32_t>::max() < length) - { - actual = 0U; - return std::errc::invalid_argument; - } - // actual length not valid on error + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); std::uint32_t count; - std::error_condition err = file().read(buffer, offset, std::uint32_t(length), count); + std::error_condition err = file().read(buffer, offset, chunk, count); if (!err) actual = std::size_t(count); else @@ -696,48 +685,148 @@ public: return file().flush(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { - actual = 0U; - while (length) + // actual length not valid on error + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); + std::uint32_t count; + std::error_condition err = file().write(buffer, m_pointer, chunk, count); + if (!err) { - // actual length not valid on error - std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); - std::uint32_t written; - std::error_condition err = file().write(buffer, m_pointer, chunk, written); - if (err) - return err; - m_pointer += written; - buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written; - length -= written; - actual += written; + actual = std::size_t(count); + m_pointer += count; } - return std::error_condition(); + else + { + actual = 0U; + } + return err; } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { - actual = 0U; - while (length) - { - // actual length not valid on error - std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); - std::uint32_t written; - std::error_condition err = file().write(buffer, offset, chunk, written); - if (err) - return err; - offset += written; - buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written; - length -= written; - actual += written; - } - return std::error_condition(); + // actual length not valid on error + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length); + std::uint32_t count; + std::error_condition err = file().write(buffer, offset, chunk, count); + if (!err) + actual = std::size_t(count); + else + actual = 0U; + return err; } }; } // anonymous namespace +// helper functions for common patterns + +std::pair<std::error_condition, std::size_t> read(read_stream &stream, void *buffer, std::size_t length) noexcept +{ + std::size_t actual = 0; + do + { + std::size_t count; + std::error_condition err = stream.read_some(buffer, length, count); + actual += count; + if (!err) + { + if (!count) + break; + } + else if (std::errc::interrupted != err) + { + return std::make_pair(err, actual); + } + buffer = reinterpret_cast<std::uint8_t *>(buffer) + count; + length -= count; + } + while (length); + return std::make_pair(std::error_condition(), actual); +} + +std::tuple<std::error_condition, std::unique_ptr<std::uint8_t []>, std::size_t> read(read_stream &stream, std::size_t length) noexcept +{ + std::unique_ptr<std::uint8_t []> buffer(new (std::nothrow) std::uint8_t [length]); + if (!buffer) + return std::make_tuple(std::errc::not_enough_memory, std::move(buffer), std::size_t(0)); + auto [err, actual] = read(stream, buffer.get(), length); + return std::make_tuple(err, std::move(buffer), actual); +} + +std::pair<std::error_condition, std::size_t> read_at(random_read &stream, std::uint64_t offset, void *buffer, std::size_t length) noexcept +{ + std::size_t actual = 0; + do + { + std::size_t count; + std::error_condition err = stream.read_some_at(offset, buffer, length, count); + actual += count; + if (!err) + { + if (!count) + break; + } + else if (std::errc::interrupted != err) + { + return std::make_pair(err, actual); + } + offset += count; + buffer = reinterpret_cast<std::uint8_t *>(buffer) + count; + length -= count; + } + while (length); + return std::make_pair(std::error_condition(), actual); +} + +std::tuple<std::error_condition, std::unique_ptr<std::uint8_t []>, std::size_t> read_at(random_read &stream, std::uint64_t offset, std::size_t length) noexcept +{ + std::unique_ptr<std::uint8_t []> buffer(new (std::nothrow) std::uint8_t [length]); + if (!buffer) + return std::make_tuple(std::errc::not_enough_memory, std::move(buffer), std::size_t(0)); + auto [err, actual] = read_at(stream, offset, buffer.get(), length); + return std::make_tuple(err, std::move(buffer), actual); +} + +std::pair<std::error_condition, std::size_t> write(write_stream &stream, void const *buffer, std::size_t length) noexcept +{ + std::size_t actual = 0; + do + { + std::size_t written; + std::error_condition const err = stream.write_some(buffer, length, written); + assert(written || err || !length); + actual += written; + if (err && (std::errc::interrupted != err)) + return std::make_pair(err, actual); + buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written; + length -= written; + } + while (length); + return std::make_pair(std::error_condition(), actual); +} + +std::pair<std::error_condition, std::size_t> write_at(random_write &stream, std::uint64_t offset, void const *buffer, std::size_t length) noexcept +{ + std::size_t actual = 0; + do + { + std::size_t written; + std::error_condition const err = stream.write_some_at(offset, buffer, length, written); + assert(written || err || !length); + actual += written; + if (err && (std::errc::interrupted != err)) + return std::make_pair(err, actual); + offset += written; + buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written; + length -= written; + } + while (length); + return std::make_pair(std::error_condition(), actual); +} + + // creating RAM read adapters random_read::ptr ram_read(void const *data, std::size_t size) noexcept diff --git a/src/lib/util/ioprocs.h b/src/lib/util/ioprocs.h index efe0a231a11..9e2c29d2a14 100644 --- a/src/lib/util/ioprocs.h +++ b/src/lib/util/ioprocs.h @@ -19,6 +19,8 @@ #include <cstdlib> #include <memory> #include <system_error> +#include <tuple> +#include <utility> // FIXME: make a proper place for OSD forward declarations @@ -56,7 +58,7 @@ public: /// \param [out] actual Number of bytes actually read. Will always /// be less than or equal to the requested length. /// \return An error condition if reading stopped due to an error. - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept = 0; + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept = 0; }; @@ -100,7 +102,7 @@ public: /// \param [out] actual Number of bytes actually written. Will /// always be less than or equal to the requested length. /// \return An error condition if writing stopped due to an error. - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept = 0; + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept = 0; }; @@ -184,7 +186,7 @@ public: /// be less than or equal to the requested length. /// \return An error condition if seeking failed or reading stopped /// due to an error. - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept = 0; + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept = 0; }; @@ -214,7 +216,7 @@ public: /// always be less than or equal to the requested length. /// \return An error condition if seeking failed or writing stopped /// due to an error. - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept = 0; + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept = 0; }; @@ -229,6 +231,123 @@ public: using ptr = std::unique_ptr<random_read_write>; }; + +/// \brief Read from the current position in the stream +/// +/// Reads up to the specified number of bytes from the stream into the +/// supplied buffer, continuing if interrupted by asynchronous signals. +/// May read less than the requested number of bytes if the end of the +/// stream is reached or an error occurs. If the stream supports +/// seeking, reading starts at the current position in the stream, and +/// the current position is incremented by the number of bytes read. +/// The operation may not be atomic if it is interrupted before the +/// requested number of bytes is read. +/// \param [in] stream The stream to read from. +/// \param [out] buffer Destination buffer. Must be large enough to +/// hold the requested number of bytes. +/// \param [in] length Maximum number of bytes to read. +/// \return A pair containing an error condition if reading stopped due +/// to an error, and the actual number of bytes read. +std::pair<std::error_condition, std::size_t> read(read_stream &stream, void *buffer, std::size_t length) noexcept; + +/// \brief Allocate memory and read from the current position in the +/// stream +/// +/// Allocates the specified number of bytes and then reads up to that +/// number of bytes from the stream into the newly allocated buffer, +/// continuing if interrupted by asynchronous signals. May read less +/// than the requested number of bytes if the end of the stream is +/// reached or an error occurs. If the stream supports seeking, +/// reading starts at the current position in the stream, and the +/// current position is incremented by the number of bytes read. The +/// operation may not be atomic if it is interrupted before the +/// requested number of bytes is read. No data will be read if +/// allocation fails. +/// \param [in] stream The stream to read from. +/// hold the requested number of bytes. +/// \param [in] length Maximum number of bytes to read. +/// \return A tuple containing an error condition if allocation failed +/// or reading stopped due to an error, the allocated buffer, and the +/// actual number of bytes read. +std::tuple<std::error_condition, std::unique_ptr<std::uint8_t []>, std::size_t> read(read_stream &stream, std::size_t length) noexcept; + +/// \brief Read from the specified position +/// +/// Reads up to the specified number of bytes from the stream into the +/// supplied buffer, continuing if interrupted by asynchronous signals. +/// May read less than the requested number of bytes if the end of the +/// stream is reached or an error occurs. If seeking is supported, +/// reading starts at the specified position and the current position is +/// unaffected. The operation may not be atomic if it is interrupted +/// before the requested number of bytes is read. +/// \param [in] stream The stream to read from. +/// \param [in] offset The position to start reading from, specified as +/// a number of bytes from the beginning of the stream. +/// \param [out] buffer Destination buffer. Must be large enough to +/// hold the requested number of bytes. +/// \param [in] length Maximum number of bytes to read. +/// \return A pair containing an error condition if reading stopped due +/// to an error, and the actual number of bytes read. +std::pair<std::error_condition, std::size_t> read_at(random_read &stream, std::uint64_t offset, void *buffer, std::size_t length) noexcept; + +/// \brief Allocate memory and read from the specified position +/// +/// Allocates the specified number of bytes and then reads up to that +/// number of bytes from the stream into the newly allocated buffer, +/// continuing if interrupted by asynchronous signals. May read less +/// than the requested number of bytes if the end of the stream is +/// reached or an error occurs. If seeking is supported, reading +/// starts at the specified position and the current position is +/// unaffected. The operation may not be atomic if it is interrupted +/// before the requested number of bytes is read. No data will be read +/// if allocation fails. +/// \param [in] stream The stream to read from. +/// \param [in] offset The position to start reading from, specified as +/// a number of bytes from the beginning of the stream. +/// \param [out] buffer Destination buffer. Must be large enough to +/// hold the requested number of bytes. +/// \param [in] length Maximum number of bytes to read. +/// \return A tuple containing an error condition if allocation failed +/// or reading stopped due to an error, the allocated buffer, and the +/// actual number of bytes read. +std::tuple<std::error_condition, std::unique_ptr<std::uint8_t []>, std::size_t> read_at(random_read &stream, std::uint64_t offset, std::size_t length) noexcept; + +/// \brief Write at the current position in the stream +/// +/// Writes up to the specified number of bytes from the supplied +/// buffer to the stream, continuing if interrupted by asynchronous +/// signals. May write less than the requested number of bytes if an +/// error occurs. If the stream supports seeking, writing starts at the +/// current position in the stream, and the current position is +/// incremented by the number of bytes written. The operation may not +/// be atomic if it is interrupted before the requested number of bytes +/// is written. +/// \param [in] stream The stream to write to. +/// \param [in] buffer Buffer containing the data to write. Must +/// contain at least the specified number of bytes. +/// \param [in] length Number of bytes to write. +/// \return A pair containing an error condition if writing stopped due +/// to an error, and the actual number of bytes written. +std::pair<std::error_condition, std::size_t> write(write_stream &stream, void const *buffer, std::size_t length) noexcept; + +/// \brief Write at specified position +/// +/// Writes up to the specified number of bytes from the supplied buffer, +/// continuing if interrupted by asynchronous signals. If seeking is +/// supported, writing starts at the specified position and the current +/// position is unaffected. May write less than the requested number +/// of bytes if an error occurs. The operation may not be atomic if it +/// is interrupted before the requested number of bytes is written. +/// \param [in] stream The stream to write to. +/// \param [in] offset The position to start writing at, specified as a +/// number of bytes from the beginning of the stream. +/// \param [in] buffer Buffer containing the data to write. Must +/// contain at least the specified number of bytes. +/// \param [in] length Number of bytes to write. +/// \return A pair containing an error condition if writing stopped due +/// to an error, and the actual number of bytes written. +std::pair<std::error_condition, std::size_t> write_at(random_write &stream, std::uint64_t offset, void const *buffer, std::size_t length) noexcept; + /// \} diff --git a/src/lib/util/ioprocsfill.h b/src/lib/util/ioprocsfill.h index e49b10687f0..7179d61852c 100644 --- a/src/lib/util/ioprocsfill.h +++ b/src/lib/util/ioprocsfill.h @@ -45,9 +45,21 @@ class read_stream_fill_wrapper : public Base, public virtual fill_wrapper_base<D public: using Base::Base; - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { - std::error_condition err = Base::read(buffer, length, actual); + // not atomic with respect to other read/write calls + actual = 0U; + std::error_condition err; + std::size_t chunk; + do + { + err = Base::read_some( + reinterpret_cast<std::uint8_t *>(buffer) + actual, + length - actual, + chunk); + actual += chunk; + } + while ((length > actual) && ((!err && chunk) || (std::errc::interrupted == err))); assert(length >= actual); std::fill( reinterpret_cast<std::uint8_t *>(buffer) + actual, @@ -64,9 +76,23 @@ class random_read_fill_wrapper : public read_stream_fill_wrapper<Base, DefaultFi public: using read_stream_fill_wrapper<Base, DefaultFiller>::read_stream_fill_wrapper; - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { - std::error_condition err = Base::read_at(offset, buffer, length, actual); + // not atomic with respect to other read/write calls + actual = 0U; + std::error_condition err; + std::size_t chunk; + do + { + err = Base::read_some_at( + offset, + reinterpret_cast<std::uint8_t *>(buffer) + actual, + length - actual, + chunk); + offset += chunk; + actual += chunk; + } + while ((length > actual) && ((!err && chunk) || (std::errc::interrupted == err))); assert(length >= actual); std::fill( reinterpret_cast<std::uint8_t *>(buffer) + actual, @@ -83,8 +109,9 @@ class random_write_fill_wrapper : public Base, public virtual fill_wrapper_base< public: using Base::Base; - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { + // not atomic with respect to other read/write calls std::error_condition err; actual = 0U; @@ -108,23 +135,22 @@ public: do { std::size_t const chunk = std::min<std::common_type_t<std::size_t, std::uint64_t> >(FillBlock, unfilled); - err = Base::write_at(current, fill_buffer, chunk, actual); - if (err) - { - actual = 0U; + std::size_t filled; + err = Base::write_some_at(current, fill_buffer, chunk, filled); + current += filled; + unfilled -= filled; + if (err && (std::errc::interrupted != err)) return err; - } - current += chunk; - unfilled -= chunk; } while (unfilled); } - return Base::write(buffer, length, actual); + return Base::write_some(buffer, length, actual); } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { + // not atomic with respect to other read/write calls std::error_condition err; std::uint64_t current; err = Base::length(current); @@ -139,18 +165,19 @@ public: do { std::size_t const chunk = std::min<std::common_type_t<std::size_t, std::uint64_t> >(FillBlock, unfilled); - err = Base::write_at(current, fill_buffer, chunk, actual); - current += chunk; - unfilled -= chunk; + std::size_t filled; + err = Base::write_some_at(current, fill_buffer, chunk, filled); + current += filled; + unfilled -= filled; } - while (unfilled && !err); + while (unfilled && (!err || (std::errc::interrupted == err))); } if (err) { actual = 0U; return err; } - return Base::write_at(offset, buffer, length, actual); + return Base::write_some_at(offset, buffer, length, actual); } }; diff --git a/src/lib/util/ioprocsfilter.cpp b/src/lib/util/ioprocsfilter.cpp index 68b9c90d018..b8d4cc3e786 100644 --- a/src/lib/util/ioprocsfilter.cpp +++ b/src/lib/util/ioprocsfilter.cpp @@ -21,6 +21,7 @@ #include <cstdint> #include <limits> #include <system_error> +#include <tuple> #include <type_traits> #include <utility> @@ -372,9 +373,9 @@ class read_stream_proxy : public virtual read_stream, public T public: using T::T; - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { - return this->object().read(buffer, length, actual); + return this->object().read_some(buffer, length, actual); } }; @@ -397,9 +398,9 @@ public: return this->object().flush(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { - return this->object().write(buffer, length, actual); + return this->object().write_some(buffer, length, actual); } }; @@ -437,9 +438,9 @@ class random_read_proxy : public virtual random_read, public read_stream_proxy<T public: using read_stream_proxy<T>::read_stream_proxy; - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { - return this->object().read_at(offset, buffer, length, actual); + return this->object().read_some_at(offset, buffer, length, actual); } }; @@ -452,9 +453,9 @@ class random_write_proxy : public virtual random_write, public write_stream_prox public: using write_stream_proxy<T>::write_stream_proxy; - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { - return this->object().write_at(offset, buffer, length, actual); + return this->object().write_some_at(offset, buffer, length, actual); } }; @@ -487,7 +488,7 @@ public: { } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { std::error_condition err; actual = 0U; @@ -504,7 +505,7 @@ public: { auto const space = get_unfilled_input(); std::size_t filled; - err = this->object().read(space.first, space.second, filled); + std::tie(err, filled) = read(this->object(), space.first, space.second); add_input(filled); short_input = space.second > filled; } @@ -598,8 +599,7 @@ public: auto const output = get_output(); if (output.second) { - std::size_t written; - std::error_condition err = object().write(output.first, output.second, written); + auto const [err, written] = write(object(), output.first, output.second); consume_output(written); if (err) { @@ -611,7 +611,7 @@ public: return object().flush(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { std::error_condition err; actual = 0U; @@ -651,7 +651,7 @@ private: { auto const output = get_output(); std::size_t written; - std::error_condition err = object().write(output.first, output.second, written); + std::error_condition const err = object().write_some(output.first, output.second, written); consume_output(written); return err; } diff --git a/src/lib/util/ioprocsvec.h b/src/lib/util/ioprocsvec.h index d3c1c7fe01b..e66000b5729 100644 --- a/src/lib/util/ioprocsvec.h +++ b/src/lib/util/ioprocsvec.h @@ -90,14 +90,14 @@ public: return std::error_condition(); } - virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { do_read(m_pointer, buffer, length, actual); m_pointer += actual; return std::error_condition(); } - virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { do_read(offset, buffer, length, actual); return std::error_condition(); @@ -113,14 +113,14 @@ public: return std::error_condition(); } - virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { std::error_condition result = do_write(m_pointer, buffer, length, actual); m_pointer += actual; return result; } - virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override + virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return do_write(offset, buffer, length, actual); } diff --git a/src/lib/util/jedparse.cpp b/src/lib/util/jedparse.cpp index 5915886b1a6..b347a2dd2b5 100644 --- a/src/lib/util/jedparse.cpp +++ b/src/lib/util/jedparse.cpp @@ -24,6 +24,7 @@ #include <cstdlib> #include <cstring> #include <cctype> +#include <tuple> @@ -194,7 +195,6 @@ static void process_field(jed_data *data, const uint8_t *cursrc, const uint8_t * int jed_parse(util::random_read &src, jed_data *result) { jed_parse_info pinfo; - int i; std::size_t actual; std::error_condition err; @@ -206,7 +206,7 @@ int jed_parse(util::random_read &src, jed_data *result) uint8_t ch; do { - err = src.read(&ch, 1, actual); + std::tie(err, actual) = read(src, &ch, 1); if (err) { if (LOG_PARSE) printf("Read error searching for JED start marker\n"); @@ -225,7 +225,7 @@ int jed_parse(util::random_read &src, jed_data *result) uint16_t checksum = ch; do { - err = src.read(&ch, 1, actual); + std::tie(err, actual) = read(src, &ch, 1); if (err) { if (LOG_PARSE) printf("Read error searching for JED end marker\n"); @@ -261,7 +261,8 @@ int jed_parse(util::random_read &src, jed_data *result) /* see if there is a transmission checksum at the end */ uint8_t sumbuf[4]; - if (!src.read(&sumbuf[0], 4, actual) && actual == 4 && ishex(sumbuf[0]) && ishex(sumbuf[1]) && ishex(sumbuf[2]) && ishex(sumbuf[3])) + std::tie(err, actual) = read(src, &sumbuf[0], 4); + if (!err && (actual == 4) && ishex(sumbuf[0]) && ishex(sumbuf[1]) && ishex(sumbuf[2]) && ishex(sumbuf[3])) { uint16_t dessum = (hexval(sumbuf[0]) << 12) | (hexval(sumbuf[1]) << 8) | (hexval(sumbuf[2]) << 4) | hexval(sumbuf[3] << 0); if (dessum != 0 && dessum != checksum) @@ -281,7 +282,8 @@ int jed_parse(util::random_read &src, jed_data *result) } } auto srcdata = std::make_unique<uint8_t[]>(endpos - startpos); - if (src.read(&srcdata[0], endpos - startpos, actual) || actual != endpos - startpos) + std::tie(err, actual) = read(src, &srcdata[0], endpos - startpos); + if (err || ((endpos - startpos) != actual)) { if (LOG_PARSE) printf("Error reading JED data\n"); return JEDERR_INVALID_DATA; @@ -325,7 +327,7 @@ int jed_parse(util::random_read &src, jed_data *result) /* validate the checksum */ checksum = 0; - for (i = 0; i < (result->numfuses + 7) / 8; i++) + for (int i = 0; i < ((result->numfuses + 7) / 8); i++) checksum += result->fusemap[i]; if (pinfo.checksum != 0 && checksum != pinfo.checksum) { @@ -441,13 +443,16 @@ size_t jed_output(const jed_data *data, void *result, size_t length) int jedbin_parse(util::read_stream &src, jed_data *result) { + std::error_condition err; + std::size_t actual; + /* initialize the output */ memset(result, 0, sizeof(*result)); /* need at least 4 bytes */ uint8_t buf[4]; - std::size_t actual; - if (src.read(&buf[0], 4, actual) || actual != 4) + std::tie(err, actual) = read(src, &buf[0], 4); + if (err || (4 != actual)) return JEDERR_INVALID_DATA; /* first unpack the number of fuses */ @@ -457,7 +462,8 @@ int jedbin_parse(util::read_stream &src, jed_data *result) /* now make sure we have enough data in the source */ /* copy in the data */ - if (src.read(result->fusemap, (result->numfuses + 7) / 8, actual) || actual != (result->numfuses + 7) / 8) + std::tie(err, actual) = read(src, result->fusemap, (result->numfuses + 7) / 8); + if (err || (((result->numfuses + 7) / 8) != actual)) return JEDERR_INVALID_DATA; return JEDERR_NONE; } diff --git a/src/lib/util/language.cpp b/src/lib/util/language.cpp index 3a00fe80ad7..50b518acd1b 100644 --- a/src/lib/util/language.cpp +++ b/src/lib/util/language.cpp @@ -61,11 +61,10 @@ void load_translation(random_read &file) return; } - std::size_t read; - file.read(translation_data.get(), size, read); - if (read != size) + auto const [err, actual] = read(file, translation_data.get(), size); + if (err || (actual != size)) { - osd_printf_error("Error reading translation file: requested %u bytes but got %u bytes\n", size, read); + osd_printf_error("Error reading translation file: requested %u bytes but got %u bytes\n", size, actual); translation_data.reset(); return; } diff --git a/src/lib/util/mfpresolve.cpp b/src/lib/util/mfpresolve.cpp new file mode 100644 index 00000000000..c7b9c58fde2 --- /dev/null +++ b/src/lib/util/mfpresolve.cpp @@ -0,0 +1,431 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + mfpresolve.h + + Helpers for resolving member function pointers to entry points. + +***************************************************************************/ + +#include "mfpresolve.h" + +#include "osdcomm.h" + +#include <cstdio> + + +//************************************************************************** +// MACROS +//************************************************************************** + +#if defined(MAME_DELEGATE_LOG_ADJ) + #define LOG(...) printf(__VA_ARGS__) +#else + #define LOG(...) do { if (false) printf(__VA_ARGS__); } while (false) +#endif + + + +namespace util::detail { + +std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_itanium( + std::uintptr_t function, + std::ptrdiff_t delta, + void const *object) noexcept +{ + // apply the "this" delta to the object first - the value is shifted to the left one bit position for the ARM-like variant + LOG("Input this=%p ptr=%p adj=%ld ", object, reinterpret_cast<void const *>(function), long(delta)); + constexpr int deltashift = (MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 1 : 0; + object = reinterpret_cast<std::uint8_t const *>(object) + (delta >> deltashift); + LOG("Calculated this=%p ", object); + + // test the virtual member function flag - it's the low bit of either the ptr or adj field, depending on the variant + if ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? !(delta & 1) : !(function & 1)) + { + // conventional function pointer + LOG("ptr=%p\n", reinterpret_cast<void const *>(function)); + return std::make_pair(function, std::uintptr_t(object)); + } + else + { + // byte index into the vtable to the function + auto const vtable_ptr = *reinterpret_cast<std::uint8_t const *const *>(object) + function - ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 0 : 1); + std::uintptr_t result; + if (MAME_ABI_CXX_VTABLE_FNDESC) + result = std::uintptr_t(vtable_ptr); + else + result = *reinterpret_cast<std::uintptr_t const *>(vtable_ptr); + LOG("ptr=%p (vtable)\n", reinterpret_cast<void const *>(result)); + return std::make_pair(result, std::uintptr_t(object)); + } +} + + +std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function_itanium( + std::uintptr_t function, + std::ptrdiff_t delta) noexcept +{ + constexpr uintptr_t funcmask = ~uintptr_t((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 0 : 1); + constexpr int deltashift = (MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 1 : 0; + return std::make_tuple( + function & funcmask, + delta >> deltashift, + (MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? (delta & 1) : (function & 1)); +} + + +std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_msvc( + void const *funcptr, + std::size_t size, + void const *object) noexcept +{ + mfp_msvc_unknown_equiv const *unknown; + assert(sizeof(*unknown) >= size); + unknown = reinterpret_cast<mfp_msvc_unknown_equiv const *>(funcptr); + + LOG("Input this=%p ", object); + if (sizeof(mfp_msvc_single_equiv) < size) + LOG("thisdelta=%d ", unknown->delta); + if (sizeof(mfp_msvc_unknown_equiv) == size) + LOG("vptrdelta=%d vindex=%d ", unknown->voffset, unknown->vindex); + auto byteptr = reinterpret_cast<std::uint8_t const *>(object); + + // test for pointer to member function cast across virtual inheritance relationship + if ((sizeof(mfp_msvc_unknown_equiv) == size) && unknown->vindex) + { + // add offset from "this" pointer to location of vptr, and add offset to virtual base from vtable + byteptr += unknown->voffset; + auto const vptr = *reinterpret_cast<std::uint8_t const *const *>(byteptr); + byteptr += *reinterpret_cast<int const *>(vptr + unknown->vindex); + } + + // add "this" pointer displacement if present in the pointer to member function + if (sizeof(mfp_msvc_single_equiv) < size) + byteptr += unknown->delta; + LOG("Calculated this=%p\n", reinterpret_cast<void const *>(byteptr)); + + // walk past recognisable thunks + return std::make_pair(bypass_member_function_thunks(unknown->entrypoint, byteptr), std::uintptr_t(byteptr)); +} + + +std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function_msvc( + void const *funcptr, + std::size_t size) noexcept +{ + mfp_msvc_unknown_equiv const *unknown; + assert(sizeof(*unknown) >= size); + unknown = reinterpret_cast<mfp_msvc_unknown_equiv const *>(funcptr); + + // no way to represent pointer to member function cast across virtual inheritance relationship + if ((sizeof(mfp_msvc_unknown_equiv) == size) && unknown->vindex) + return std::make_tuple(std::uintptr_t(static_cast<void (*)()>(nullptr)), std::ptrdiff_t(0), false); + + auto const [function, is_virtual] = bypass_member_function_thunks(unknown->entrypoint); + return std::make_tuple( + function, + (sizeof(mfp_msvc_single_equiv) < size) ? unknown->delta : 0, + is_virtual); +} + + +std::uintptr_t bypass_member_function_thunks( + std::uintptr_t entrypoint, + void const *object) noexcept +{ +#if defined(__x86_64__) || defined(_M_X64) + std::uint8_t const *func = reinterpret_cast<std::uint8_t const *>(entrypoint); + while (true) + { + // Assumes Windows calling convention, and doesn't consider that + // the "this" pointer could be in RDX if RCX is a pointer to + // space for an oversize scalar result. Since the result area + // is uninitialised on entry, you won't see something that looks + // like a vtable dispatch through RCX in this case - it won't + // behave badly, it just won't bypass virtual call thunks in the + // rare situations where the return type is an oversize scalar. + if (0xe9 == func[0]) + { + // relative jump with 32-bit displacement (typically a resolved PLT entry) + LOG("Found relative jump at %p ", func); + func += std::ptrdiff_t(5) + *reinterpret_cast<std::int32_t const *>(func + 1); + LOG("redirecting to %p\n", func); + continue; + } + else if (object && (0x48 == func[0]) && (0x8b == func[1]) && (0x01 == func[2])) + { + if ((0xff == func[3]) && ((0x20 == func[4]) || (0x60 == func[4]) || (0xa0 == func[4]))) + { + // MSVC virtual function call thunk - mov rax,QWORD PTR [rcx] ; jmp QWORD PTR [rax+...] + LOG("Found virtual member function thunk at %p ", func); + std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); + if (0x20 == func[4]) // no displacement + func = *reinterpret_cast<std::uint8_t const *const *>(vptr); + else if (0x60 == func[4]) // 8-bit displacement + func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int8_t const *>(func + 5)); + else // 32-bit displacement + func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int32_t const *>(func + 5)); + LOG("redirecting to %p\n", func); + continue; + } + else if ((0x48 == func[3]) && (0x8b == func[4])) + { + // clang virtual function call thunk - mov rax,QWORD PTR [rcx] ; mov rax,QWORD PTR [rax+...] ; jmp rax + if ((0x00 == func[5]) && (0x48 == func[6]) && (0xff == func[7]) && (0xe0 == func[8])) + { + // no displacement + LOG("Found virtual member function thunk at %p ", func); + std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); + func = *reinterpret_cast<std::uint8_t const *const *>(vptr); + LOG("redirecting to %p\n", func); + continue; + } + else if ((0x40 == func[5]) && (0x48 == func[7]) && (0xff == func[8]) && (0xe0 == func[9])) + { + // 8-bit displacement + LOG("Found virtual member function thunk at %p ", func); + std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); + func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int8_t const *>(func + 6)); + LOG("redirecting to %p\n", func); + continue; + } + else if ((0x80 == func[5]) && (0x48 == func[10]) && (0xff == func[11]) && (0xe0 == func[12])) + { + // 32-bit displacement + LOG("Found virtual member function thunk at %p ", func); + std::uint8_t const *const vptr = *reinterpret_cast<std::uint8_t const *const *>(object); + func = *reinterpret_cast<std::uint8_t const *const *>(vptr + *reinterpret_cast<std::int32_t const *>(func + 6)); + LOG("redirecting to %p\n", func); + continue; + } + } + } + + // clang uses unoptimised thunks if optimisation is disabled + // Without optimisation, clang produces thunks like: + // 50 push rax + // 48 89 0c 24 mov QWORD PTR [rsp],rcx + // 48 8b 0c 24 mov rcx,QWORD PTR [rsp] + // 48 8b 01 mov rax,QWORD PTR [rcx] + // 48 8b 80 xx xx xx xx mov rax,QWORD PTR [rax+...] + // 41 5a pop r10 + // 48 ff e0 jmp rax + // Trying to decode these thunks likely isn't worth the effort. + // Chasing performance in unoptimised builds isn't very useful, + // and the format of these thunks may be fragile. + + // not something we can easily bypass + break; + } + return std::uintptr_t(func); +#elif defined(__aarch64__) || defined(_M_ARM64) + std::uint32_t const *func = reinterpret_cast<std::uint32_t const *>(entrypoint); + auto const fetch = [&func] (auto offset) { return little_endianize_int32(func[offset]); }; + while (true) + { + if ((0x90000010 == (fetch(0) & 0x9f00001f)) && (0x91000210 == (fetch(1) & 0xffc003ff)) && (0xd61f0200 == fetch(2))) + { + // page-relative jump with +/-4GB reach - adrp xip0,... ; add xip0,xip0,#... ; br xip0 + LOG("Found page-relative jump at %p ", func); + std::int64_t const page = + (std::uint64_t(fetch(0) & 0x60000000) >> 17) | + (std::uint64_t(fetch(0) & 0x00ffffe0) << 9) | + ((fetch(0) & 0x00800000) ? (~std::uint64_t(0) << 33) : 0); + std::uint32_t const offset = (fetch(1) & 0x003ffc00) >> 10; + func = reinterpret_cast<std::uint32_t const *>(((std::uintptr_t(func) + page) & (~std::uintptr_t(0) << 12)) + offset); + LOG("redirecting to %p\n", func); + } + else if (object && (0xf9400010 == fetch(0)) && (0xf9400210 == (fetch(1) & 0xffc003ff)) && (0xd61f0200 == fetch(2))) + { + // virtual function call thunk - ldr xip0,[x0] ; ldr xip0,[x0,#...] ; br xip0 + LOG("Found virtual member function thunk at %p ", func); + auto const vptr = *reinterpret_cast<std::uint32_t const *const *const *>(object); + func = vptr[(fetch(1) & 0x003ffc00) >> 10]; + LOG("redirecting to %p\n", func); + } + else + { + // not something we can easily bypass + break; + } + + // clang uses horribly sub-optimal thunks for AArch64 + // Without optimisation, clang produces thunks like: + // d10143ff sub sp,sp,#80 + // f90027e7 str x7,[sp,#72] + // f90023e6 str x6,[sp,#64] + // f9001fe5 str x5,[sp,#56] + // f9001be4 str x4,[sp,#48] + // f90017e3 str x3,[sp,#40] + // f90013e2 str x2,[sp,#32] + // f9000fe1 str x1,[sp,#24] + // f90007e0 str x0,[sp,#8] + // f94007e0 ldr x0,[sp,#8] + // f9400009 ldr x9,[x0] + // f9400129 ldr x9,[x9,#...] + // 910143ff add sp,sp,#80 + // d61f0120 br x9 + // With optimisation, clang produces thunks like: + // d10103ff sub sp,sp,#64 + // a9008be1 stp x1,x2,[sp,#8] + // a90193e3 stp x3,x4,[sp,#24] + // a9029be5 stp x5,x6,[sp,#40] + // f9001fe7 str x7,[sp,#56] + // f9400009 ldr x9,[x0] + // f9400129 ldr x9,[x9,#...] + // 910103ff add sp,sp,#64 + // d61f0120 br x9 + // It's more effort than it's worth to try decoding these + // thunks. + + } + return std::uintptr_t(func); +#else + return entrypoint; +#endif +} + + +std::pair<std::uintptr_t, bool> bypass_member_function_thunks( + std::uintptr_t entrypoint) noexcept +{ +#if defined(__x86_64__) || defined(_M_X64) + std::uint8_t const *func = reinterpret_cast<std::uint8_t const *>(entrypoint); + while (true) + { + // Assumes Windows calling convention, and doesn't consider that + // the "this" pointer could be in RDX if RCX is a pointer to + // space for an oversize scalar result. Since the result area + // is uninitialised on entry, you won't see something that looks + // like a vtable dispatch through RCX in this case - it won't + // behave badly, it just won't bypass virtual call thunks in the + // rare situations where the return type is an oversize scalar. + if (0xe9 == func[0]) + { + // relative jump with 32-bit displacement (typically a resolved PLT entry) + LOG("Found relative jump at %p ", func); + func += std::ptrdiff_t(5) + *reinterpret_cast<std::int32_t const *>(func + 1); + LOG("redirecting to %p\n", func); + continue; + } + else if ((0x48 == func[0]) && (0x8b == func[1]) && (0x01 == func[2])) + { + if ((0xff == func[3]) && ((0x20 == func[4]) || (0x60 == func[4]) || (0xa0 == func[4]))) + { + // MSVC virtual function call thunk - mov rax,QWORD PTR [rcx] ; jmp QWORD PTR [rax+...] + LOG("Found virtual member function thunk at %p\n", func); + if (0x20 == func[4]) // no displacement + return std::make_pair(std::uintptr_t(0), true); + else if (0x60 == func[4]) // 8-bit displacement + return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int8_t const *>(func + 5)), true); + else // 32-bit displacement + return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int32_t const *>(func + 5)), true); + } + else if ((0x48 == func[3]) && (0x8b == func[4])) + { + // clang virtual function call thunk - mov rax,QWORD PTR [rcx] ; mov rax,QWORD PTR [rax+...] ; jmp rax + if ((0x00 == func[5]) && (0x48 == func[6]) && (0xff == func[7]) && (0xe0 == func[8])) + { + // no displacement + LOG("Found virtual member function thunk at %p\n", func); + return std::make_pair(std::uintptr_t(0), true); + } + else if ((0x40 == func[5]) && (0x48 == func[7]) && (0xff == func[8]) && (0xe0 == func[9])) + { + // 8-bit displacement + LOG("Found virtual member function thunk at %p\n", func); + return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int8_t const *>(func + 6)), true); + } + else if ((0x80 == func[5]) && (0x48 == func[10]) && (0xff == func[11]) && (0xe0 == func[12])) + { + // 32-bit displacement + LOG("Found virtual member function thunk at %p\n", func); + return std::make_pair(std::uintptr_t(*reinterpret_cast<std::int32_t const *>(func + 6)), true); + } + } + } + + // clang uses unoptimised thunks if optimisation is disabled + // Without optimisation, clang produces thunks like: + // 50 push rax + // 48 89 0c 24 mov QWORD PTR [rsp],rcx + // 48 8b 0c 24 mov rcx,QWORD PTR [rsp] + // 48 8b 01 mov rax,QWORD PTR [rcx] + // 48 8b 80 xx xx xx xx mov rax,QWORD PTR [rax+...] + // 41 5a pop r10 + // 48 ff e0 jmp rax + // Trying to decode these thunks likely isn't worth the effort. + // Chasing performance in unoptimised builds isn't very useful, + // and the format of these thunks may be fragile. + + // not something we can easily bypass + break; + } + return std::make_pair(std::uintptr_t(func), false); +#elif defined(__aarch64__) || defined(_M_ARM64) + std::uint32_t const *func = reinterpret_cast<std::uint32_t const *>(entrypoint); + auto const fetch = [&func] (auto offset) { return little_endianize_int32(func[offset]); }; + while (true) + { + if ((0x90000010 == (fetch(0) & 0x9f00001f)) && (0x91000210 == (fetch(1) & 0xffc003ff)) && (0xd61f0200 == fetch(2))) + { + // page-relative jump with +/-4GB reach - adrp xip0,... ; add xip0,xip0,#... ; br xip0 + LOG("Found page-relative jump at %p ", func); + std::int64_t const page = + (std::uint64_t(fetch(0) & 0x60000000) >> 17) | + (std::uint64_t(fetch(0) & 0x00ffffe0) << 9) | + ((fetch(0) & 0x00800000) ? (~std::uint64_t(0) << 33) : 0); + std::uint32_t const offset = (fetch(1) & 0x003ffc00) >> 10; + func = reinterpret_cast<std::uint32_t const *>(((std::uintptr_t(func) + page) & (~std::uintptr_t(0) << 12)) + offset); + LOG("redirecting to %p\n", func); + } + else if ((0xf9400010 == fetch(0)) && (0xf9400210 == (fetch(1) & 0xffc003ff)) && (0xd61f0200 == fetch(2))) + { + // virtual function call thunk - ldr xip0,[x0] ; ldr xip0,[x0,#...] ; br xip0 + LOG("Found virtual member function thunk at %p\n", func); + return std::make_pair(std::uintptr_t((fetch(1) & 0x003ffc00) >> (10 - 3)), true); + } + else + { + // not something we can easily bypass + break; + } + + // clang uses horribly sub-optimal thunks for AArch64 + // Without optimisation, clang produces thunks like: + // d10143ff sub sp,sp,#80 + // f90027e7 str x7,[sp,#72] + // f90023e6 str x6,[sp,#64] + // f9001fe5 str x5,[sp,#56] + // f9001be4 str x4,[sp,#48] + // f90017e3 str x3,[sp,#40] + // f90013e2 str x2,[sp,#32] + // f9000fe1 str x1,[sp,#24] + // f90007e0 str x0,[sp,#8] + // f94007e0 ldr x0,[sp,#8] + // f9400009 ldr x9,[x0] + // f9400129 ldr x9,[x9,#...] + // 910143ff add sp,sp,#80 + // d61f0120 br x9 + // With optimisation, clang produces thunks like: + // d10103ff sub sp,sp,#64 + // a9008be1 stp x1,x2,[sp,#8] + // a90193e3 stp x3,x4,[sp,#24] + // a9029be5 stp x5,x6,[sp,#40] + // f9001fe7 str x7,[sp,#56] + // f9400009 ldr x9,[x0] + // f9400129 ldr x9,[x9,#...] + // 910103ff add sp,sp,#64 + // d61f0120 br x9 + // It's more effort than it's worth to try decoding these + // thunks. + + } + return std::make_pair(std::uintptr_t(func), false); +#else + return std::make_pair(entrypoint, false); +#endif +} + +} // namespace util::detail diff --git a/src/lib/util/mfpresolve.h b/src/lib/util/mfpresolve.h new file mode 100644 index 00000000000..eb3fba2b85d --- /dev/null +++ b/src/lib/util/mfpresolve.h @@ -0,0 +1,151 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +/*************************************************************************** + + mfpresolve.h + + Helpers for resolving member function pointers to entry points. + +***************************************************************************/ +#ifndef MAME_LIB_UTIL_MFPRESOLVE_H +#define MAME_LIB_UTIL_MFPRESOLVE_H + +#pragma once + +#include "abi.h" + +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <tuple> +#include <utility> + + +namespace util { + +namespace detail { + +struct mfp_itanium_equiv +{ + std::uintptr_t function; + std::ptrdiff_t delta; + + constexpr std::ptrdiff_t this_delta() const noexcept { return delta >> ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? 1 : 0); } + constexpr bool is_virtual() const noexcept { return ((MAME_ABI_CXX_ITANIUM_MFP_TYPE == MAME_ABI_CXX_ITANIUM_MFP_ARM) ? delta : function) & 1; } +}; + +struct mfp_msvc_single_equiv { std::uintptr_t entrypoint; }; +struct mfp_msvc_multi_equiv { std::uintptr_t entrypoint; int delta; }; +struct mfp_msvc_virtual_equiv { std::uintptr_t entrypoint; int delta; int vindex; }; +struct mfp_msvc_unknown_equiv { std::uintptr_t entrypoint; int delta; int voffset; int vindex; }; + +std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_itanium(std::uintptr_t function, std::ptrdiff_t delta, void const *object) noexcept; +std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function_itanium(std::uintptr_t function, std::ptrdiff_t delta) noexcept; +std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function_msvc(void const *funcptr, std::size_t size, void const *object) noexcept; +std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function_msvc(void const *funcptr, std::size_t size) noexcept; +std::uintptr_t bypass_member_function_thunks(std::uintptr_t entrypoint, void const *object) noexcept; +std::pair<std::uintptr_t, bool> bypass_member_function_thunks(std::uintptr_t entrypoint) noexcept; + +} // namespace detail + + +template <typename T, typename U> +inline T bypass_member_function_thunks(T entrypoint, U const *object) noexcept +{ + return reinterpret_cast<T>( + detail::bypass_member_function_thunks( + reinterpret_cast<std::uintptr_t>(entrypoint), + reinterpret_cast<void const *>(object))); +} + + +template <typename T, typename Ret, typename... Params> +inline std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function(Ret (T::*function)(Params...), T &object) noexcept +{ + if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_ITANIUM) + { + detail::mfp_itanium_equiv equiv; + assert(sizeof(function) == sizeof(equiv)); + *reinterpret_cast<decltype(function) *>(&equiv) = function; + return detail::resolve_member_function_itanium(equiv.function, equiv.delta, &object); + } + else if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC) + { + return detail::resolve_member_function_msvc(&function, sizeof(function), &object); + } + else + { + return std::make_pair( + std::uintptr_t(static_cast<void (*)()>(nullptr)), + std::uintptr_t(static_cast<void *>(nullptr))); + } +} + + +template <typename T, typename Ret, typename... Params> +inline std::pair<std::uintptr_t, std::uintptr_t> resolve_member_function(Ret (T::*function)(Params...) const, T const &object) noexcept +{ + if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_ITANIUM) + { + detail::mfp_itanium_equiv equiv; + assert(sizeof(function) == sizeof(equiv)); + *reinterpret_cast<decltype(function) *>(&equiv) = function; + return detail::resolve_member_function_itanium(equiv.function, equiv.delta, &object); + } + else if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC) + { + return detail::resolve_member_function_msvc(&function, sizeof(function), &object); + } + else + { + return std::make_pair( + std::uintptr_t(static_cast<void (*)()>(nullptr)), + std::uintptr_t(static_cast<void *>(nullptr))); + } +} + + +template <typename T, typename Ret, typename... Params> +inline std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function(Ret (T::*function)(Params...)) noexcept +{ + if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_ITANIUM) + { + detail::mfp_itanium_equiv equiv; + assert(sizeof(function) == sizeof(equiv)); + *reinterpret_cast<decltype(function) *>(&equiv) = function; + return detail::resolve_member_function_itanium(equiv.function, equiv.delta); + } + else if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC) + { + return detail::resolve_member_function_msvc(&function, sizeof(function)); + } + else + { + return std::make_tuple(std::uintptr_t(static_cast<void (*)()>(nullptr)), std::ptrdiff_t(0), false); + } +} + + +template <typename T, typename Ret, typename... Params> +inline std::tuple<std::uintptr_t, std::ptrdiff_t, bool> resolve_member_function(Ret (T::*function)(Params...) const) noexcept +{ + if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_ITANIUM) + { + detail::mfp_itanium_equiv equiv; + assert(sizeof(function) == sizeof(equiv)); + *reinterpret_cast<decltype(function) *>(&equiv) = function; + return detail::resolve_member_function_itanium(equiv.function, equiv.delta); + } + else if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC) + { + return detail::resolve_member_function_msvc(&function, sizeof(function)); + } + else + { + return std::make_tuple(std::uintptr_t(static_cast<void (*)()>(nullptr)), std::ptrdiff_t(0), false); + } +} + +} // namespace util + +#endif // MAME_LIB_UTIL_MFPRESOLVE_H diff --git a/src/lib/util/msdib.cpp b/src/lib/util/msdib.cpp index e70ba1b592a..bf33991f116 100644 --- a/src/lib/util/msdib.cpp +++ b/src/lib/util/msdib.cpp @@ -20,6 +20,7 @@ #include <cassert> #include <cstdlib> #include <cstring> +#include <tuple> #define LOG_GENERAL (1U << 0) @@ -127,11 +128,10 @@ std::uint8_t dib_splat_sample(std::uint8_t val, unsigned bits) noexcept msdib_error dib_read_file_header(read_stream &fp, std::uint32_t &filelen) noexcept { - std::size_t actual; - // the bitmap file header doesn't use natural alignment bitmap_file_header file_header; - if (fp.read(file_header, sizeof(file_header), actual) || (sizeof(file_header) != actual)) + auto const [err, actual] = read(fp, file_header, sizeof(file_header)); + if (err || (sizeof(file_header) != actual)) { LOG("Error reading DIB file header\n"); return msdib_error::FILE_TRUNCATED; @@ -167,6 +167,7 @@ msdib_error dib_read_bitmap_header( std::size_t &row_bytes, std::uint32_t length) noexcept { + std::error_condition err; std::size_t actual; // check that these things haven't been padded somehow @@ -178,7 +179,8 @@ msdib_error dib_read_bitmap_header( if (sizeof(header.core) > length) return msdib_error::FILE_TRUNCATED; std::memset(&header, 0, sizeof(header)); - if (fp.read(&header.core.size, sizeof(header.core.size), actual) || (sizeof(header.core.size) != actual)) + std::tie(err, actual) = read(fp, &header.core.size, sizeof(header.core.size)); + if (err || (sizeof(header.core.size) != actual)) { LOG("Error reading DIB header size (length %u)\n", length); return msdib_error::FILE_TRUNCATED; @@ -208,7 +210,8 @@ msdib_error dib_read_bitmap_header( { palette_bytes = 3U; std::uint32_t const header_read(std::min<std::uint32_t>(header.core.size, sizeof(header.core)) - sizeof(header.core.size)); - if (fp.read(&header.core.width, header_read, actual) || (header_read != actual)) + std::tie(err, actual) = read(fp, &header.core.width, header_read); + if (err || (header_read != actual)) { LOG("Error reading DIB core header from image data (%u bytes)\n", length); return msdib_error::FILE_TRUNCATED; @@ -261,7 +264,8 @@ msdib_error dib_read_bitmap_header( { palette_bytes = 4U; std::uint32_t const header_read(std::min<std::uint32_t>(header.info.size, sizeof(header.info)) - sizeof(header.info.size)); - if (fp.read(&header.info.width, header_read, actual) || (header_read != actual)) + std::tie(err, actual) = read(fp, &header.info.width, header_read); + if (err || (header_read != actual)) { LOG("Error reading DIB info header from image data (%u bytes)\n", length); return msdib_error::FILE_TRUNCATED; @@ -445,7 +449,6 @@ msdib_error msdib_read_bitmap(random_read &fp, bitmap_argb32 &bitmap) noexcept msdib_error msdib_read_bitmap_data(random_read &fp, bitmap_argb32 &bitmap, std::uint32_t length, std::uint32_t dirheight) noexcept { // read the bitmap header - std::size_t actual; bitmap_headers header; unsigned palette_bytes; bool indexed; @@ -492,7 +495,8 @@ msdib_error msdib_read_bitmap_data(random_read &fp, bitmap_argb32 &bitmap, std:: std::unique_ptr<std::uint8_t []> palette_data(new (std::nothrow) std::uint8_t [palette_size]); if (!palette_data) return msdib_error::OUT_OF_MEMORY; - if (fp.read(palette_data.get(), palette_size, actual) || (palette_size != actual)) + auto const [err, actual] = read(fp, palette_data.get(), palette_size); + if (err || (palette_size != actual)) { LOG("Error reading palette from DIB image data (%u bytes)\n", length); return msdib_error::FILE_TRUNCATED; @@ -575,7 +579,8 @@ msdib_error msdib_read_bitmap_data(random_read &fp, bitmap_argb32 &bitmap, std:: int const y_inc(top_down ? 1 : -1); for (std::int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc) { - if (fp.read(row_data.get(), row_bytes, actual) || (row_bytes != actual)) + auto const [err, actual] = read(fp, row_data.get(), row_bytes); + if (err || (row_bytes != actual)) { LOG("Error reading DIB row %d data from image data\n", i); return msdib_error::FILE_TRUNCATED; @@ -638,7 +643,8 @@ msdib_error msdib_read_bitmap_data(random_read &fp, bitmap_argb32 &bitmap, std:: { for (std::int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc) { - if (fp.read(row_data.get(), mask_row_bytes, actual) || (mask_row_bytes != actual)) + auto const [err, actual] = read(fp, row_data.get(), mask_row_bytes); + if (err || (mask_row_bytes != actual)) { LOG("Error reading DIB mask row %d data from image data\n", i); return msdib_error::FILE_TRUNCATED; diff --git a/src/lib/util/options.h b/src/lib/util/options.h index 99db519a5e5..8b85b9f58e6 100644 --- a/src/lib/util/options.h +++ b/src/lib/util/options.h @@ -12,6 +12,7 @@ #define MAME_LIB_UTIL_OPTIONS_H #include "strformat.h" +#include "utilfwd.h" #include <algorithm> #include <exception> @@ -43,7 +44,6 @@ const int OPTION_PRIORITY_MAXIMUM = 255; // maximum priority //************************************************************************** struct options_entry; -namespace util { class core_file; } // exception thrown by core_options when an illegal request is made class options_exception : public std::exception diff --git a/src/lib/util/plaparse.cpp b/src/lib/util/plaparse.cpp index 423939b41a8..9f4d1c0bc14 100644 --- a/src/lib/util/plaparse.cpp +++ b/src/lib/util/plaparse.cpp @@ -68,11 +68,14 @@ static uint32_t suck_number(util::random_read &src) uint32_t value = 0; // find first digit - uint8_t ch; - std::size_t actual; bool found = false; - while (!src.read(&ch, 1, actual) && actual == 1) + while (true) { + uint8_t ch; + auto const [err, actual] = read(src, &ch, 1); + if (err || (1 != actual)) + break; + // loop over and accumulate digits if (isdigit(ch)) { @@ -189,8 +192,8 @@ static bool process_terms(jed_data *data, util::random_read &src, uint8_t ch, pa curoutput = 0; } - std::size_t actual; - if (src.read(&ch, 1, actual)) + auto const [err, actual] = read(src, &ch, 1); + if (err) return false; if (actual != 1) return true; @@ -227,9 +230,11 @@ static bool process_field(jed_data *data, util::random_read &src, parse_info *pi int destptr = 0; uint8_t seek; - std::size_t actual; - while (!src.read(&seek, 1, actual) && actual == 1 && isalpha(seek)) + while (true) { + auto const [err, actual] = read(src, &seek, 1); + if (err || (actual != 1) || !isalpha(seek)) + break; dest[destptr++] = tolower(seek); if (destptr == sizeof(dest)) break; @@ -275,8 +280,11 @@ static bool process_field(jed_data *data, util::random_read &src, parse_info *pi // output polarity (optional) case KW_PHASE: if (LOG_PARSE) printf("Phase...\n"); - while (!src.read(&seek, 1, actual) && actual == 1 && !iscrlf(seek) && pinfo->xorptr < (JED_MAX_FUSES/2)) + while (true) { + auto const [err, actual] = read(src, &seek, 1); + if (err || (actual != 1) || iscrlf(seek) || (pinfo->xorptr >= (JED_MAX_FUSES / 2))) + break; if (seek == '0' || seek == '1') { // 0 is negative @@ -322,19 +330,23 @@ int pla_parse(util::random_read &src, jed_data *result) result->numfuses = 0; memset(result->fusemap, 0, sizeof(result->fusemap)); - uint8_t ch; - std::size_t actual; - while (!src.read(&ch, 1, actual) && actual == 1) + while (true) { + std::error_condition err; + size_t actual; + uint8_t ch; + std::tie(err, actual) = read(src, &ch, 1); + if (err || (actual != 1)) + break; switch (ch) { // comment line case '#': - while (!src.read(&ch, 1, actual) && actual == 1) + do { - if (iscrlf(ch)) - break; + std::tie(err, actual) = read(src, &ch, 1); } + while (!err && (actual == 1) && !iscrlf(ch)); break; // keyword diff --git a/src/lib/util/png.cpp b/src/lib/util/png.cpp index 8e077f92114..ce28a866604 100644 --- a/src/lib/util/png.cpp +++ b/src/lib/util/png.cpp @@ -24,6 +24,7 @@ #include <cstdlib> #include <cstring> #include <new> +#include <tuple> namespace util { @@ -452,7 +453,7 @@ private: std::uint8_t tempbuff[4]; // fetch the length of this chunk - err = fp.read(tempbuff, 4, actual); + std::tie(err, actual) = read(fp, tempbuff, 4); if (err) return err; else if (4 != actual) @@ -460,7 +461,7 @@ private: length = fetch_32bit(tempbuff); // fetch the type of this chunk - err = fp.read(tempbuff, 4, actual); + std::tie(err, actual) = read(fp, tempbuff, 4); if (err) return err; else if (4 != actual) @@ -477,13 +478,8 @@ private: // read the chunk itself into an allocated memory buffer if (length) { - // allocate memory for this chunk - data.reset(new (std::nothrow) std::uint8_t [length]); - if (!data) - return std::errc::not_enough_memory; - - // read the data from the file - err = fp.read(data.get(), length, actual); + // allocate memory and read the data from the file + std::tie(err, data, actual) = read(fp, length); if (err) { data.reset(); @@ -500,7 +496,7 @@ private: } // read the CRC - err = fp.read(tempbuff, 4, actual); + std::tie(err, actual) = read(fp, tempbuff, 4); if (err) { data.reset(); @@ -789,8 +785,7 @@ public: std::uint8_t signature[sizeof(PNG_SIGNATURE)]; // read 8 bytes - std::size_t actual; - std::error_condition err = fp.read(signature, sizeof(signature), actual); + auto const [err, actual] = read(fp, signature, sizeof(signature)); if (err) return err; else if (sizeof(signature) != actual) @@ -941,7 +936,6 @@ std::error_condition png_info::add_text(std::string_view keyword, std::string_vi static std::error_condition write_chunk(write_stream &fp, const uint8_t *data, uint32_t type, uint32_t length) noexcept { std::error_condition err; - std::size_t written; std::uint8_t tempbuff[8]; std::uint32_t crc; @@ -951,30 +945,24 @@ static std::error_condition write_chunk(write_stream &fp, const uint8_t *data, u crc = crc32(0, tempbuff + 4, 4); // write that data - err = fp.write(tempbuff, 8, written); + std::tie(err, std::ignore) = write(fp, tempbuff, 8); if (err) return err; - else if (8 != written) - return std::errc::io_error; // append the actual data if (length > 0) { - err = fp.write(data, length, written); + std::tie(err, std::ignore) = write(fp, data, length); if (err) return err; - else if (length != written) - return std::errc::io_error; crc = crc32(crc, data, length); } // write the CRC put_32bit(tempbuff, crc); - err = fp.write(tempbuff, 4, written); + std::tie(err, std::ignore) = write(fp, tempbuff, 4); if (err) return err; - else if (4 != written) - return std::errc::io_error; return std::error_condition(); } @@ -993,7 +981,6 @@ static std::error_condition write_deflated_chunk(random_write &fp, uint8_t *data if (err) return err; - std::size_t written; std::uint8_t tempbuff[8192]; std::uint32_t zlength = 0; z_stream stream; @@ -1006,11 +993,9 @@ static std::error_condition write_deflated_chunk(random_write &fp, uint8_t *data crc = crc32(0, tempbuff + 4, 4); // write that data - err = fp.write(tempbuff, 8, written); + std::tie(err, std::ignore) = write(fp, tempbuff, 8); if (err) return err; - else if (8 != written) - return std::errc::io_error; // initialize the stream memset(&stream, 0, sizeof(stream)); @@ -1036,17 +1021,12 @@ static std::error_condition write_deflated_chunk(random_write &fp, uint8_t *data if (stream.avail_out < sizeof(tempbuff)) { int bytes = sizeof(tempbuff) - stream.avail_out; - err = fp.write(tempbuff, bytes, written); + std::tie(err, std::ignore) = write(fp, tempbuff, bytes); if (err) { deflateEnd(&stream); return err; } - else if (bytes != written) - { - deflateEnd(&stream); - return std::errc::io_error; - } crc = crc32(crc, tempbuff, bytes); zlength += bytes; } @@ -1079,22 +1059,18 @@ static std::error_condition write_deflated_chunk(random_write &fp, uint8_t *data // write the CRC put_32bit(tempbuff, crc); - err = fp.write(tempbuff, 4, written); + std::tie(err, std::ignore) = write(fp, tempbuff, 4); if (err) return err; - else if (4 != written) - return std::errc::io_error; // seek back and update the length err = fp.seek(lengthpos, SEEK_SET); if (err) return err; put_32bit(tempbuff + 0, zlength); - err = fp.write(tempbuff, 4, written); + std::tie(err, std::ignore) = write(fp, tempbuff, 4); if (err) return err; - else if (4 != written) - return std::errc::io_error; // return to the end return fp.seek(lengthpos + 8 + zlength + 4, SEEK_SET); @@ -1326,12 +1302,9 @@ std::error_condition png_write_bitmap(random_write &fp, png_info *info, bitmap_t info = &pnginfo; // write the PNG signature - std::size_t written; - std::error_condition err = fp.write(PNG_SIGNATURE, sizeof(PNG_SIGNATURE), written); + auto const [err, written] = write(fp, PNG_SIGNATURE, sizeof(PNG_SIGNATURE)); if (err) return err; - else if (sizeof(PNG_SIGNATURE) != written) - return std::errc::io_error; // write the rest of the PNG data return write_png_stream(fp, *info, bitmap, palette_length, palette); @@ -1347,12 +1320,9 @@ std::error_condition png_write_bitmap(random_write &fp, png_info *info, bitmap_t std::error_condition mng_capture_start(random_write &fp, bitmap_t const &bitmap, unsigned rate) noexcept { - std::size_t written; - std::error_condition err = fp.write(MNG_Signature, 8, written); + auto const [err, written] = write(fp, MNG_Signature, 8); if (err) return err; - else if (8 != written) - return std::errc::io_error; uint8_t mhdr[28]; memset(mhdr, 0, 28); diff --git a/src/lib/util/server_http_impl.hpp b/src/lib/util/server_http_impl.hpp index e949e6b2483..7de440bce2c 100644 --- a/src/lib/util/server_http_impl.hpp +++ b/src/lib/util/server_http_impl.hpp @@ -63,10 +63,10 @@ namespace webpp { } } public: - virtual Response& status(int number) { m_ostream << statusToString(number); return *this; } - virtual void type(std::string str) { m_header << "Content-Type: "<< str << "\r\n"; } - virtual void send(std::string str) { m_ostream << m_header.str() << "Content-Length: " << str.length() << "\r\n\r\n" << str; } - virtual size_t size() const { return m_streambuf.size(); } + virtual Response& status(int number) override { m_ostream << statusToString(number); return *this; } + virtual void type(std::string str) override { m_header << "Content-Type: "<< str << "\r\n"; } + virtual void send(std::string str) override { m_ostream << m_header.str() << "Content-Length: " << str.length() << "\r\n\r\n" << str; } + virtual size_t size() const override { return m_streambuf.size(); } std::shared_ptr<socket_type> socket() { return m_socket; } /// If true, force server to close the connection after the response have been sent. diff --git a/src/lib/util/simh_tape_file.cpp b/src/lib/util/simh_tape_file.cpp index 4fe1af5ae88..ffd6a48aeb3 100644 --- a/src/lib/util/simh_tape_file.cpp +++ b/src/lib/util/simh_tape_file.cpp @@ -119,16 +119,14 @@ void simh_tape_file::raw_seek(const osd::u64 pos) const void simh_tape_file::raw_read(osd::u8 *const buf, const osd::u32 len) const { - size_t actual_len; - std::error_condition err = m_file.read(buf, len, actual_len); + auto const [err, actual_len] = read(m_file, buf, len); if (err || actual_len != len) // error: we failed to read expected number of bytes throw std::runtime_error(std::string("failed read: ") + (err ? err.message() : std::string("unexpected length"))); } void simh_tape_file::raw_write(const osd::u8 *const buf, const osd::u32 len) const { - size_t actual_len; - std::error_condition err = m_file.write(buf, len, actual_len); + auto const [err, actual_len] = write(m_file, buf, len); if (err || actual_len != len) // error: we failed to write expected number of bytes throw std::runtime_error(std::string("failed write: ") + (err ? err.message() : std::string("unexpected length"))); } diff --git a/src/lib/util/un7z.cpp b/src/lib/util/un7z.cpp index 362c95ee235..5ee97d62777 100644 --- a/src/lib/util/un7z.cpp +++ b/src/lib/util/un7z.cpp @@ -71,8 +71,7 @@ private: if (!size) return SZ_OK; - std::size_t read_length(0); - std::error_condition const err = file->read_at(currfpos, data, size, read_length); + auto const [err, read_length] = read_at(*file, currfpos, data, size); size = read_length; currfpos += read_length; diff --git a/src/lib/util/unzip.cpp b/src/lib/util/unzip.cpp index c1df469fed4..2ca8e722d25 100644 --- a/src/lib/util/unzip.cpp +++ b/src/lib/util/unzip.cpp @@ -154,8 +154,7 @@ public: while (cd_remaining) { std::size_t const chunk(std::size_t(std::min<std::uint64_t>(std::numeric_limits<std::size_t>::max(), cd_remaining))); - std::size_t read_length(0); - std::error_condition const filerr = m_file->read_at(m_ecd.cd_start_disk_offset + cd_offs, &m_cd[cd_offs], chunk, read_length); + auto const [filerr, read_length] = read_at(*m_file, m_ecd.cd_start_disk_offset + cd_offs, &m_cd[cd_offs], chunk); if (filerr) { osd_printf_error( @@ -914,7 +913,7 @@ std::error_condition zip_file_impl::decompress(void *buffer, std::size_t length) } // get the compressed data offset - std::uint64_t offset; + std::uint64_t offset = 0; auto const ziperr = get_compressed_data_offset(offset); if (ziperr) return ziperr; @@ -979,7 +978,7 @@ std::error_condition zip_file_impl::read_ecd() noexcept } // read in one buffers' worth of data - filerr = m_file->read_at(m_length - buflen, &buffer[0], buflen, read_length); + std::tie(filerr, read_length) = read_at(*m_file, m_length - buflen, &buffer[0], buflen); if (filerr) { osd_printf_error( @@ -1024,11 +1023,11 @@ std::error_condition zip_file_impl::read_ecd() noexcept } // try to read the ZIP64 ECD locator - filerr = m_file->read_at( + std::tie(filerr, read_length) = read_at( + *m_file, m_length - buflen + offset - ecd64_locator_reader::minimum_length(), &buffer[0], - ecd64_locator_reader::minimum_length(), - read_length); + ecd64_locator_reader::minimum_length()); if (filerr) { osd_printf_error( @@ -1058,7 +1057,7 @@ std::error_condition zip_file_impl::read_ecd() noexcept } // try to read the ZIP64 ECD - filerr = m_file->read_at(ecd64_loc_rd.ecd64_offset(), &buffer[0], ecd64_reader::minimum_length(), read_length); + std::tie(filerr, read_length) = read_at(*m_file, ecd64_loc_rd.ecd64_offset(), &buffer[0], ecd64_reader::minimum_length()); if (filerr) { osd_printf_error( @@ -1160,8 +1159,7 @@ std::error_condition zip_file_impl::get_compressed_data_offset(std::uint64_t &of return ziperr; // now go read the fixed-sized part of the local file header - std::size_t read_length; - std::error_condition const filerr = m_file->read_at(m_header.local_header_offset, &m_buffer[0], local_file_header_reader::minimum_length(), read_length); + auto const [filerr, read_length] = read_at(*m_file, m_header.local_header_offset, &m_buffer[0], local_file_header_reader::minimum_length()); if (filerr) { osd_printf_error( @@ -1205,8 +1203,7 @@ std::error_condition zip_file_impl::get_compressed_data_offset(std::uint64_t &of std::error_condition zip_file_impl::decompress_data_type_0(std::uint64_t offset, void *buffer, std::size_t length) noexcept { // the data is uncompressed; just read it - std::size_t read_length(0); - std::error_condition const filerr = m_file->read_at(offset, buffer, m_header.compressed_length, read_length); + auto const [filerr, read_length] = read_at(*m_file, offset, buffer, m_header.compressed_length); if (filerr) { osd_printf_error( @@ -1277,12 +1274,11 @@ std::error_condition zip_file_impl::decompress_data_type_8(std::uint64_t offset, while (true) { // read in the next chunk of data - std::size_t read_length(0); - auto const filerr = m_file->read_at( + auto const [filerr, read_length] = read_at( + *m_file, offset, &m_buffer[0], - std::size_t(std::min<std::uint64_t>(input_remaining, m_buffer.size())), - read_length); + std::size_t(std::min<std::uint64_t>(input_remaining, m_buffer.size()))); if (filerr) { osd_printf_error( @@ -1393,7 +1389,7 @@ std::error_condition zip_file_impl::decompress_data_type_14(std::uint64_t offset m_header.file_name, m_filename); return archive_file::error::DECOMPRESS_ERROR; } - filerr = m_file->read_at(offset, &m_buffer[0], 4, read_length); + std::tie(filerr, read_length) = read_at(*m_file, offset, &m_buffer[0], 4); if (filerr) { osd_printf_error( @@ -1425,7 +1421,7 @@ std::error_condition zip_file_impl::decompress_data_type_14(std::uint64_t offset m_header.file_name, m_filename); return archive_file::error::DECOMPRESS_ERROR; } - filerr = m_file->read_at(offset, &m_buffer[0], props_size, read_length); + std::tie(filerr, read_length) = read_at(*m_file, offset, &m_buffer[0], props_size); if (filerr) { osd_printf_error( @@ -1472,11 +1468,11 @@ std::error_condition zip_file_impl::decompress_data_type_14(std::uint64_t offset while (0 < input_remaining) { // read in the next chunk of data - filerr = m_file->read_at( + std::tie(filerr, read_length) = read_at( + *m_file, offset, &m_buffer[0], - std::size_t((std::min<std::uint64_t>)(input_remaining, m_buffer.size())), - read_length); + std::size_t((std::min<std::uint64_t>)(input_remaining, m_buffer.size()))); if (filerr) { osd_printf_error( @@ -1569,12 +1565,11 @@ std::error_condition zip_file_impl::decompress_data_type_93(std::uint64_t offset while (input_remaining && length) { // read in the next chunk of data - std::size_t read_length(0); - auto const filerr = m_file->read_at( + auto const [filerr, read_length] = read_at( + *m_file, offset, &m_buffer[0], - std::size_t(std::min<std::uint64_t>(input_remaining, m_buffer.size())), - read_length); + std::size_t(std::min<std::uint64_t>(input_remaining, m_buffer.size()))); if (filerr) { osd_printf_error( diff --git a/src/lib/util/xmlfile.cpp b/src/lib/util/xmlfile.cpp index 39604a29a87..db8d87930e2 100644 --- a/src/lib/util/xmlfile.cpp +++ b/src/lib/util/xmlfile.cpp @@ -41,14 +41,14 @@ constexpr unsigned TEMP_BUFFER_SIZE(4096U); void write_escaped(core_file &file, std::string const &str) { + // FIXME: check for errors std::string::size_type pos = 0; while ((str.size() > pos) && (std::string::npos != pos)) { std::string::size_type const found = str.find_first_of("\"&<>", pos); if (found != std::string::npos) { - std::size_t written; - file.write(&str[pos], found - pos, written); + write(file, &str[pos], found - pos); switch (str[found]) { case '"': file.puts("""); pos = found + 1; break; @@ -60,8 +60,7 @@ void write_escaped(core_file &file, std::string const &str) } else { - std::size_t written; - file.write(&str[pos], str.size() - pos, written); + write(file, &str[pos], str.size() - pos); pos = found; } } @@ -77,10 +76,12 @@ void write_escaped(core_file &file, std::string const &str) struct parse_info { + parse_info() { memset(&parser, 0, sizeof(parser)); } + XML_Parser parser; file::ptr rootnode; - data_node * curnode; - uint32_t flags; + data_node * curnode = nullptr; + uint32_t flags = 0; }; @@ -134,8 +135,7 @@ file::ptr file::read(read_stream &file, parse_options const *opts) char tempbuf[TEMP_BUFFER_SIZE]; // read as much as we can - size_t bytes; - file.read(tempbuf, sizeof(tempbuf), bytes); // TODO: better error handling + auto const [err, bytes] = util::read(file, tempbuf, sizeof(tempbuf)); // FIXME: better error handling done = !bytes; // parse the data @@ -815,7 +815,7 @@ std::string normalize_string(std::string_view string) static bool expat_setup_parser(parse_info &info, parse_options const *opts) { // setup info structure - memset(&info, 0, sizeof(info)); + info = parse_info(); if (opts != nullptr) { info.flags = opts->flags; |