Răsfoiți Sursa

Bug fixes and improvements to CD-ROM emulation with cue sheets

Petteri Aimonen 2 ani în urmă
părinte
comite
d382dabcff
5 a modificat fișierele cu 587 adăugiri și 51 ștergeri
  1. 5 0
      src/BlueSCSI.cpp
  2. 558 45
      src/BlueSCSI_cdrom.cpp
  3. 9 1
      src/BlueSCSI_cdrom.h
  4. 5 5
      src/BlueSCSI_disk.cpp
  5. 10 0
      src/BlueSCSI_log_trace.cpp

+ 5 - 0
src/BlueSCSI.cpp

@@ -317,6 +317,11 @@ bool findHDDImages()
           continue;
         }
 
+        if (strcasecmp(extension, ".cue") == 0)
+        {
+          continue; // .cue will be handled with corresponding .bin
+        }
+
         // Check if the image should be loaded to microcontroller flash ROM drive
         bool is_romdrive = false;
         if (extension && strcasecmp(extension, ".rom") == 0)

+ 558 - 45
src/BlueSCSI_cdrom.cpp

@@ -33,6 +33,7 @@
 #include "BlueSCSI_config.h"
 #include "BlueSCSI_cdrom.h"
 #include <CUEParser.h>
+#include <assert.h>
 
 extern "C" {
 #include <scsi.h>
@@ -169,18 +170,65 @@ static const uint8_t FullTOC[] =
 	0x00  // 69: PFRAME
 };
 
+static uint8_t SimpleHeader[] =
+{
+	0x01, // 2048byte user data, L-EC in 288 byte aux field.
+	0x00, // reserved
+	0x00, // reserved
+	0x00, // reserved
+	0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
+};
+
+static const uint8_t DiscInformation[] =
+{
+    0x00,   //  0: disc info length, MSB
+    0x20,   //  1: disc info length, LSB
+    0x0E,   //  2: disc status (finalized, single session non-rewritable)
+    0x01,   //  3: first track number
+    0x01,   //  4: number of sessions (LSB)
+    0x01,   //  5: first track in last session (LSB)
+    0x01,   //  6: last track in last session (LSB)
+    0x00,   //  7: format status (0x00 = non-rewritable, no barcode, no disc id)
+    0x00,   //  8: disc type (0x00 = CD-ROM)
+    0x00,   //  9: number of sessions (MSB)
+    0x00,   // 10: first track in last session (MSB)
+    0x00,   // 11: last track in last session (MSB)
+    0x00,   // 12: disc ID (MSB)
+    0x00,   // 13: .
+    0x00,   // 14: .
+    0x00,   // 15: disc ID (LSB)
+    0x00,   // 16: last session lead-in start (MSB)
+    0x00,   // 17: .
+    0x00,   // 18: .
+    0x00,   // 19: last session lead-in start (LSB)
+    0x00,   // 20: last possible lead-out start (MSB)
+    0x00,   // 21: .
+    0x00,   // 22: .
+    0x00,   // 23: last possible lead-out start (LSB)
+    0x00,   // 24: disc bar code (MSB)
+    0x00,   // 25: .
+    0x00,   // 26: .
+    0x00,   // 27: .
+    0x00,   // 28: .
+    0x00,   // 29: .
+    0x00,   // 30: .
+    0x00,   // 31: disc bar code (LSB)
+    0x00,   // 32: disc application code
+    0x00,   // 33: number of opc tables
+};
+
+// Convert logical block address to CD-ROM time in formatted TOC format
 static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
 {
 	MSF[0] = 0; // reserved.
-	MSF[3] = LBA % 75; // M
+	MSF[3] = LBA % 75; // Frames
 	uint32_t rem = LBA / 75;
 
-	MSF[2] = rem % 60; // S
-	MSF[1] = rem / 60;
-
+	MSF[2] = rem % 60; // Seconds
+	MSF[1] = rem / 60; // Minutes
 }
 
