summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib
diff options
context:
space:
mode:
author nhand42 <38346367+nhand42@users.noreply.github.com>2020-12-18 03:48:47 +1100
committer GitHub <noreply@github.com>2020-12-17 18:48:47 +0200
commitccacd0f16550cd104c7a0b1b10358b7e18aeabf3 (patch)
tree1787d8117ee659590316925601a4736c635a98b8 /src/lib
parent0e09ae932831a1f0907d330d5c392281340f3ef4 (diff)
support Redump extended bin/cue format for Dreamcast discs (#7422)
* initial check-in of Redump bin/cue support for Dreamcast GDI * correctly identifies multi-cue format and sets GDROM flags * creates a working Crazy Taxi chd from a Redump bin/cue * disabled debugging code and started tidying up * simple tool to compare chdman bin/cue and bin/gdi conversions, should be identical * final tidy up, the testing is going well * testing failed for Aero Dancing i (Japan), didnt zero last track * added some comments about .gdi compatibility * addressing review feedback on pull request #7422 * match TOSEC layout for Pattern I discs (3 tracks) * initial support for Pattern III discs * Pattern III discs now work and match TOSEC layout * reading datasize from wrong track, same result though * identify the GDI pattern, makes the code clearer * support for Pattern II and consecutive AUDIO tracks * use C99 type not POSIX type to build on Windows * support Redump tracks split across two .bin files
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/util/cdrom.h4
-rw-r--r--src/lib/util/chdcd.cpp516
2 files changed, 519 insertions, 1 deletions
diff --git a/src/lib/util/cdrom.h b/src/lib/util/cdrom.h
index 50b762d5009..a5b6beda806 100644
--- a/src/lib/util/cdrom.h
+++ b/src/lib/util/cdrom.h
@@ -82,11 +82,15 @@ struct cdrom_track_info
/* 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 */
/* fields used in MAME/MESS only */
uint32_t logframeofs; /* logical frame of actual track data - offset by pregap size if pregap not physically present */
uint32_t physframeofs; /* physical frame of actual track data in CHD data */
uint32_t chdframeofs; /* frame number this track starts at on the CHD */
+
+ /* fields used in multi-cue GDI */
+ uint32_t multicuearea;
};
diff --git a/src/lib/util/chdcd.cpp b/src/lib/util/chdcd.cpp
index b2b0e141499..605b629c623 100644
--- a/src/lib/util/chdcd.cpp
+++ b/src/lib/util/chdcd.cpp
@@ -36,6 +36,19 @@
#define TOKENIZE i = tokenize( linebuffer, i, sizeof(linebuffer), token, sizeof(token) );
+enum gdi_area {
+ SINGLE_DENSITY,
+ HIGH_DENSITY
+};
+
+enum gdi_pattern {
+ TYPE_UNKNOWN = 0,
+ TYPE_I,
+ TYPE_II,
+ TYPE_III,
+ TYPE_III_SPLIT
+};
+
/***************************************************************************
GLOBAL VARIABLES
@@ -1129,6 +1142,504 @@ chd_error chdcd_parse_cue(const char *tocfname, cdrom_toc &outtoc, chdcd_track_i
return CHDERR_NONE;
}
+/*---------------------------------------------------------------------------------------
+ chdcd_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 chdcd_is_gdicue(const char *tocfname)
+{
+ FILE *infile;
+ bool has_rem_singledensity = false;
+ bool has_rem_highdensity = false;
+ std::string path = std::string(tocfname);
+
+ infile = fopen(tocfname, "rt");
+ path = get_file_path(path);
+ if (infile == (FILE *)nullptr)
+ {
+ return false;
+ }
+
+ while (!feof(infile))
+ {
+ fgets(linebuffer, 511, infile);
+
+ /* if EOF didn't hit, keep going */
+ if (!feof(infile))
+ {
+ 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;
+}
+
+/*-----------------------------------------------------------------
+ chdcd_parse_gdicue - parse a Redump multi-CUE for Dreamcast GDI
+------------------------------------------------------------------*/
+
+/**
+ * @fn chd_error chdcd_parse_gdicue(const char *tocfname, cdrom_toc &outtoc, chdcd_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 chd_error.
+ *
+ * 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.
+ */
+
+chd_error chdcd_parse_gdicue(const char *tocfname, cdrom_toc &outtoc, chdcd_track_input_info &outinfo)
+{
+ FILE *infile;
+ 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;
+
+ infile = fopen(tocfname, "rt");
+ path = get_file_path(path);
+ if (infile == (FILE *)nullptr)
+ {
+ return CHDERR_FILE_NOT_FOUND;
+ }
+
+ /* clear structures */
+ memset(&outtoc, 0, sizeof(outtoc));
+ outinfo.reset();
+
+ trknum = -1;
+ wavoffs = wavlen = 0;
+
+ outtoc.flags = CD_FLAG_GDROM;
+
+ while (!feof(infile))
+ {
+ /* get the next line */
+ fgets(linebuffer, 511, infile);
+
+ /* if EOF didn't hit, keep going */
+ if (!feof(infile))
+ {
+ /* single-density area starts LBA = 0 */
+ if (!strncmp(linebuffer, "REM SINGLE-DENSITY AREA", 23))
+ {
+ current_area = SINGLE_DENSITY;
+ continue;
+ }
+
+ /* high-density area starts LBA = 45000 */
+ if (!strncmp(linebuffer, "REM HIGH-DENSITY AREA", 21))
+ {
+ current_area = HIGH_DENSITY;
+ continue;
+ }
+
+ i = 0;
+
+ TOKENIZE
+
+ if (!strcmp(token, "FILE"))
+ {
+ /* 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"))
+ {
+ outinfo.track[trknum+1].swap = true;
+ }
+ else if (!strcmp(token, "WAVE"))
+ {
+ wavlen = parse_wav_sample(lastfname.c_str(), &wavoffs);
+ if (!wavlen)
+ {
+ fclose(infile);
+ printf("ERROR: couldn't read [%s] or not a valid .WAV\n", lastfname.c_str());
+ return CHDERR_INVALID_DATA;
+ }
+ }
+ else
+ {
+ fclose(infile);
+ printf("ERROR: Unhandled track type %s\n", token);
+ return CHDERR_UNSUPPORTED_FORMAT;
+ }
+ }
+ else if (!strcmp(token, "TRACK"))
+ {
+ /* get the track number */
+ TOKENIZE
+ trknum = strtoul(token, nullptr, 10) - 1;
+
+ /* next token on the line is the track 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 0
+ 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);
+#endif
+
+ cdrom_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 CHDERR_UNSUPPORTED_FORMAT;
+ }
+
+ /* next (optional) token on the line is the subcode type */
+ TOKENIZE
+
+ cdrom_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 number */
+ TOKENIZE
+ idx = strtoul(token, nullptr, 10);
+
+ /* get index */
+ 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;
+ }
+ }
+ }
+ else if (!strcmp(token, "PREGAP"))
+ {
+ int frames;
+
+ /* get index */
+ TOKENIZE
+ frames = msf_to_frames( token );
+
+ 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;
+ }
+ }
+ }
+
+ /* 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)
+ {
+ 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)
+ {
+ // is this the last track?
+ if (trknum == (outtoc.numtrks-1))
+ {
+ /* 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.c_str());
+ if (tlen == 0)
+ {
+ printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str());
+ return CHDERR_FILE_NOT_FOUND;
+ }
+ 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.c_str());
+ if (tlen == 0)
+ {
+ printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str());
+ return CHDERR_FILE_NOT_FOUND;
+ }
+ tlen /= (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
+ outtoc.tracks[trknum].frames = tlen;
+ outinfo.track[trknum].offset = 0;
+ }
+ }
+ else
+ {
+ /* if we have the same filename as the next track, do it that way */
+ if (outinfo.track[trknum].fname.compare(outinfo.track[trknum+1].fname)==0)
+ {
+ outtoc.tracks[trknum].frames = outinfo.track[trknum+1].idx0offs - outinfo.track[trknum].idx0offs;
+
+ 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);
+ }
+
+ if (!outtoc.tracks[trknum].frames)
+ {
+ printf("ERROR: unable to determine size of track %d, missing INDEX 01 markers?\n", trknum+1);
+ return CHDERR_INVALID_DATA;
+ }
+ }
+ else /* data files are different */
+ {
+ tlen = get_file_size(outinfo.track[trknum].fname.c_str());
+ if (tlen == 0)
+ {
+ printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum].fname.c_str());
+ return CHDERR_FILE_NOT_FOUND;
+ }
+ 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 (outtoc.tracks[outtoc.numtrks-2].pgtype == CD_TRACK_AUDIO)
+ disc_pattern = TYPE_III_SPLIT;
+ else
+ disc_pattern = TYPE_III;
+ }
+ else if (outtoc.numtrks > 3)
+ {
+ if (outtoc.tracks[outtoc.numtrks-1].pgtype == CD_TRACK_AUDIO)
+ disc_pattern = TYPE_II;
+ else
+ disc_pattern = TYPE_III;
+ }
+ else if (outtoc.numtrks == 3)
+ {
+ disc_pattern = TYPE_I;
+ }
+
+ /*
+ * Strip pregaps from Redump tracks and adjust the LBA offset to match TOSEC layout
+ */
+ for (trknum = 1; trknum < outtoc.numtrks; trknum++)
+ {
+ uint32_t prev_pregap = outtoc.tracks[trknum-1].pregap;
+ uint32_t prev_offset = prev_pregap * (outtoc.tracks[trknum-1].datasize + outtoc.tracks[trknum-1].subsize);
+ uint32_t this_pregap = outtoc.tracks[trknum].pregap;
+ uint32_t this_offset = this_pregap * (outtoc.tracks[trknum].datasize + outtoc.tracks[trknum].subsize);
+
+ if (outtoc.tracks[trknum-1].pgtype != CD_TRACK_AUDIO)
+ {
+ // pad previous DATA track to match TOSEC layout
+ outtoc.tracks[trknum-1].frames += this_pregap;
+ outtoc.tracks[trknum-1].padframes += this_pregap;
+ }
+
+ if (outtoc.tracks[trknum-1].pgtype == CD_TRACK_AUDIO && outtoc.tracks[trknum].pgtype == CD_TRACK_AUDIO)
+ {
+ // shift previous AUDIO track to match TOSEC layout
+ outinfo.track[trknum-1].offset += prev_offset;
+ outtoc.tracks[trknum-1].splitframes += prev_pregap;
+ }
+
+ if (outtoc.tracks[trknum-1].pgtype == CD_TRACK_AUDIO && outtoc.tracks[trknum].pgtype != CD_TRACK_AUDIO)
+ {
+ // shrink previous AUDIO track to match TOSEC layout
+ outtoc.tracks[trknum-1].frames -= prev_pregap;
+ outinfo.track[trknum-1].offset += prev_offset;
+ }
+
+ if (outtoc.tracks[trknum].pgtype == CD_TRACK_AUDIO && trknum == outtoc.numtrks-1)
+ {
+ // shrink final AUDIO track to match TOSEC layout
+ outtoc.tracks[trknum].frames -= this_pregap;
+ outinfo.track[trknum].offset += this_offset;
+ }
+ }
+
+ /*
+ * Special handling for TYPE_III_SPLIT, pregap in last track contains 75 frames audio and 150 frames data
+ */
+ if (disc_pattern == TYPE_III_SPLIT)
+ {
+ assert(outtoc.tracks[outtoc.numtrks-1].pregap == 225);
+
+ // 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;
+
+ // 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);
+ }
+
+ /*
+ * 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;
+ }
+
+ // no longer need the pregap info, zeroed out to match TOSEC layout
+ outtoc.tracks[trknum].pregap = 0;
+ outtoc.tracks[trknum].pgtype = 0;
+ }
+
+#if 0
+ 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);
+ }
+#endif
+
+ return CHDERR_NONE;
+}
+
/*-------------------------------------------------
chdcd_parse_toc - parse a CDRDAO format TOC file
-------------------------------------------------*/
@@ -1165,7 +1676,10 @@ chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc &outtoc, chdcd_track_i
if (strstr(tocftemp,".cue"))
{
- return chdcd_parse_cue(tocfname, outtoc, outinfo);
+ if (chdcd_is_gdicue(tocfname))
+ return chdcd_parse_gdicue(tocfname, outtoc, outinfo);
+ else
+ return chdcd_parse_cue(tocfname, outtoc, outinfo);
}
if (strstr(tocftemp,".nrg"))