diff options
Diffstat (limited to 'src/lib/util/cdrom.cpp')
-rw-r--r-- | src/lib/util/cdrom.cpp | 1766 |
1 files changed, 865 insertions, 901 deletions
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(); } |