-static void doReadTOCSimple(int MSF, uint8_t track, uint16_t allocationLength)
+static void doReadTOCSimple(bool MSF, uint8_t track, uint16_t allocationLength)
 {
 	if (track == 0xAA)
 	{
@@ -313,16 +361,7 @@ static void doReadFullTOCSimple(int convertBCD, uint8_t session, uint16_t alloca
 	}
 }
 
-static uint8_t SimpleHeader[] =
-{
-	0x01, // 2048byte user data, L-EC in 288 byte aux field.
-	0x00, // reserved
-	0x00, // reserved
-	0x00, // reserved
-	0x00,0x00,0x00,0x00 // Track start sector (LBA or MSF)
-};
-
-void doReadHeaderSimple(int MSF, uint32_t lba, uint16_t allocationLength)
+void doReadHeaderSimple(bool MSF, uint32_t lba, uint16_t allocationLength)
 {
 	uint32_t len = sizeof(SimpleHeader);
 	memcpy(scsiDev.data, SimpleHeader, len);
@@ -334,10 +373,45 @@ void doReadHeaderSimple(int MSF, uint32_t lba, uint16_t allocationLength)
 	scsiDev.phase = DATA_IN;
 }
 
+void doReadDiscInformationSimple(uint16_t allocationLength)
+{
+    uint32_t len = sizeof(DiscInformation);
+	memcpy(scsiDev.data, DiscInformation, len);
+	if (len > allocationLength)
+	{
+		len = allocationLength;
+	}
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
 /*********************************/
 /* TOC generation from cue sheet */
 /*********************************/
 
+// Fetch track info based on LBA
+static void getTrackFromLBA(CUEParser &parser, uint32_t lba, CUETrackInfo *result)
+{
+    // Track info in case we have no .cue file
+    result->file_mode = CUEFile_BINARY;
+    result->track_mode = CUETrack_MODE1_2048;
+    result->sector_length = 2048;
+    result->track_number = 1;
+
+    const CUETrackInfo *tmptrack;
+    while ((tmptrack = parser.next_track()) != NULL)
+    {
+        if (tmptrack->track_start <= lba)
+        {
+            *result = *tmptrack;
+        }
+        else
+        {
+            break;
+        }
+    }
+}
+
 // Format track info read from cue sheet into the format used by ReadTOC command.
 // Refer to T10/1545-D MMC-4 Revision 5a, "Response Format 0000b: Formatted TOC"
 static void formatTrackInfo(const CUETrackInfo *track, uint8_t *dest, bool use_MSF_time)
@@ -380,9 +454,10 @@ static bool loadCueSheet(image_config_t &img, CUEParser &parser)
     }
 
     // Use second half of scsiDev.data as the buffer for cue sheet text
-    char *cuebuf = (char*)&scsiDev.data[sizeof(scsiDev.data) / 2];
+    size_t halfbufsize = sizeof(scsiDev.data) / 2;
+    char *cuebuf = (char*)&scsiDev.data[halfbufsize];
     img.cuesheetfile.seek(0);
-    int len = img.cuesheetfile.read(cuebuf, sizeof(cuebuf));
+    int len = img.cuesheetfile.read(cuebuf, halfbufsize);
 
     if (len <= 0)
     {
@@ -394,7 +469,7 @@ static bool loadCueSheet(image_config_t &img, CUEParser &parser)
     return true;
 }
 
-static void doReadTOC(int MSF, uint8_t track, uint16_t allocationLength)
+static void doReadTOC(bool MSF, uint8_t track, uint16_t allocationLength)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     CUEParser parser;
@@ -491,13 +566,27 @@ static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
 	scsiDev.phase = DATA_IN;
 }
 
+// Convert logical block address to CD-ROM time in the raw TOC format
 static void LBA2MSFRaw(uint32_t LBA, uint8_t* MSF)
 {
-    MSF[2] = LBA % 75; // M
+    MSF[2] = LBA % 75; // Frames
+	uint32_t rem = LBA / 75;
+
+	MSF[1] = rem % 60; // Seconds
+	MSF[0] = rem / 60; // Minutes
+}
+
+// Convert logical block address to CD-ROM time in binary coded decimal format
+static void LBA2MSFBCD(uint32_t LBA, uint8_t* MSF)
+{
+    uint8_t fra = LBA % 75;
 	uint32_t rem = LBA / 75;
+	uint8_t sec = rem % 60;
+	uint8_t min = rem / 60;
 
-	MSF[1] = rem % 60; // S
-	MSF[0] = rem / 60;
+    MSF[0] = ((min / 10) << 4) | (min % 10);
+    MSF[1] = ((sec / 10) << 4) | (sec % 10);
+    MSF[2] = ((fra / 10) << 4) | (fra % 10);
 }
 
 // Format track info read from cue sheet into the format used by ReadFullTOC command.
