From df62ba1451bc96711877937f22461e07d24b0163 Mon Sep 17 00:00:00 2001 From: 987123879113 <63495610+987123879113@users.noreply.github.com> Date: Mon, 13 May 2024 02:26:38 +0900 Subject: util/cdrom, machine/t10mmc: Add preliminary support for multisession CD-ROMs, indexes, and track flags (#12201) * util/cdrom: Refactoring * util/cdrom: Read all indexes from cue files * util/cdrom: Read in track flags from cues * util/cdrom: Multisession support for cues * machine/t10mmc: Playback from current head, other misc cleanup * machine/t10mmc: Implement T10MMC_CMD_MECHANISM_STATUS * machine/t10mmc: Implement TOC_FORMAT_FULL_TOC, TOC_FORMAT_SESSIONS, and return proper indexes for T10MMC_CMD_READ_SUB_CHANNEL --- src/devices/bus/ata/atapicdr.cpp | 20 +- src/devices/imagedev/cdromimg.cpp | 14 ++ src/devices/imagedev/cdromimg.h | 2 + src/devices/machine/t10mmc.cpp | 265 ++++++++++++++++++----- src/devices/machine/t10mmc.h | 9 +- src/lib/util/cdrom.cpp | 428 ++++++++++++++++++++++++++------------ src/lib/util/cdrom.h | 65 ++++-- 7 files changed, 601 insertions(+), 202 deletions(-) diff --git a/src/devices/bus/ata/atapicdr.cpp b/src/devices/bus/ata/atapicdr.cpp index aa32806913f..3b035b1b503 100644 --- a/src/devices/bus/ata/atapicdr.cpp +++ b/src/devices/bus/ata/atapicdr.cpp @@ -199,9 +199,19 @@ void atapi_cdrom_device::ExecCommand() } t10mmc::ExecCommand(); - // truckk requires seek complete flag to be set after calling the SEEK command - // so set the seek complete status flag after a successful request to emulate - // having asked the device itself to seek - if (command[0] == T10SBC_CMD_SEEK_10 && m_status_code == SCSI_STATUS_CODE_GOOD) - m_status |= IDE_STATUS_DSC; + if (m_status_code == SCSI_STATUS_CODE_GOOD) + { + switch (command[0]) + { + // Set the DSC (Drive Seek Complete) bit on commands that involve a drive seek. + // truckk is known to rely on this flag being set after T10SBC_CMD_SEEK_10. + case T10SBC_CMD_SEEK_10: + case T10MMC_CMD_PLAY_AUDIO_10: + case T10MMC_CMD_PLAY_AUDIO_12: + case T10MMC_CMD_PLAY_AUDIO_MSF: + case T10MMC_CMD_PLAY_AUDIO_TRACK_INDEX: + m_status |= IDE_STATUS_DSC; + break; + } + } } diff --git a/src/devices/imagedev/cdromimg.cpp b/src/devices/imagedev/cdromimg.cpp index a5ceca8b16d..5848eb349dc 100644 --- a/src/devices/imagedev/cdromimg.cpp +++ b/src/devices/imagedev/cdromimg.cpp @@ -184,6 +184,13 @@ int cdrom_image_device::get_last_track() const return 0; } +int cdrom_image_device::get_last_session() const +{ + if (m_cdrom_handle) + return m_cdrom_handle->get_last_session(); + return 0; +} + uint32_t cdrom_image_device::get_track(uint32_t frame) const { if (m_cdrom_handle) @@ -191,6 +198,13 @@ uint32_t cdrom_image_device::get_track(uint32_t frame) const return 0; } +uint32_t cdrom_image_device::get_track_index(uint32_t frame) const +{ + if (m_cdrom_handle) + return m_cdrom_handle->get_track_index(frame); + return 0; +} + uint32_t cdrom_image_device::get_track_start(uint32_t track) const { if (m_cdrom_handle) diff --git a/src/devices/imagedev/cdromimg.h b/src/devices/imagedev/cdromimg.h index 6e222703672..d37e0d5d64d 100644 --- a/src/devices/imagedev/cdromimg.h +++ b/src/devices/imagedev/cdromimg.h @@ -59,7 +59,9 @@ public: virtual const char *image_brief_type_name() const noexcept override { return "cdrm"; } int get_last_track() const; + int get_last_session() const; uint32_t get_track(uint32_t frame) const; + uint32_t get_track_index(uint32_t frame) const; uint32_t get_track_start(uint32_t track) const; bool read_data(uint32_t lbasector, void *buffer, uint32_t datatype, bool phys=false); bool read_subcode(uint32_t lbasector, void *buffer, bool phys=false); diff --git a/src/devices/machine/t10mmc.cpp b/src/devices/machine/t10mmc.cpp index bc3d6d0a95c..eac2c97f6d0 100644 --- a/src/devices/machine/t10mmc.cpp +++ b/src/devices/machine/t10mmc.cpp @@ -5,26 +5,25 @@ #include "multibyte.h" -static int to_msf_raw(int frame) +static int32_t to_msf_raw(int32_t frame) { - int m = frame / (75 * 60); - int s = (frame / 75) % 60; - int f = frame % 75; - + const int m = (frame / (75 * 60)) % 100; + const int s = (frame / 75) % 60; + const int f = frame % 75; return (m << 16) | (s << 8) | f; } -static int to_msf(int frame) +static int32_t to_msf(int32_t frame) { - int adjusted_frame = frame + 150; - if (frame <= -151) + int32_t adjusted_frame = frame + 150; + if (adjusted_frame < 0) adjusted_frame += 450000; return to_msf_raw(adjusted_frame); } -static int to_lba(int msf) +static int32_t to_lba(int32_t msf) { - int lba = cdrom_file::msf_to_lba(msf) - 150; + int32_t lba = cdrom_file::msf_to_lba(msf) - 150; if (BIT(msf, 16, 8) >= 90) // 90:00:00 and later lba -= 450000; return lba; @@ -256,6 +255,16 @@ void t10mmc::ExecCommand() length = 4 + (8 * 1); break; + case TOC_FORMAT_FULL_TOC: + { + const int total_tracks = m_image->get_last_track(); + const int total_sessions = m_image->get_last_session(); + const int total_a0s = total_sessions * 3; // every session has a set of a0/a1/a2 + const int total_b0s = (total_sessions - 1) * 2; // every additional session starts with a b0/c0 set + length = 4 + (11 * (total_tracks + total_a0s + total_b0s)); + break; + } + default: m_device->logerror("T10MMC: Unhandled READ TOC format %d\n", toc_format()); length = 0; @@ -288,6 +297,12 @@ void t10mmc::ExecCommand() m_lba = get_u32be(&command[2]); m_blocks = SCSILengthFromUINT16( &command[7] ); + if (m_lba == 0xffffffff) + { + m_device->logerror("T10MMC: play audio from current not fully implemented!\n"); + m_lba = m_cdda->get_audio_lba(); + } + if (m_lba == 0) { // A request for LBA 0 will return something different depending on the type of media being played. @@ -298,10 +313,6 @@ void t10mmc::ExecCommand() else m_lba = 150; } - else if (m_lba == 0xffffffff) - { - m_device->logerror("T10MMC: play audio from current not implemented!\n"); - } //m_device->logerror("T10MMC: PLAY AUDIO(10) at LBA %x for %x blocks\n", m_lba, m_blocks); @@ -334,23 +345,19 @@ void t10mmc::ExecCommand() break; } - const uint32_t msf_start = get_u24be(&command[3]); + uint32_t msf_start = get_u24be(&command[3]); const uint32_t msf_end = get_u24be(&command[6]); - int32_t lba_start = to_lba(msf_start); - int32_t lba_end = to_lba(msf_end); - - // LBA valid range is technically -45150 to 404849 but negatives are not handled anywhere - if (lba_start < 0 || lba_end < 0) + if (msf_start == 0xffffff) { - m_device->logerror("T10MMC: tried playing audio from lba %d to %d\n", lba_start, lba_end); - m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION; - set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE); - break; + // when start MSF is set to all FFs, the starting address becomes the current optical head location + m_lba = m_cdda->get_audio_lba(); + msf_start = to_msf(m_lba); } - m_lba = lba_start; - m_blocks = lba_end - lba_start; + // note: BeOS's CD player sends the start MSF + a large end MSF (99:59:71) when a scan is ended and it wants to resume playback + m_lba = to_lba(msf_start); + m_blocks = cdrom_file::msf_to_lba(msf_end - msf_start); if (m_lba == 0) { @@ -372,13 +379,6 @@ void t10mmc::ExecCommand() m_status_code = SCSI_STATUS_CODE_GOOD; m_audio_sense = SCSI_SENSE_ASC_ASCQ_NO_SENSE; } - else if (msf_start == 0xffffff) - { - // when start MSF is set to all FFs, the starting address becomes the current optical head location - m_device->logerror("T10MMC: play audio from current not implemented!\n"); - m_status_code = SCSI_STATUS_CODE_CHECK_CONDITION; - set_sense(SCSI_SENSE_KEY_ILLEGAL_REQUEST, SCSI_SENSE_ASC_ASCQ_AUDIO_PLAY_OPERATION_STOPPED_DUE_TO_ERROR); - } else if (msf_start > msf_end) { m_device->logerror("T10MMC: track starts after requested end time!\n"); @@ -479,6 +479,8 @@ void t10mmc::ExecCommand() break; case T10MMC_CMD_STOP_PLAY_SCAN: + m_last_lba = m_cdda->get_audio_lba(); + abort_audio(); //m_device->logerror("T10MMC: STOP_PLAY_SCAN\n"); @@ -512,6 +514,12 @@ void t10mmc::ExecCommand() m_lba = get_u32be(&command[2]); m_blocks = get_u32be(&command[6]); + if (m_lba == 0xffffffff) + { + m_device->logerror("T10MMC: play audio from current not fully implemented!\n"); + m_lba = m_cdda->get_audio_lba(); + } + if (m_lba == 0) { if (m_image->get_track_type(0) == cdrom_file::CD_TRACK_AUDIO) @@ -519,10 +527,6 @@ void t10mmc::ExecCommand() else m_lba = 150; } - else if (m_lba == 0xffffffff) - { - m_device->logerror("T10MMC: play audio from current not implemented!\n"); - } //m_device->logerror("T10MMC: PLAY AUDIO(12) at LBA %x for %x blocks\n", m_lba, m_blocks); @@ -582,6 +586,13 @@ void t10mmc::ExecCommand() m_transfer_length = 0; break; + case T10MMC_CMD_MECHANISM_STATUS: + m_phase = SCSI_PHASE_DATAIN; + m_status_code = SCSI_STATUS_CODE_GOOD; + m_transfer_length = get_u16be(&command[8]); + m_device->logerror("T10MMC: MECHANISM STATUS requested %d bytes\n", m_transfer_length); + break; + case T10MMC_CMD_READ_CD: { if (!m_image->exists()) @@ -892,6 +903,32 @@ void t10mmc::ReadData( uint8_t *data, int dataLength ) } break; + case T10MMC_CMD_MECHANISM_STATUS: + { + uint8_t status_header[8]; + size_t transfer_len = get_u16be(&command[8]); + + if (transfer_len == 0) + break; + + std::fill_n(&data[0], transfer_len, 0); + std::fill(std::begin(status_header), std::end(status_header), 0); + + put_u24be(&status_header[2], m_last_lba); + + if (m_cdda->audio_active()) + { + // cdrdao checks this flag after starting an audio track to play for 1 block to + // determine when it can read the sub q channel data when finding tracks and indexes + m_last_lba = m_cdda->get_audio_lba(); + status_header[1] |= 1 << 5; // playing (audio or data) flag + } + + std::copy_n(std::begin(status_header), std::min(std::size(status_header), transfer_len), &data[0]); + + break; + } + case T10MMC_CMD_READ_CD: //m_device->logerror("T10MMC: read CD %x dataLength lba=%x\n", dataLength, m_lba); if ((m_image) && (m_blocks)) @@ -1134,13 +1171,11 @@ void t10mmc::ReadData( uint8_t *data, int dataLength ) return; } - m_device->logerror("T10MMC: READ SUB-CHANNEL Time = %x, SUBQ = %x\n", command[1], command[2]); - bool msf = (command[1] & 0x2) != 0; data[0]= 0x00; - int audio_active = m_cdda->audio_active(); + const int audio_active = m_cdda->audio_active(); if (audio_active) { // if audio is playing, get the latest LBA from the CDROM layer @@ -1168,14 +1203,18 @@ void t10mmc::ReadData( uint8_t *data, int dataLength ) } } + m_device->logerror("T10MMC: READ SUB-CHANNEL Time = %x, SUBQ = %x, LBA = %d)\n", msf, command[2], m_last_lba); + if (command[2] & 0x40) { + int track = m_image->get_track(m_last_lba); + data[2] = 0; - data[3] = 12; // data length + data[3] = 12; // data length data[4] = 0x01; // sub-channel format code - data[5] = 0x10 | (audio_active ? 0 : 4); - data[6] = m_image->get_track(m_last_lba) + 1; // track - data[7] = 0; // index + data[5] = m_image->get_adr_control(track); + data[6] = track + 1; // track + data[7] = m_image->get_track_index(m_last_lba); // index uint32_t frame = m_last_lba; @@ -1203,8 +1242,11 @@ void t10mmc::ReadData( uint8_t *data, int dataLength ) } break; } + default: m_device->logerror("T10MMC: Unknown subchannel type %d requested\n", command[3]); + std::fill_n(data, dataLength, 0); + break; } break; @@ -1275,33 +1317,158 @@ void t10mmc::ReadData( uint8_t *data, int dataLength ) case TOC_FORMAT_SESSIONS: { + auto toc = m_image->get_toc(); + const int tracks = toc_tracks(); + const int last_session = m_image->get_last_session(); + uint32_t first_track_last_session = 0; + + for (int i = 0; i < tracks - 1; i++) + { + if (toc.tracks[i].session == last_session - 1) + first_track_last_session = i; + } + int len = 2 + (8 * 1); int dptr = 0; put_u16be(&data[dptr], len); dptr += 2; data[dptr++] = 1; - data[dptr++] = 1; + data[dptr++] = last_session; data[dptr++] = 0; - data[dptr++] = m_image->get_adr_control(0); - data[dptr++] = 1; + data[dptr++] = m_image->get_adr_control(first_track_last_session); + data[dptr++] = first_track_last_session + 1; // First Track Number In Last Complete Session data[dptr++] = 0; - uint32_t tstart = m_image->get_track_start(0); + uint32_t tstart = m_image->get_track_start(first_track_last_session); if (msf) { tstart = to_msf(tstart); } - put_u32be(&data[dptr], tstart); + put_u32be(&data[dptr], tstart); // Start Address of First Track in Last Session dptr += 4; } break; + case TOC_FORMAT_FULL_TOC: + { + auto toc = m_image->get_toc(); + + const uint32_t tracks = m_image->get_last_track(); + const uint32_t total_sessions = m_image->get_last_session(); + const int total_a0s = total_sessions * 3; // every session has a set of a0/a1/a2 + const int total_b0s = (total_sessions - 1) * 2; // every additional session starts with a b0/c0 set + const int len = 2 + (11 * (tracks + total_a0s + total_b0s)); + + int dptr = 0; + put_u16be(&data[dptr], len); + dptr += 2; + data[dptr++] = 1; + data[dptr++] = total_sessions; + + int cur_track = 0; + for (uint32_t cur_session = 0; cur_session < total_sessions; cur_session++) + { + if (toc.tracks[cur_track].session != cur_session) + continue; + + int last_track_in_session = cur_track; + for (int i = cur_track; i < tracks && toc.tracks[i].session == cur_session; i++) + last_track_in_session = i; + + // point 0xa0, first track number in session + data[dptr++] = toc.tracks[cur_track].session + 1; + data[dptr++] = m_image->get_adr_control(cur_track); + data[dptr++] = 0; + data[dptr++] = 0xa0; + put_u24be(&data[dptr], 0); + dptr += 3; + data[dptr++] = 0; + data[dptr++] = cur_track + 1; // first track number in session + data[dptr++] = 0; + data[dptr++] = 0; + + // point 0xa1, last track number in session + data[dptr++] = toc.tracks[cur_track].session + 1; + data[dptr++] = m_image->get_adr_control(last_track_in_session); + data[dptr++] = 0; + data[dptr++] = 0xa1; + put_u24be(&data[dptr], 0); + dptr += 3; + data[dptr++] = 0; + data[dptr++] = last_track_in_session + 1; // last track number in session + data[dptr++] = 0; + data[dptr++] = 0; + + // point 0xa2, start of lead-out + uint32_t leadout_addr = m_image->get_track_start(last_track_in_session) + toc.tracks[last_track_in_session].frames; + if (toc.tracks[last_track_in_session].pgdatasize != 0) + leadout_addr -= toc.tracks[last_track_in_session].pregap; // pregap is already included in frame count if pgdatasize is set + + data[dptr++] = toc.tracks[cur_track].session + 1; + data[dptr++] = m_image->get_adr_control(last_track_in_session); + data[dptr++] = 0; + data[dptr++] = 0xa2; + put_u24be(&data[dptr], 0); + dptr += 3; + data[dptr++] = 0; + put_u24be(&data[dptr], to_msf(leadout_addr)); // Track start time + dptr += 3; + + while (cur_track < tracks && toc.tracks[cur_track].session == cur_session) + { + data[dptr++] = toc.tracks[cur_track].session + 1; + data[dptr++] = m_image->get_adr_control(cur_track); + data[dptr++] = 0; + data[dptr++] = cur_track + 1; + put_u24be(&data[dptr], 0); + dptr += 3; + data[dptr++] = 0; + put_u24be(&data[dptr], to_msf(m_image->get_track_start(cur_track))); // Track start time + dptr += 3; + + cur_track++; + } + + if (cur_session + 1 < total_sessions) + { + const uint32_t leadout_addr = m_image->get_track_start(cur_track) + toc.tracks[cur_track].frames; + const uint32_t next_session_program_area = m_image->get_track_start(cur_track) - 150; // fixed 2s pregap + + // point 0xb0, info about next session + data[dptr++] = cur_session + 1; + data[dptr++] = (m_image->get_adr_control(cur_track - 1) & 0x0f) | 0x50; + data[dptr++] = 0; + data[dptr++] = 0xb0; + put_u24be(&data[dptr], to_msf(next_session_program_area)); // start time for the next possible session's program area + dptr += 3; + data[dptr++] = (total_sessions - (cur_session + 1)) * 2; // the number of different Mode-5 pointers present from this point, inclusive + put_u24be(&data[dptr], to_msf(leadout_addr)); // the maximum possible start time of the outermost Lead-out + dptr += 3; + + // point 0xc0, start time of first lead-in of the disc + data[dptr++] = cur_session + 1; + data[dptr++] = (m_image->get_adr_control(0) & 0x0f) | 0x50; + data[dptr++] = 0; + data[dptr++] = 0xc0; + put_u24be(&data[dptr], 0); + dptr += 3; + data[dptr++] = 0; + // Hardcode a value of 95:00:00 because that's the most common value I've seen on various multisession CD dumps + put_u24be(&data[dptr], 0x5f0000); // Start time of the first Lead-in area of the disc + dptr += 3; + } + } + + break; + } + default: m_device->logerror("T10MMC: Unhandled READ TOC format %d\n", toc_format()); + std::fill_n(data, dataLength, 0); break; } } diff --git a/src/devices/machine/t10mmc.h b/src/devices/machine/t10mmc.h index 9b84e929c29..852e21db046 100644 --- a/src/devices/machine/t10mmc.h +++ b/src/devices/machine/t10mmc.h @@ -45,6 +45,7 @@ protected: T10MMC_CMD_PLAY_AUDIO_12 = 0xa5, T10MMC_CMD_READ_DISC_STRUCTURE = 0xad, T10MMC_CMD_SET_CD_SPEED = 0xbb, + T10MMC_CMD_MECHANISM_STATUS = 0xbd, T10MMC_CMD_READ_CD = 0xbe }; @@ -70,8 +71,12 @@ protected: enum toc_format_t { - TOC_FORMAT_TRACKS = 0, - TOC_FORMAT_SESSIONS = 1 + TOC_FORMAT_TRACKS = 0, // legacy toc + TOC_FORMAT_SESSIONS = 1, // multi-session information + TOC_FORMAT_FULL_TOC = 2, // full TOC - includes sessions, lead-ins, lead-outs + TOC_FORMAT_PMA = 3, // PMA + TOC_FORMAT_ATIP = 4, // ATIP + TOC_FORMAT_CDTEXT = 5, // valid only for CD audio discs }; void abort_audio(); diff --git a/src/lib/util/cdrom.cpp b/src/lib/util/cdrom.cpp index 159a9c964a1..d9074bfd838 100644 --- a/src/lib/util/cdrom.cpp +++ b/src/lib/util/cdrom.cpp @@ -21,6 +21,7 @@ #include "corestr.h" #include "multibyte.h" #include "osdfile.h" +#include "path.h" #include "strformat.h" #include @@ -180,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; @@ -191,8 +195,13 @@ 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 pad %d\n", i+1, + printf("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, @@ -290,7 +299,9 @@ 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 pad %d\n", i+1, + printf("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, @@ -583,6 +594,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 @@ -868,70 +900,57 @@ std::error_condition cdrom_file::parse_metadata(chd_file *chd, toc &toc) /* 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) + 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]; - } + /* legacy GDROM track was detected */ + toc.flags |= CD_FLAG_GDROMLE; 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; } + 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; @@ -986,8 +1005,11 @@ std::error_condition cdrom_file::parse_metadata(chd_file *chd, toc &toc) auto *mrp = reinterpret_cast(&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++; @@ -1045,7 +1067,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]; @@ -1065,15 +1096,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; } @@ -1833,6 +1855,8 @@ 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); @@ -1895,8 +1919,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) { @@ -2016,11 +2039,11 @@ 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 ) { outtoc.tracks[0].trktype = CD_TRACK_MODE1; @@ -2189,6 +2212,7 @@ 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(); } @@ -2215,13 +2239,15 @@ std::error_condition cdrom_file::parse_gdi(std::string_view tocfname, toc &outto std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo) { - int i, trknum; + 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) @@ -2237,6 +2263,8 @@ 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) { @@ -2261,13 +2289,65 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto if (!strcmp(token, "REM")) { - /* TODO: sessions are notated using REM commands: "REM SESSION 01" */ - /* skip to actual data of REM command */ while (i < std::size(linebuffer) && isspace((uint8_t)linebuffer[i])) i++; - if (is_gdrom && !strncmp(linebuffer+i, "SINGLE-DENSITY AREA", 19)) + if (!strncmp(linebuffer+i, "SESSION", 7)) + { + /* IsoBuster extension */ + TOKENIZE + + /* get the session number */ + TOKENIZE + + 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 ((outtoc.flags & CD_FLAG_MULTISESSION) && !strncmp(linebuffer+i, "PREGAP", 6)) + { + /* + Redump extension? PREGAP associated with the session instead of the track + + 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 + + /* get pregap time */ + TOKENIZE + session_pregap = msf_to_frames( token ); + } + else if (!strncmp(linebuffer+i, "LEAD-OUT", 8)) + { + /* + 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 + + Redump and DiscImageCreator (multiple bins) - Lead-out time is the duration of just the lead-out + */ + TOKENIZE + + /* get lead-out time */ + TOKENIZE + int leadout_offset = msf_to_frames( token ); + outinfo.track[trknum].leadout = leadout_offset; + } + else if (!strncmp(linebuffer+i, "LEAD-IN", 7)) + { + /* + 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 + + /* get lead-in time */ + TOKENIZE + leadin = msf_to_frames( token ); + } + else if (is_gdrom && !strncmp(linebuffer+i, "SINGLE-DENSITY AREA", 19)) { /* single-density area starts LBA = 0 */ current_area = SINGLE_DENSITY; @@ -2284,7 +2364,10 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto TOKENIZE /* keep the filename */ + std::string prevfname = lastfname; lastfname.assign(path).append(token); + if (prevfname.length() > 0 && lastfname.compare(prevfname) != 0) + is_multibin = true; /* get the file type */ TOKENIZE @@ -2323,6 +2406,7 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto /* next token on the line is the track type */ TOKENIZE + outtoc.tracks[trknum].session = sessionnum; outtoc.tracks[trknum].subtype = CD_SUB_NONE; outtoc.tracks[trknum].subsize = 0; outtoc.tracks[trknum].pgsub = CD_SUB_NONE; @@ -2330,9 +2414,26 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto outtoc.tracks[trknum].padframes = 0; outtoc.tracks[trknum].datasize = 0; outtoc.tracks[trknum].multicuearea = is_gdrom ? current_area : 0; - outinfo.track[trknum].idx0offs = -1; - outinfo.track[trknum].idx1offs = 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) + { + /* + 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; + } if (wavlen != 0) { @@ -2341,7 +2442,7 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto wavoffs = wavlen = 0; } - outinfo.track[trknum].fname.assign(lastfname); // default filename to the last one + outinfo.track[trknum].fname.assign(lastfname); /* default filename to the last one */ if (EXTRA_VERBOSE) { @@ -2368,7 +2469,7 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto convert_subtype_string_to_track_info(token, &outtoc.tracks[trknum]); } - else if (!strcmp(token, "INDEX")) /* only in bin/cue files */ + else if (!strcmp(token, "INDEX")) { int idx, frames; @@ -2380,22 +2481,25 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto TOKENIZE frames = msf_to_frames( token ); - if (idx == 0) + if (idx < 0 || idx > MAX_INDEX) { - outinfo.track[trknum].idx0offs = frames; + printf("ERROR: encountered invalid index %d\n", idx); + return chd_file::error::INVALID_DATA; } - else if (idx == 1) + + outinfo.track[trknum].idx[idx] = frames; + + if (idx == 1) { - outinfo.track[trknum].idx1offs = frames; - if ((outtoc.tracks[trknum].pregap == 0) && (outinfo.track[trknum].idx0offs != -1)) + if (outtoc.tracks[trknum].pregap == 0 && outinfo.track[trknum].idx[0] != -1) { - outtoc.tracks[trknum].pregap = frames - outinfo.track[trknum].idx0offs; + 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 // pregap sectors not in file, but we're always using idx0ofs for track length calc now + 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].idx0offs = frames; + outinfo.track[trknum].idx[0] = frames; } } } @@ -2419,6 +2523,28 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto 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 last_idx = i; + + TOKENIZE + + 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; + } + } } } @@ -2427,82 +2553,126 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto /* 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 */ + printf("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)) { - // is this the last track? - if (trknum == (outtoc.numtrks-1)) + /* if the last track's filename is the same as the previous track */ + tlen = get_file_size(outinfo.track[trknum].fname); + if (tlen == 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)) + 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 if (trknum+1 < outtoc.numtrks && outinfo.track[trknum].fname.compare(outinfo.track[trknum+1].fname)==0) + { + /* 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) + { + printf("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) + { + printf("ERROR: couldn't find bin file [%s]\n", outinfo.track[trknum-1].fname.c_str()); + 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; } } } @@ -2523,7 +2693,7 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto outinfo.track[trknum].offset += this_offset; outtoc.tracks[trknum].frames -= this_pregap; - outinfo.track[trknum].idx1offs -= this_pregap; + outinfo.track[trknum].idx[1] -= this_pregap; outtoc.tracks[trknum].pregap = 0; outtoc.tracks[trknum].pgtype = 0; @@ -2551,7 +2721,8 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto 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, pgdatasize=%d, idx0=%d, idx1=%d, dataframes=%d\n", + printf("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, @@ -2562,8 +2733,8 @@ std::error_condition cdrom_file::parse_cue(std::string_view tocfname, toc &outto outtoc.tracks[trknum].pregap, outtoc.tracks[trknum].pgtype, outtoc.tracks[trknum].pgdatasize, - outinfo.track[trknum].idx0offs, - outinfo.track[trknum].idx1offs, + outinfo.track[trknum].idx[0], + outinfo.track[trknum].idx[1], outtoc.tracks[trknum].frames - outtoc.tracks[trknum].padframes); } @@ -2832,6 +3003,7 @@ std::error_condition cdrom_file::parse_toc(std::string_view tocfname, toc &outto /* store the number of tracks found */ outtoc.numtrks = trknum + 1; + outtoc.numsessions = 1; return std::error_condition(); } diff --git a/src/lib/util/cdrom.h b/src/lib/util/cdrom.h index 9bf80190a7c..dd62badeff1 100644 --- a/src/lib/util/cdrom.h +++ b/src/lib/util/cdrom.h @@ -24,6 +24,7 @@ public: static constexpr uint32_t MAX_TRACKS = 99; /* AFAIK the theoretical limit */ static constexpr uint32_t MAX_SECTOR_DATA = 2352; static constexpr uint32_t MAX_SUBCODE_DATA = 96; + static constexpr uint32_t MAX_INDEX = 99; static constexpr uint32_t FRAME_SIZE = MAX_SECTOR_DATA + MAX_SUBCODE_DATA; static constexpr uint32_t FRAMES_PER_HUNK = 8; @@ -53,25 +54,43 @@ public: enum { - CD_FLAG_GDROM = 0x00000001, // disc is a GD-ROM, all tracks should be stored with GD-ROM metadata - CD_FLAG_GDROMLE = 0x00000002 // legacy GD-ROM, with little-endian CDDA data + CD_FLAG_GDROM = 0x00000001, // disc is a GD-ROM, all tracks should be stored with GD-ROM metadata + CD_FLAG_GDROMLE = 0x00000002, // legacy GD-ROM, with little-endian CDDA data + CD_FLAG_MULTISESSION = 0x00000004, // multisession CD-ROM + }; + + enum + { + CD_FLAG_CONTROL_PREEMPHASIS = 1, + CD_FLAG_CONTROL_DIGITAL_COPY_PERMITTED = 2, + CD_FLAG_CONTROL_DATA_TRACK = 4, + CD_FLAG_CONTROL_4CH = 8, + }; + + enum + { + CD_FLAG_ADR_START_TIME = 1, + CD_FLAG_ADR_CATALOG_CODE, + CD_FLAG_ADR_ISRC_CODE, }; struct track_info { /* fields used by CHDMAN and in MAME */ - uint32_t trktype; /* track type */ - uint32_t subtype; /* subcode data type */ - uint32_t datasize; /* size of data in each sector of this track */ - uint32_t subsize; /* size of subchannel data in each sector of this track */ - uint32_t frames; /* number of frames in this track */ - uint32_t extraframes; /* number of "spillage" frames in this track */ - uint32_t pregap; /* number of pregap frames */ - uint32_t postgap; /* number of postgap frames */ - uint32_t pgtype; /* type of sectors in pregap */ - uint32_t pgsub; /* type of subchannel data in pregap */ - uint32_t pgdatasize; /* size of data in each sector of the pregap */ - uint32_t pgsubsize; /* size of subchannel data in each sector of the pregap */ + uint32_t trktype; /* track type */ + uint32_t subtype; /* subcode data type */ + uint32_t datasize; /* size of data in each sector of this track */ + uint32_t subsize; /* size of subchannel data in each sector of this track */ + uint32_t frames; /* number of frames in this track */ + uint32_t extraframes; /* number of "spillage" frames in this track */ + uint32_t pregap; /* number of pregap frames */ + uint32_t postgap; /* number of postgap frames */ + uint32_t pgtype; /* type of sectors in pregap */ + uint32_t pgsub; /* type of subchannel data in pregap */ + uint32_t pgdatasize; /* size of data in each sector of the pregap */ + uint32_t pgsubsize; /* size of subchannel data in each sector of the pregap */ + uint32_t control_flags; /* metadata flags associated with each track */ + uint32_t session; /* session number */ /* fields used in CHDMAN only */ uint32_t padframes; /* number of frames of padding to add to the end of the track; needed for GDI */ @@ -91,6 +110,7 @@ public: struct toc { uint32_t numtrks; /* number of tracks */ + uint32_t numsessions; /* number of sessions */ uint32_t flags; /* see FLAG_ above */ track_info tracks[MAX_TRACKS + 1]; }; @@ -98,13 +118,13 @@ public: struct track_input_entry { track_input_entry() { reset(); } - void reset() { fname.clear(); offset = idx0offs = idx1offs = 0; swap = false; } + void reset() { fname.clear(); offset = 0; leadin = leadout = -1; swap = false; std::fill(std::begin(idx), std::end(idx), -1); } std::string fname; // filename for each track uint32_t offset; // offset in the data file for each track bool swap; // data needs to be byte swapped - uint32_t idx0offs; - uint32_t idx1offs; + int32_t idx[MAX_INDEX + 1]; + int32_t leadin, leadout; // TODO: these should probably be their own tracks entirely }; struct track_input_info @@ -128,6 +148,7 @@ public: uint32_t get_track(uint32_t frame) const; uint32_t get_track_start(uint32_t track) const {return cdtoc.tracks[track == 0xaa ? cdtoc.numtrks : track].logframeofs; } uint32_t get_track_start_phys(uint32_t track) const { return cdtoc.tracks[track == 0xaa ? cdtoc.numtrks : track].physframeofs; } + uint32_t get_track_index(uint32_t frame) const; /* TOC utilities */ static std::error_condition parse_nero(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); @@ -136,8 +157,16 @@ public: static std::error_condition parse_cue(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); static bool is_gdicue(std::string_view tocfname); static std::error_condition parse_toc(std::string_view tocfname, toc &outtoc, track_input_info &outinfo); + int get_last_session() const { return cdtoc.numsessions ? cdtoc.numsessions : 1; } int get_last_track() const { return cdtoc.numtrks; } - int get_adr_control(int track) const { return track == 0xaa || cdtoc.tracks[track].trktype == CD_TRACK_AUDIO ? 0x10 : 0x14; } + int get_adr_control(int track) const { + if (track == 0xaa) + track = get_last_track() - 1; // use last track's flags + int adrctl = (CD_FLAG_ADR_START_TIME << 4) | (cdtoc.tracks[track].control_flags & 0x0f); + if (cdtoc.tracks[track].trktype != CD_TRACK_AUDIO) + adrctl |= CD_FLAG_CONTROL_DATA_TRACK; + return adrctl; + } int get_track_type(int track) const { return cdtoc.tracks[track].trktype; } const toc &get_toc() const { return cdtoc; } -- cgit v1.2.3