@@ -527,7 +616,7 @@ static void formatRawTrackInfo(const CUETrackInfo *track, uint8_t *dest)
 
     dest[7] = 0; // HOUR
 
-    LBA2MSFRaw(track->data_start, &dest[8]);
+    LBA2MSFBCD(track->data_start, &dest[8]);
 }
 
 static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
@@ -574,7 +663,7 @@ static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLe
     scsiDev.data[23] = lasttrack;
 
     // Leadout track position
-    LBA2MSFRaw(img.scsiSectors, &scsiDev.data[34]);
+    LBA2MSFBCD(img.scsiSectors, &scsiDev.data[34]);
 
     // Append recordable disc records b0 and c0 indicating non-recordable disc
     memcpy(scsiDev.data + len, &FullTOC[48], 22);
@@ -595,7 +684,7 @@ static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLe
 
 // SCSI-3 MMC Read Header command, seems to be deprecated in later standards.
 // Refer to ANSI X3.304-1997
-void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
+void doReadHeader(bool MSF, uint32_t lba, uint16_t allocationLength)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     CUEParser parser;
@@ -610,20 +699,11 @@ void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
 	memcpy(scsiDev.data, SimpleHeader, len);
 
     // Search the track with the requested LBA
-    const CUETrackInfo *trackinfo;
-    CUETrackMode trackmode = CUETrack_MODE1_2048;
-    uint32_t trackstart = 0;
-    while ((trackinfo = parser.next_track()) != NULL)
-    {
-        if (trackinfo->data_start < lba)
-        {
-            trackstart = trackinfo->data_start;
-            trackmode = trackinfo->track_mode;
-        }
-    }
+    CUETrackInfo trackinfo = {};
+    getTrackFromLBA(parser, lba, &trackinfo);
 
     // Track mode (audio / data)
-    if (trackmode == CUETrack_AUDIO)
+    if (trackinfo.track_mode == CUETrack_AUDIO)
     {
         scsiDev.data[0] = 0;
     }
@@ -631,14 +711,14 @@ void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
     // Track start
     if (MSF)
     {
-        LBA2MSF(trackstart, &scsiDev.data[4]);
+        LBA2MSF(trackinfo.data_start, &scsiDev.data[4]);
     }
     else
     {
-        scsiDev.data[4] = (trackstart >> 24) & 0xFF;
-        scsiDev.data[5] = (trackstart >> 16) & 0xFF;
-        scsiDev.data[6] = (trackstart >>  8) & 0xFF;
-        scsiDev.data[7] = (trackstart >>  0) & 0xFF;
+        scsiDev.data[4] = (trackinfo.data_start >> 24) & 0xFF;
+        scsiDev.data[5] = (trackinfo.data_start >> 16) & 0xFF;
+        scsiDev.data[6] = (trackinfo.data_start >>  8) & 0xFF;
+        scsiDev.data[7] = (trackinfo.data_start >>  0) & 0xFF;
     }
 
     if (len > allocationLength)
@@ -649,6 +729,42 @@ void doReadHeader(int MSF, uint32_t lba, uint16_t allocationLength)
 	scsiDev.phase = DATA_IN;
 }
 
+void doReadDiscInformation(uint16_t allocationLength)
+{
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    CUEParser parser;
+    if (!loadCueSheet(img, parser))
+    {
+        // No CUE sheet, use hardcoded data
+        return doReadDiscInformationSimple(allocationLength);
+    }
+
+    // Take the hardcoded header as base
+    uint32_t len = sizeof(DiscInformation);
+	memcpy(scsiDev.data, DiscInformation, len);
+
+    // Find first and last track number
+    int firsttrack = -1;
+    int lasttrack = -1;
+    const CUETrackInfo *trackinfo;
+    while ((trackinfo = parser.next_track()) != NULL)
+    {
+        if (firsttrack < 0) firsttrack = trackinfo->track_number;
+        lasttrack = trackinfo->track_number;
+    }
+
+    scsiDev.data[3] = firsttrack;
+    scsiDev.data[5] = firsttrack;
+    scsiDev.data[6] = lasttrack;
+
+	if (len > allocationLength)
+	{
+		len = allocationLength;
+	}
+	scsiDev.dataLen = len;
+	scsiDev.phase = DATA_IN;
+}
+
 /****************************************/
 /* CUE sheet check at image load time   */
 /****************************************/
@@ -668,7 +784,8 @@ bool cdromValidateCueSheet(image_config_t &img)
         trackcount++;
 
         if (trackinfo->track_mode != CUETrack_AUDIO &&
-            trackinfo->track_mode != CUETrack_MODE1_2048)
+            trackinfo->track_mode != CUETrack_MODE1_2048 &&
+            trackinfo->track_mode != CUETrack_MODE1_2352)
         {
             log("---- Warning: track ", trackinfo->track_number, " has unsupported mode ", (int)trackinfo->track_mode);
         }
@@ -790,10 +907,23 @@ static void doGetEventStatusNotification(bool immed)
 /* CD-ROM audio playback              */
 /**************************************/
 
+// TODO: where to store these? Should be updated as playback progresses.
+static CDROMAudioPlaybackStatus g_cdrom_audio_status;
+static uint32_t g_cdrom_audio_lba;
+
+void cdromGetAudioPlaybackStatus(CDROMAudioPlaybackStatus *status, uint32_t *current_lba)
+{
+    if (status) *status = g_cdrom_audio_status;
+    if (current_lba) *current_lba = g_cdrom_audio_lba;
+}
+
 static void doPlayAudio(uint32_t lba, uint32_t length)
 {
     log("------ CD-ROM Play Audio starting at ", lba, " for ", length, " sectors");
 
+    g_cdrom_audio_lba = lba;
+    g_cdrom_audio_status = CDROMAudio_Stopped; // Playback not actually implemented
+
     scsiDev.status = 0;
     scsiDev.phase = STATUS;
 }
@@ -806,6 +936,300 @@ static void doPauseResumeAudio(bool resume)
     scsiDev.phase = STATUS;
 }
 
+static void doMechanismStatus(uint16_t allocation_length)
+{
+    uint8_t *buf = scsiDev.data;
+
+    CDROMAudioPlaybackStatus status;
+    uint32_t lba;
+    cdromGetAudioPlaybackStatus(&status, &lba);
+
+    *buf++ = 0x00; // No fault state
+    *buf++ = (status == CDROMAudio_Playing) ? 0x20 : 0x00; // Currently playing?
+    *buf++ = (lba >> 16) & 0xFF;
+    *buf++ = (lba >> 8) & 0xFF;
+    *buf++ = (lba >> 0) & 0xFF;
+    *buf++ = 0; // No CD changer
+    *buf++ = 0;
+    *buf++ = 0;
+
+    int len = 8;
+    if (len > allocation_length) len = allocation_length;
+
+    scsiDev.dataLen = len;
+    scsiDev.phase = DATA_IN;
+}
+
+/*******************************************/
+/* CD-ROM data reading in low level format */
+/*******************************************/
+
+static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
+                     uint8_t main_channel, uint8_t sub_channel)
+{
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+    CUEParser parser;
+    if (!loadCueSheet(img, parser)
+        && (sector_type == 0 || sector_type == 2)
+        && main_channel == 0x10 && sub_channel == 0)
+    {
+        // Simple case, return sector data directly
+        scsiDiskStartRead(lba, length);
+        return;
+    }
+
+    // Search the track with the requested LBA
+    // Supplies dummy data if no cue sheet is active.
+    CUETrackInfo trackinfo = {};
+    getTrackFromLBA(parser, lba, &trackinfo);
+
+    // Figure out the data offset in the file
+    uint64_t offset = trackinfo.file_offset + trackinfo.sector_length * (lba - trackinfo.data_start);
+    debuglog("------ Read CD: ", (int)length, " sectors starting at ", (int)lba,
+           ", track number ", trackinfo.track_number, ", sector size ", (int)trackinfo.sector_length,
+           ", main channel ", main_channel, ", sub channel ", sub_channel,
+           ", data offset in file ", (int)offset);
+
+    // Verify sector type
+    if (sector_type != 0)
+    {
+        bool sector_type_ok = false;
+        if (sector_type == 1 && trackinfo.track_mode == CUETrack_AUDIO)
+        {
+            sector_type_ok = true;
+        }
+        else if (sector_type == 2 && trackinfo.track_mode == CUETrack_MODE1_2048)
+        {
+            sector_type_ok = true;
+        }
+
+        if (!sector_type_ok)
+        {
+            debuglog("---- Failed sector type check, host requested ", (int)sector_type, " CUE file has ", (int)trackinfo.track_mode);
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            scsiDev.target->sense.asc = 0x6400; // ILLEGAL MODE FOR THIS TRACK
+            scsiDev.phase = STATUS;
+            return;
+        }
+    }
+
+    // Select fields to transfer
+    // Refer to table 351 in T10/1545-D MMC-4 Revision 5a
+    // Only the mandatory cases are supported.
+    int sector_length = 0;
+    int skip_begin = 0;
+    bool add_fake_headers = false;
+
+    if (main_channel == 0)
+    {
+        // No actual data requested, just sector type check or subchannel
+        sector_length = 0;
+    }
+    else if (trackinfo.track_mode == CUETrack_AUDIO)
+    {
+        // Transfer whole 2352 byte audio sectors from file to host
+        sector_length = 2352;
+    }
+    else if (trackinfo.track_mode == CUETrack_MODE1_2048 && main_channel == 0x10)
+    {
+        // Transfer whole 2048 byte data sectors from file to host
+        sector_length = 2048;
+    }
+    else if (trackinfo.track_mode == CUETrack_MODE1_2048 && (main_channel & 0xB8) == 0xB8)
+    {
+        // Transfer 2048 bytes of data from file and fake the headers
+        sector_length = 2048;
+        add_fake_headers = true;
+        debuglog("------ Host requested ECC data but image file lacks it, replacing with zeros");
+    }
+    else if (trackinfo.track_mode == CUETrack_MODE1_2352 && main_channel == 0x10)
+    {
+        // Transfer the 2048 byte payload of data sector to host.
+        sector_length = 2048;
+        skip_begin = 16;
+    }
+    else if (trackinfo.track_mode == CUETrack_MODE1_2352 && (main_channel & 0xB8) == 0xB8)
+    {
+        // Transfer whole 2352 byte data sector with ECC to host
+        sector_length = 2352;
+    }
+    else
+    {
+        debuglog("---- Unsupported channel request for track type ", (int)trackinfo.track_mode);
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = 0x6400; // ILLEGAL MODE FOR THIS TRACK
+        scsiDev.phase = STATUS;
+        return;
+    }
+
+    bool field_q_subchannel = false;
+    if (sub_channel == 2)
+    {
+        // Include position information in Q subchannel
+        field_q_subchannel = true;
+    }
+    else if (sub_channel != 0)
+    {
+        debuglog("---- Unsupported subchannel request");
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+        scsiDev.phase = STATUS;
+        return;
+    }
+
+    scsiDev.phase = DATA_IN;
+    scsiDev.dataLen = 0;
+    scsiDev.dataPtr = 0;
+    scsiEnterPhase(DATA_IN);
+
+    // Use two buffers alternately for formatting sector data
+    uint32_t result_length = sector_length + (field_q_subchannel ? 16 : 0) + (add_fake_headers ? 304 : 0);
+    uint8_t *buf0 = scsiDev.data;
+    uint8_t *buf1 = scsiDev.data + result_length;
+
+    // Format the sectors for transfer
+    for (uint32_t idx = 0; idx < length; idx++)
+    {
+        platform_poll();
+
+        img.file.seek(offset + idx * trackinfo.sector_length + skip_begin);
+
+        // Verify that previous write using this buffer has finished
+        uint8_t *buf = ((idx & 1) ? buf1 : buf0);
+        uint8_t *bufstart = buf;
+        uint32_t start = millis();
+        while (!scsiIsWriteFinished(buf + result_length - 1) && !scsiDev.resetFlag)
+        {
+            if ((uint32_t)(millis() - start) > 5000)
+            {
+                log("doReadCD() timeout waiting for previous to finish");
+                scsiDev.resetFlag = 1;
+            }
+            platform_poll();
+        }
+        if (scsiDev.resetFlag) break;
+
+        if (add_fake_headers)
+        {
+            // 12-byte data sector sync pattern
+            *buf++ = 0x00;
+            for (int i = 0; i < 10; i++)
+            {
+                *buf++ = 0xFF;
+            }
+            *buf++ = 0x00;
+
+            // 4-byte data sector header
+            LBA2MSFBCD(lba + idx, buf);
+            buf += 3;
+            *buf++ = 0x01; // Mode 1
+        }
+
+        if (sector_length > 0)
+        {
+            // User data
+            img.file.read(buf, sector_length);
+            buf += sector_length;
+        }
+
+        if (add_fake_headers)
+        {
+            // 288 bytes of ECC
+            memset(buf, 0, 288);
+            buf += 288;
+        }
+
+        if (field_q_subchannel)
+        {
+            // Formatted Q subchannel data
+            // Refer to table 354 in T10/1545-D MMC-4 Revision 5a
+            *buf++ = (trackinfo.track_mode == CUETrack_AUDIO ? 0x10 : 0x14); // Control & ADR
+            *buf++ = trackinfo.track_number;
+            *buf++ = (lba + idx >= trackinfo.data_start) ? 1 : 0; // Index number (0 = pregap)
+            LBA2MSFRaw(lba + idx, buf); buf += 3;
+            *buf++ = 0;
+            LBA2MSFRaw(lba + idx, buf); buf += 3;
+            *buf++ = 0; *buf++ = 0; // CRC (optional)
+            *buf++ = 0; *buf++ = 0; *buf++ = 0; // (pad)
+            *buf++ = 0; // No P subchannel
+        }
+
+        assert(buf == bufstart + result_length);
+        scsiStartWrite(bufstart, result_length);
+    }
+
+    scsiFinishWrite();
+
+    scsiDev.status = 0;
+    scsiDev.phase = STATUS;
+}
+
+static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t track_number, uint16_t allocation_length)
+{
+    uint8_t *buf = scsiDev.data;
+
+    if (parameter == 0x01)
+    {
+        CDROMAudioPlaybackStatus audiostatus;
+        uint32_t lba;
+        cdromGetAudioPlaybackStatus(&audiostatus, &lba);
+        debuglog("------ Get audio playback position: status ", (int)audiostatus, " lba ", (int)lba);
+
+        // Fetch current track info
+        image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+        CUEParser parser;
+        CUETrackInfo trackinfo = {};
+        loadCueSheet(img, parser);
+        getTrackFromLBA(parser, lba, &trackinfo);
+
+        // Request sub channel data at current playback position
+        *buf++ = 0; // Reserved
+
+        switch (audiostatus)
+        {
+            case CDROMAudio_Playing: *buf++ = 0x11; break;
+            case CDROMAudio_Paused:  *buf++ = 0x12; break;
+            case CDROMAudio_Stopped: *buf++ = 0x13; break;
+            default: *buf++ = 0; break;
+        }
+
+        int len = 12;
+        *buf++ = 0;  // Subchannel data length (MSB)
+        *buf++ = len; // Subchannel data length (LSB)
+        *buf++ = 0x01; // Subchannel data format
+        *buf++ = (trackinfo.track_mode == CUETrack_AUDIO ? 0x10 : 0x14);
+        *buf++ = trackinfo.track_number;
+        *buf++ = (lba >= trackinfo.data_start) ? 1 : 0; // Index number (0 = pregap)
+        *buf++ = (lba >> 24) & 0xFF; // Absolute block address
+        *buf++ = (lba >> 16) & 0xFF;
+        *buf++ = (lba >>  8) & 0xFF;
+        *buf++ = (lba >>  0) & 0xFF;
+
+        uint32_t relpos = (uint32_t)((int32_t)lba - (int32_t)trackinfo.data_start);
+        *buf++ = (relpos >> 24) & 0xFF; // Track relative position (may be negative)
+        *buf++ = (relpos >> 16) & 0xFF;
+        *buf++ = (relpos >>  8) & 0xFF;
+        *buf++ = (relpos >>  0) & 0xFF;
+
+        if (len > allocation_length) len = allocation_length;
+        scsiDev.dataLen = len;
+        scsiDev.phase = DATA_IN;
+    }
+    else
+    {
+        debuglog("---- Unsupported subchannel request");
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+        scsiDev.phase = STATUS;
+        return;
+    }
+
+}
+
 /**************************************/
 /* CD-ROM command dispatching         */
 /**************************************/
@@ -837,7 +1261,7 @@ extern "C" int scsiCDRomCommand()
 	else if (command == 0x43)
 	{
 		// CD-ROM Read TOC
-		int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
+		bool MSF = (scsiDev.cdb[1] & 0x02);
 		uint8_t track = scsiDev.cdb[6];
 		uint16_t allocationLength =
 			(((uint32_t) scsiDev.cdb[7]) << 8) +
@@ -865,13 +1289,20 @@ extern "C" int scsiCDRomCommand()
 	else if (command == 0x44)
 	{
 		// CD-ROM Read Header
-		int MSF = scsiDev.cdb[1] & 0x02 ? 1 : 0;
+		bool MSF = (scsiDev.cdb[1] & 0x02);
 		uint32_t lba = 0; // IGNORED for now
 		uint16_t allocationLength =
 			(((uint32_t) scsiDev.cdb[7]) << 8) +
 			scsiDev.cdb[8];
 		doReadHeader(MSF, lba, allocationLength);
 	}
+    else if (command == 0x51)
+    {
+        uint16_t allocationLength =
+			(((uint32_t) scsiDev.cdb[7]) << 8) +
+			scsiDev.cdb[8];
+        doReadDiscInformation(allocationLength);
+    }
     else if (command == 0x4A)
     {
         // Get event status notifications (media change notifications)
@@ -921,7 +1352,89 @@ extern "C" int scsiCDRomCommand()
         // PAUSE/RESUME AUDIO
         doPauseResumeAudio(scsiDev.cdb[8] & 1);
     }
-	else
+    else if (command == 0xBD)
+    {
+        // Mechanism status
+        uint16_t allocationLength = (((uint32_t) scsiDev.cdb[8]) << 8) + scsiDev.cdb[9];
+        doMechanismStatus(allocationLength);
+    }
+    else if (command == 0xBB)
+    {
+        // Set CD speed (just ignored)
+        scsiDev.status = 0;
+        scsiDev.phase = STATUS;
+    }
+    else if (command == 0xBE)
+    {
+        // ReadCD (in low level format)
+        uint8_t sector_type = (scsiDev.cdb[1] >> 2) & 7;
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint32_t blocks =
+            (((uint32_t) scsiDev.cdb[6]) << 16) +
+            (((uint32_t) scsiDev.cdb[7]) << 8) +
+            (((uint32_t) scsiDev.cdb[8]));
+        uint8_t main_channel = scsiDev.cdb[9];
+        uint8_t sub_channel = scsiDev.cdb[10];
+
+        doReadCD(lba, blocks, sector_type, main_channel, sub_channel);
+    }
+    else if (command == 0xB9)
+    {
+        // ReadCD MSF
+        uint8_t sector_type = (scsiDev.cdb[1] >> 2) & 7;
+        uint32_t start = (scsiDev.cdb[3] * 60 + scsiDev.cdb[4]) * 75 + scsiDev.cdb[5];
+        uint32_t end   = (scsiDev.cdb[6] * 60 + scsiDev.cdb[7]) * 75 + scsiDev.cdb[8];
+        uint8_t main_channel = scsiDev.cdb[9];
+        uint8_t sub_channel = scsiDev.cdb[10];
+
+        doReadCD(start, end - start, sector_type, main_channel, sub_channel);
+    }
+    else if (command == 0x42)
+    {
+        // Read subchannel data
+        bool time = (scsiDev.cdb[1] & 0x02);
+        bool subq = (scsiDev.cdb[2] & 0x40);
+        uint8_t parameter = scsiDev.cdb[3];
+        uint8_t track_number = scsiDev.cdb[6];
+        uint16_t allocationLength = (((uint32_t) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
+
+        doReadSubchannel(time, subq, parameter, track_number, allocationLength);
+    }
+    else if (command == 0x28)
+    {
+        // READ(10) for CDs (may need sector translation for cue file handling)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint32_t blocks =
+            (((uint32_t) scsiDev.cdb[7]) << 8) +
+            scsiDev.cdb[8];
+
+        doReadCD(lba, blocks, 0, 0x10, 0);
+    }
+    else if (command == 0xA8)
+    {
+        // READ(12) for CDs (may need sector translation for cue file handling)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint32_t blocks =
+            (((uint32_t) scsiDev.cdb[6]) << 24) +
+            (((uint32_t) scsiDev.cdb[7]) << 16) +
+            (((uint32_t) scsiDev.cdb[8]) << 8) +
+            scsiDev.cdb[9];
+
+        doReadCD(lba, blocks, 0, 0x10, 0);
+    }
+    else
 	{
 		commandHandled = 0;
 	}

+ 9 - 1
src/BlueSCSI_cdrom.h

@@ -19,4 +19,12 @@ bool cdromSwitchNextImage(image_config_t &img);
 
 // Check if the currently loaded cue sheet for the image can be parsed
 // and print warnings about unsupported track types
-bool cdromValidateCueSheet(image_config_t &img);
+bool cdromValidateCueSheet(image_config_t &img);
+
+// Audio playback status
+enum CDROMAudioPlaybackStatus {
+    CDROMAudio_Stopped = 0,
+    CDROMAudio_Playing = 1,
+    CDROMAudio_Paused = 2
+};
+void cdromGetAudioPlaybackStatus(CDROMAudioPlaybackStatus *status, uint32_t *current_lba);

+ 5 - 5
src/BlueSCSI_disk.cpp

@@ -385,11 +385,11 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_id, int
             if (img.cuesheetfile.isOpen())
             {
                 log("---- Found CD-ROM CUE sheet at ", cuesheetname);
-            }
-            else if (!cdromValidateCueSheet(img))
-            {
-                log("---- Failed to parse cue sheet ", cuesheetname, ", using as plain binary image");
-                img.cuesheetfile.close();
+                if (!cdromValidateCueSheet(img))
+                {
+                    log("---- Failed to parse cue sheet, using as plain binary image");
+                    img.cuesheetfile.close();
+                }
             }
             else
             {

+ 10 - 0
src/BlueSCSI_log_trace.cpp

@@ -52,9 +52,19 @@ static const char *getCommandName(uint8_t cmd)
         case 0x43: return "CDROM Read TOC";
         case 0x44: return "CDROM Read Header";
         case 0x4A: return "GetEventStatusNotification";
+        case 0x51: return "CDROM ReadDiscInformation";
+        case 0x45: return "CDROM PlayAudio10";
+        case 0xA5: return "CDROM PlayAudio12";
+        case 0x47: return "CDROM PlayAudioMSF";
+        case 0x48: return "CDROM PauseResume";
+        case 0xBB: return "CDROM SetCDSpeed";
+        case 0xBD: return "CDROM MechanismStatus";
+        case 0xBE: return "ReadCD";
+        case 0xB9: return "ReadCDMSF";
         case 0x55: return "ModeSelect10";
         case 0x5A: return "ModeSense10";
         case 0xAC: return "Erase12";
+        case 0xA8: return "Read12";
         case 0xC0: return "OMTI-5204 DefineFlexibleDiskFormat";
         case 0xC2: return "OMTI-5204 AssignDiskParameters";
         default:   return "Unknown";