Просмотр исходного кода

Merge branch 'main' into f4-usb

Morio 2 лет назад
Родитель
Сommit
4c12469e7e

+ 11 - 7
lib/CUEParser/src/CUEParser.cpp

@@ -57,11 +57,12 @@ void CUEParser::restart()
 const CUETrackInfo *CUEParser::next_track()
 {
     // Previous track info is needed to track file offset
-    uint32_t prev_data_start = m_track_info.data_start;
+    uint32_t prev_track_start = m_track_info.track_start;
     uint32_t prev_sector_length = get_sector_length(m_track_info.file_mode, m_track_info.track_mode); // Defaults to 2352 before first track
 
     bool got_track = false;
     bool got_data = false;
+    bool got_pause = false; // true if a period of silence (INDEX 00) was encountered for a track
     while(!(got_track && got_data) && start_line())
     {
         if (strncasecmp(m_parse_pos, "FILE ", 5) == 0)
@@ -70,7 +71,7 @@ const CUETrackInfo *CUEParser::next_track()
             m_track_info.file_mode = parse_file_mode(skip_space(p));
             m_track_info.file_offset = 0;
             m_track_info.track_mode = CUETrack_AUDIO;
-            prev_data_start = 0;
+            prev_track_start = 0;
             prev_sector_length = get_sector_length(m_track_info.file_mode, m_track_info.track_mode);
         }
         else if (strncasecmp(m_parse_pos, "TRACK ", 6) == 0)
@@ -80,18 +81,17 @@ const CUETrackInfo *CUEParser::next_track()
             m_track_info.track_number = strtoul(track_num, &endptr, 10);
             m_track_info.track_mode = parse_track_mode(skip_space(endptr));
             m_track_info.sector_length = get_sector_length(m_track_info.file_mode, m_track_info.track_mode);
-            m_track_info.pregap_start = 0;
             m_track_info.unstored_pregap_length = 0;
             m_track_info.data_start = 0;
             m_track_info.track_start = 0;
             got_track = true;
             got_data = false;
+            got_pause = false;
         }
         else if (strncasecmp(m_parse_pos, "PREGAP ", 7) == 0)
         {
             const char *time_str = skip_space(m_parse_pos + 7);
             m_track_info.unstored_pregap_length = parse_time(time_str);
-            m_track_info.pregap_start = 0;
         }
         else if (strncasecmp(m_parse_pos, "INDEX ", 6) == 0)
         {
@@ -104,12 +104,11 @@ const CUETrackInfo *CUEParser::next_track()
 
             if (index == 0)
             {
-                m_track_info.pregap_start = time;
                 m_track_info.track_start = time;
+                got_pause = true;
             }
             else if (index == 1)
             {
-                m_track_info.file_offset += (uint64_t)(time - prev_data_start) * prev_sector_length;
                 m_track_info.data_start = time;
                 got_data = true;
             }
@@ -118,15 +117,20 @@ const CUETrackInfo *CUEParser::next_track()
         next_line();
     }
 
-    if (got_data && m_track_info.track_start == 0)
+    if (got_data && !got_pause)
     {
         m_track_info.track_start = m_track_info.data_start;
     }
 
     if (got_track && got_data)
+    {
+        m_track_info.file_offset += (uint64_t)(m_track_info.track_start - prev_track_start) * prev_sector_length;
         return &m_track_info;
+    }
     else
+    {
         return nullptr;
+    }
 }
 
 bool CUEParser::start_line()

+ 7 - 8
lib/CUEParser/src/CUEParser.h

@@ -53,25 +53,24 @@ struct CUETrackInfo
     // Source file name and file type, and offset to start of track data in bytes.
     char filename[CUE_MAX_FILENAME+1];
     CUEFileMode file_mode;
-    uint64_t file_offset;
+    uint64_t file_offset; // corresponds to track_start below
 
     // Track number and mode in CD format
     int track_number;
     CUETrackMode track_mode;
 
-    // Sector length for this track in bytes in the file, or 0 for audio files
+    // Sector length for this track in bytes, assuming BINARY or MOTOROLA file modes.
     uint32_t sector_length;
 
-    // Unstored pregap length, in CD frames, or 0
+    // The CD frames of PREGAP time at the start of this track, or 0 if none are present.
+    // These frames of silence are not stored in the underlying data file.
     uint32_t unstored_pregap_length;
 
-    // LBA start position of the pregap of this track (in CD frames)
-    uint32_t pregap_start;
-
-    // LBA start position of the data area of this track (in CD frames)
+    // LBA start position of the data area (INDEX 01) of this track (in CD frames)
     uint32_t data_start;
 
-    // Track start, either pregap_start or if no pregap, data_start.
+    // LBA for the beginning of the track, which will be INDEX 00 if that is present.
+    // Otherwise this will be INDEX 01 matching data_start above.
     uint32_t track_start;
 };
 

+ 6 - 5
lib/CUEParser/test/CUEParser_test.cpp

@@ -67,17 +67,18 @@ FILE "Sound.wav" WAVE
     COMMENT("Test TRACK 03 (audio with index 0)");
     track = parser.next_track();
     TEST(track != NULL);
-    uint32_t start3 = ((7 * 60) + 55) * 75 + 65;
+    uint32_t start3_i0 = ((7 * 60) + 55) * 75 + 58;
+    uint32_t start3_i1 = ((7 * 60) + 55) * 75 + 65;
     if (track)
     {
         TEST(strcmp(track->filename, "Image Name.bin") == 0);
         TEST(track->file_mode == CUEFile_BINARY);
-        TEST(track->file_offset == 2048 * start2 + 2352 * (start3 - start2));
+        TEST(track->file_offset == 2048 * start2 + 2352 * (start3_i0 - start2));
         TEST(track->track_number == 3);
         TEST(track->track_mode == CUETrack_AUDIO);
         TEST(track->sector_length == 2352);
-        TEST(track->pregap_start == ((7 * 60) + 55) * 75 + 58);
-        TEST(track->data_start == start3);
+        TEST(track->track_start == start3_i0);
+        TEST(track->data_start == start3_i1);
     }
 
     COMMENT("Test TRACK 11 (audio from wav)");
@@ -91,7 +92,7 @@ FILE "Sound.wav" WAVE
         TEST(track->track_number == 11);
         TEST(track->track_mode == CUETrack_AUDIO);
         TEST(track->sector_length == 0);
-        TEST(track->pregap_start == 0);
+        TEST(track->track_start == 0);
         TEST(track->data_start == 2 * 75);
     }
 

+ 3 - 2
lib/ZuluSCSI_platform_RP2040/audio.h

@@ -26,7 +26,8 @@
 
 // size of the two audio sample buffers, in bytes
 // these must be divisible by 1024
-#define AUDIO_BUFFER_SIZE 8192 // ~46.44ms
+// #define AUDIO_BUFFER_SIZE 8192 // ~46.44ms
+#define AUDIO_BUFFER_SIZE 4096 // reduce memory usage
 
 /**
  * Handler for DMA interrupts
@@ -59,4 +60,4 @@ void audio_setup();
  */
 void audio_poll();
 
-#endif // ENABLE_AUDIO_OUTPUT
+#endif // ENABLE_AUDIO_OUTPUT

+ 1 - 1
platformio.ini

@@ -1,7 +1,7 @@
 ; PlatformIO Project Configuration File https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1, ZuluSCSIv1_4, ZuluSCSI_RP2040, ZuluSCSI_BS2
+default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1, ZuluSCSIv1_4, ZuluSCSI_RP2040,  ZuluSCSI_RP2040_Audio, ZuluSCSI_BS2
 
 ; Example platform to serve as a base for porting efforts
 [env:template]

+ 305 - 160
src/ZuluSCSI_cdrom.cpp

@@ -95,7 +95,7 @@ static const uint8_t SessionTOC[] =
 static const uint8_t FullTOC[] =
 {
     0x00, //  0: toc length, MSB
-    0x44, //  1: toc length, LSB
+    0x2E, //  1: toc length, LSB
     0x01, //  2: First session number
     0x01, //  3: Last session number,
     // A0 Descriptor
@@ -131,9 +131,9 @@ static const uint8_t FullTOC[] =
     0x00, // 31: Sec
     0x00, // 32: Frame
     0x00, // 33: Zero
-    0x79, // 34: LEADOUT position BCD
-    0x59, // 35: leadout PSEC BCD
-    0x74, // 36: leadout PFRAME BCD
+    0x00, // 34: LEADOUT position
+    0x00, // 35: leadout PSEC
+    0x00, // 36: leadout PFRAME
     // TRACK 1 Descriptor
     0x01, // 37: session number
     0x14, // 38: ADR/Control
@@ -144,41 +144,8 @@ static const uint8_t FullTOC[] =
     0x00, // 43: Frame
     0x00, // 44: Zero
     0x00, // 45: PMIN
-    0x00, // 46: PSEC
+    0x02, // 46: PSEC
     0x00, // 47: PFRAME
-    // b0
-    0x01, // 48: session number
-    0x54, // 49: ADR/Control
-    0x00, // 50: TNO
-    0xB1, // 51: POINT
-    0x79, // 52: Min BCD
-    0x59, // 53: Sec BCD
-    0x74, // 54: Frame BCD
-    0x00, // 55: Zero
-    0x79, // 56: PMIN BCD
-    0x59, // 57: PSEC BCD
-    0x74, // 58: PFRAME BCD
-    // c0
-    0x01, // 59: session number
-    0x54, // 60: ADR/Control
-    0x00, // 61: TNO
-    0xC0, // 62: POINT
-    0x00, // 63: Min
-    0x00, // 64: Sec
-    0x00, // 65: Frame
-    0x00, // 66: Zero
-    0x00, // 67: PMIN
-    0x00, // 68: PSEC
-    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[] =
@@ -220,35 +187,56 @@ static const uint8_t DiscInformation[] =
 };
 
 // Convert logical block address to CD-ROM time
-static void LBA2MSF(uint32_t LBA, uint8_t* MSF)
+static void LBA2MSF(int32_t LBA, uint8_t* MSF, bool relative)
 {
-    MSF[2] = LBA % 75; // Frames
-    uint32_t rem = LBA / 75;
+    if (!relative) {
+        LBA += 150;
+    }
+    uint32_t ulba = LBA;
+    if (LBA < 0) {
+        ulba = LBA * -1;
+    }
+
+    MSF[2] = ulba % 75; // Frames
+    uint32_t rem = ulba / 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)
+static void LBA2MSFBCD(int32_t LBA, uint8_t* MSF, bool relative)
 {
-    uint8_t fra = LBA % 75;
-    uint32_t rem = LBA / 75;
-    uint8_t sec = rem % 60;
-    uint8_t min = rem / 60;
-
-    MSF[0] = ((min / 10) << 4) | (min % 10);
-    MSF[1] = ((sec / 10) << 4) | (sec % 10);
-    MSF[2] = ((fra / 10) << 4) | (fra % 10);
+    LBA2MSF(LBA, MSF, relative);
+    MSF[0] = ((MSF[0] / 10) << 4) | (MSF[0] % 10);
+    MSF[1] = ((MSF[1] / 10) << 4) | (MSF[1] % 10);
+    MSF[2] = ((MSF[2] / 10) << 4) | (MSF[2] % 10);
 }
 
 // Convert CD-ROM time to logical block address
-static uint32_t MSF2LBA(uint8_t m, uint8_t s, uint8_t f)
+static int32_t MSF2LBA(uint8_t m, uint8_t s, uint8_t f, bool relative)
 {
-    uint32_t lba = (m * 60 + s) * 75 + f;
+    int32_t lba = (m * 60 + s) * 75 + f;
+    if (!relative) lba -= 150;
     return lba;
 }
 
+// Gets the LBA position of the lead-out for the current image
+static uint32_t getLeadOutLBA(const CUETrackInfo* lasttrack)
+{
+    if (lasttrack != nullptr && lasttrack->track_number != 0)
+    {
+        image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+        uint32_t lastTrackBlocks = (img.file.size() - lasttrack->file_offset)
+                / lasttrack->sector_length;
+        return lasttrack->track_start + lastTrackBlocks + 1;
+    }
+    else
+    {
+        return 1;
+    }
+}
+
 static void doReadTOCSimple(bool MSF, uint8_t track, uint16_t allocationLength)
 {
     if (track == 0xAA)
@@ -266,7 +254,7 @@ static void doReadTOCSimple(bool MSF, uint8_t track, uint16_t allocationLength)
         if (MSF)
         {
             scsiDev.data[8] = 0;
-            LBA2MSF(capacity, scsiDev.data + 9);
+            LBA2MSF(capacity, scsiDev.data + 9, false);
         }
         else
         {
@@ -290,6 +278,11 @@ static void doReadTOCSimple(bool MSF, uint8_t track, uint16_t allocationLength)
         uint32_t len = sizeof(SimpleTOC);
         memcpy(scsiDev.data, SimpleTOC, len);
 
+        if (MSF)
+        {
+            scsiDev.data[0x0A] = 0x02;
+        }
+
         uint32_t capacity = getScsiCapacity(
             scsiDev.target->cfg->sdSectorStart,
             scsiDev.target->liveCfg.bytesPerSector,
@@ -299,7 +292,7 @@ static void doReadTOCSimple(bool MSF, uint8_t track, uint16_t allocationLength)
         if (MSF)
         {
             scsiDev.data[0x10] = 0;
-            LBA2MSF(capacity, scsiDev.data + 0x11);
+            LBA2MSF(capacity, scsiDev.data + 0x11, false);
         }
         else
         {
@@ -325,11 +318,16 @@ static void doReadTOCSimple(bool MSF, uint8_t track, uint16_t allocationLength)
     }
 }
 
-static void doReadSessionInfoSimple(uint8_t session, uint16_t allocationLength)
+static void doReadSessionInfoSimple(bool msf, uint16_t allocationLength)
 {
     uint32_t len = sizeof(SessionTOC);
     memcpy(scsiDev.data, SessionTOC, len);
 
+    if (msf)
+    {
+        scsiDev.data[0x0A] = 0x02;
+    }
+
     if (len > allocationLength)
     {
         len = allocationLength;
@@ -338,13 +336,7 @@ static void doReadSessionInfoSimple(uint8_t session, uint16_t allocationLength)
     scsiDev.phase = DATA_IN;
 }
 
-static inline uint8_t
-fromBCD(uint8_t val)
-{
-    return ((val >> 4) * 10) + (val & 0xF);
-}
-
-static void doReadFullTOCSimple(int convertBCD, uint8_t session, uint16_t allocationLength)
+static void doReadFullTOCSimple(uint8_t session, uint16_t allocationLength, bool useBCD)
 {
     // We only support session 1.
     if (session > 1)
@@ -359,20 +351,16 @@ static void doReadFullTOCSimple(int convertBCD, uint8_t session, uint16_t alloca
         uint32_t len = sizeof(FullTOC);
         memcpy(scsiDev.data, FullTOC, len);
 
-        if (convertBCD)
-        {
-            int descriptor = 4;
-            while (descriptor < len)
-            {
-                int i;
-                for (i = 0; i < 7; ++i)
-                {
-                    scsiDev.data[descriptor + i] =
-                        fromBCD(scsiDev.data[descriptor + 4 + i]);
-                }
-                descriptor += 11;
-            }
-
+        // update leadout position
+        // consistent 2048-byte blocks makes this easier than bin/cue version
+        uint32_t capacity = getScsiCapacity(
+            scsiDev.target->cfg->sdSectorStart,
+            scsiDev.target->liveCfg.bytesPerSector,
+            scsiDev.target->cfg->scsiSectors);
+        if (useBCD) {
+            LBA2MSFBCD(capacity, &scsiDev.data[34], false);
+        } else {
+            LBA2MSF(capacity, &scsiDev.data[34], false);
         }
 
         if (len > allocationLength)
@@ -384,18 +372,6 @@ static void doReadFullTOCSimple(int convertBCD, uint8_t session, uint16_t alloca
     }
 }
 
-void doReadHeaderSimple(bool MSF, uint32_t lba, uint16_t allocationLength)
-{
-    uint32_t len = sizeof(SimpleHeader);
-    memcpy(scsiDev.data, SimpleHeader, len);
-    if (len > allocationLength)
-    {
-        len = allocationLength;
-    }
-    scsiDev.dataLen = len;
-    scsiDev.phase = DATA_IN;
-}
-
 void doReadDiscInformationSimple(uint16_t allocationLength)
 {
     uint32_t len = sizeof(DiscInformation);
@@ -455,7 +431,7 @@ static void formatTrackInfo(const CUETrackInfo *track, uint8_t *dest, bool use_M
     {
         // Time in minute-second-frame format
         dest[4] = 0;
-        LBA2MSF(track->data_start, &dest[5]);
+        LBA2MSF(track->data_start, &dest[5], false);
     }
     else
     {
@@ -507,12 +483,12 @@ static void doReadTOC(bool MSF, uint8_t track, uint16_t allocationLength)
     uint8_t *trackdata = &scsiDev.data[4];
     int trackcount = 0;
     int firsttrack = -1;
-    int lasttrack = -1;
+    CUETrackInfo lasttrack = {0};
     const CUETrackInfo *trackinfo;
     while ((trackinfo = parser.next_track()) != NULL)
     {
         if (firsttrack < 0) firsttrack = trackinfo->track_number;
-        lasttrack = trackinfo->track_number;
+        lasttrack = *trackinfo;
 
         if (track <= trackinfo->track_number)
         {
@@ -524,8 +500,8 @@ static void doReadTOC(bool MSF, uint8_t track, uint16_t allocationLength)
     // Format lead-out track info
     CUETrackInfo leadout = {};
     leadout.track_number = 0xAA;
-    leadout.track_mode = CUETrack_MODE1_2048;
-    leadout.data_start = img.scsiSectors;
+    leadout.track_mode = (lasttrack.track_number != 0) ? lasttrack.track_mode : CUETrack_MODE1_2048;
+    leadout.data_start = getLeadOutLBA(&lasttrack);
     formatTrackInfo(&leadout, &trackdata[8 * trackcount], MSF);
     trackcount += 1;
 
@@ -534,7 +510,7 @@ static void doReadTOC(bool MSF, uint8_t track, uint16_t allocationLength)
     scsiDev.data[0] = toc_length >> 8;
     scsiDev.data[1] = toc_length & 0xFF;
     scsiDev.data[2] = firsttrack;
-    scsiDev.data[3] = lasttrack;
+    scsiDev.data[3] = lasttrack.track_number;
 
     if (track != 0xAA && trackcount < 2)
     {
@@ -558,14 +534,14 @@ static void doReadTOC(bool MSF, uint8_t track, uint16_t allocationLength)
     }
 }
 
-static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
+static void doReadSessionInfo(bool msf, 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 doReadSessionInfoSimple(session, allocationLength);
+        return doReadSessionInfoSimple(msf, allocationLength);
     }
 
     uint32_t len = sizeof(SessionTOC);
@@ -589,7 +565,7 @@ static void doReadSessionInfo(uint8_t session, uint16_t allocationLength)
 
 // Format track info read from cue sheet into the format used by ReadFullTOC command.
 // Refer to T10/1545-D MMC-4 Revision 5a, "Response Format 0010b: Raw TOC"
-static void formatRawTrackInfo(const CUETrackInfo *track, uint8_t *dest)
+static void formatRawTrackInfo(const CUETrackInfo *track, uint8_t *dest, bool useBCD)
 {
     uint8_t control_adr = 0x14; // Digital track
 
@@ -611,17 +587,21 @@ static void formatRawTrackInfo(const CUETrackInfo *track, uint8_t *dest)
     dest[6] = 0x00;
     dest[7] = 0; // HOUR
 
-    LBA2MSFBCD(track->data_start, &dest[8]);
+    if (useBCD) {
+        LBA2MSFBCD(track->data_start, &dest[8], false);
+    } else {
+        LBA2MSF(track->data_start, &dest[8], false);
+    }
 }
 
-static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLength)
+static void doReadFullTOC(uint8_t session, uint16_t allocationLength, bool useBCD)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
     CUEParser parser;
     if (!loadCueSheet(img, parser))
     {
         // No CUE sheet, use hardcoded data
-        return doReadFullTOCSimple(convertBCD, session, allocationLength);
+        return doReadFullTOCSimple(session, allocationLength, useBCD);
     }
 
     // We only support session 1.
@@ -641,28 +621,43 @@ static void doReadFullTOC(int convertBCD, uint8_t session, uint16_t allocationLe
     // Add track descriptors
     int trackcount = 0;
     int firsttrack = -1;
-    int lasttrack = -1;
+    CUETrackInfo lasttrack = {0};
     const CUETrackInfo *trackinfo;
     while ((trackinfo = parser.next_track()) != NULL)
     {
-        if (firsttrack < 0) firsttrack = trackinfo->track_number;
-        lasttrack = trackinfo->track_number;
+        if (firsttrack < 0)
+        {
+            firsttrack = trackinfo->track_number;
+            if (trackinfo->track_mode == CUETrack_AUDIO)
+            {
+                scsiDev.data[5] = 0x10;
+            }
+        }
+        lasttrack = *trackinfo;
 
-        formatRawTrackInfo(trackinfo, &scsiDev.data[len]);
+        formatRawTrackInfo(trackinfo, &scsiDev.data[len], useBCD);
         trackcount += 1;
         len += 11;
     }
 
     // First and last track numbers
     scsiDev.data[12] = firsttrack;
-    scsiDev.data[23] = lasttrack;
+    if (lasttrack.track_number != 0)
+    {
+        scsiDev.data[23] = lasttrack.track_number;
+        if (lasttrack.track_mode == CUETrack_AUDIO)
+        {
+            scsiDev.data[16] = 0x10;
+            scsiDev.data[27] = 0x10;
+        }
+    }
 
     // Leadout track position
-    LBA2MSFBCD(img.scsiSectors, &scsiDev.data[34]);
-
-    // Append recordable disc records b0 and c0 indicating non-recordable disc
-    memcpy(scsiDev.data + len, &FullTOC[48], 22);
-    len += 22;
+    if (useBCD) {
+        LBA2MSFBCD(getLeadOutLBA(&lasttrack), &scsiDev.data[34], false);
+    } else {
+        LBA2MSF(getLeadOutLBA(&lasttrack), &scsiDev.data[34], false);
+    }
 
     // Correct the record length in header
     uint16_t toclen = len - 2;
@@ -679,6 +674,9 @@ 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
+// The spec is vague, but based on experimentation with a Matshita drive this
+// command should return the sector header absolute time (see ECMA-130 21).
+// Given 2048-byte block sizes this effectively is 1:1 with the provided LBA.
 void doReadHeader(bool MSF, uint32_t lba, uint16_t allocationLength)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
@@ -688,41 +686,41 @@ void doReadHeader(bool MSF, uint32_t lba, uint16_t allocationLength)
     audio_stop(img.scsiId & 7);
 #endif
 
+    uint8_t mode = 1;
     CUEParser parser;
-    if (!loadCueSheet(img, parser))
+    if (loadCueSheet(img, parser))
     {
-        // No CUE sheet, use hardcoded data
-        return doReadHeaderSimple(MSF, lba, allocationLength);
-    }
-
-    // Take the hardcoded header as base
-    uint32_t len = sizeof(SimpleHeader);
-    memcpy(scsiDev.data, SimpleHeader, len);
-
-    // Search the track with the requested LBA
-    CUETrackInfo trackinfo = {};
-    getTrackFromLBA(parser, lba, &trackinfo);
+        // Search the track with the requested LBA
+        CUETrackInfo trackinfo = {};
+        getTrackFromLBA(parser, lba, &trackinfo);
 
-    // Track mode (audio / data)
-    if (trackinfo.track_mode == CUETrack_AUDIO)
-    {
-        scsiDev.data[0] = 0;
+        // Track mode (audio / data)
+        if (trackinfo.track_mode == CUETrack_AUDIO)
+        {
+            scsiDev.data[0] = 0;
+        }
     }
 
+    scsiDev.data[0] = mode;
+    scsiDev.data[1] = 0; // reserved
+    scsiDev.data[2] = 0; // reserved
+    scsiDev.data[3] = 0; // reserved
+
     // Track start
     if (MSF)
     {
         scsiDev.data[4] = 0;
-        LBA2MSF(trackinfo.data_start, &scsiDev.data[5]);
+        LBA2MSF(lba, &scsiDev.data[5], false);
     }
     else
     {
-        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;
+        scsiDev.data[4] = (lba >> 24) & 0xFF;
+        scsiDev.data[5] = (lba >> 16) & 0xFF;
+        scsiDev.data[6] = (lba >>  8) & 0xFF;
+        scsiDev.data[7] = (lba >>  0) & 0xFF;
     }
 
+    uint8_t len = 8;
     if (len > allocationLength)
     {
         len = allocationLength;
@@ -988,10 +986,8 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
             lba = img.file.position() / 2352;
         }
 
-        // --- TODO --- determine proper track offset, software I tested with had a tendency
-        // to ask for offsets that seem to hint at 2048 here, not the 2352 you'd assume.
-        // Might be due to a mode page reporting something unexpected? Needs investigation.
-        uint64_t offset = trackinfo.file_offset + 2048 * (lba - trackinfo.data_start);
+        uint64_t offset = trackinfo.file_offset
+                + trackinfo.sector_length * (lba - trackinfo.track_start);
         dbgmsg("------ Play audio CD: ", (int)length, " sectors starting at ", (int)lba,
            ", track number ", trackinfo.track_number, ", data offset in file ", (int)offset);
 
@@ -1007,7 +1003,8 @@ static void doPlayAudio(uint32_t lba, uint32_t length)
 
         // playback request appears to be sane, so perform it
         // see earlier note for context on the block length below
-        if (!audio_play(target_id, &(img.file), offset, offset + length * 2048, false))
+        if (!audio_play(target_id, &(img.file), offset,
+                offset + length * trackinfo.sector_length, false))
         {
             // Underlying data/media error? Fake a disk scratch, which should
             // be a condition most CD-DA players are expecting
@@ -1109,7 +1106,7 @@ static void doMechanismStatus(uint16_t allocation_length)
 /*******************************************/
 
 static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
-                     uint8_t main_channel, uint8_t sub_channel)
+                     uint8_t main_channel, uint8_t sub_channel, bool data_only)
 {
     image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
 
@@ -1134,12 +1131,25 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
     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);
+    uint64_t offset = trackinfo.file_offset + trackinfo.sector_length * (lba - trackinfo.track_start);
     dbgmsg("------ 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);
 
+    // Ensure read is not out of range of the image
+    uint64_t readend = offset + trackinfo.sector_length * length;
+    if (readend > img.file.size())
+    {
+        logmsg("WARNING: Host attempted CD read at sector ", lba, "+", length,
+              ", exceeding image size ", img.file.size());
+        scsiDev.status = CHECK_CONDITION;
+        scsiDev.target->sense.code = ILLEGAL_REQUEST;
+        scsiDev.target->sense.asc = LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+        scsiDev.phase = STATUS;
+        return;
+    }
+
     // Verify sector type
     if (sector_type != 0)
     {
@@ -1214,6 +1224,16 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
         return;
     }
 
+    if (data_only && sector_length != 2048)
+    {
+        dbgmsg("------ Host tried to read non-data sector with standard READ command");
+        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)
     {
@@ -1275,7 +1295,7 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
             *buf++ = 0x00;
 
             // 4-byte data sector header
-            LBA2MSFBCD(lba + idx, buf);
+            LBA2MSFBCD(lba + idx, buf, false);
             buf += 3;
             *buf++ = 0x01; // Mode 1
         }
@@ -1298,12 +1318,14 @@ static void doReadCD(uint32_t lba, uint32_t length, uint8_t sector_type,
         {
             // Formatted Q subchannel data
             // Refer to table 354 in T10/1545-D MMC-4 Revision 5a
+            // and ECMA-130 22.3.3
             *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)
-            LBA2MSF(lba + idx, buf); buf += 3;
+            int32_t rel = (int32_t)(lba + idx) - (int32_t)trackinfo.data_start;
+            LBA2MSF(rel, buf, true); buf += 3;
             *buf++ = 0;
-            LBA2MSF(lba + idx, buf); buf += 3;
+            LBA2MSF(lba + idx, buf, false); buf += 3;
             *buf++ = 0; *buf++ = 0; // CRC (optional)
             *buf++ = 0; *buf++ = 0; *buf++ = 0; // (pad)
             *buf++ = 0; // No P subchannel
@@ -1354,7 +1376,7 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
             if (time)
             {
                 *buf++ = 0;
-                LBA2MSF(lba, buf);
+                LBA2MSF(lba, buf, false);
                 dbgmsg("------ ABS M ", *buf, " S ", *(buf+1), " F ", *(buf+2));
                 buf += 3;
             }
@@ -1366,20 +1388,21 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
                 *buf++ = (lba >>  0) & 0xFF;
             }
 
-            uint32_t relpos = (uint32_t)((int32_t)lba - (int32_t)trackinfo.data_start);
+            int32_t relpos = (int32_t)lba - (int32_t)trackinfo.data_start;
             if (time)
             {
                 *buf++ = 0;
-                LBA2MSF(relpos, buf);
+                LBA2MSF(relpos, buf, true);
                 dbgmsg("------ REL M ", *buf, " S ", *(buf+1), " F ", *(buf+2));
                 buf += 3;
             }
             else
             {
-                *buf++ = (relpos >> 24) & 0xFF; // Track relative position (may be negative)
-                *buf++ = (relpos >> 16) & 0xFF;
-                *buf++ = (relpos >>  8) & 0xFF;
-                *buf++ = (relpos >>  0) & 0xFF;
+                uint32_t urelpos = relpos;
+                *buf++ = (urelpos >> 24) & 0xFF; // Track relative position (may be negative)
+                *buf++ = (urelpos >> 16) & 0xFF;
+                *buf++ = (urelpos >>  8) & 0xFF;
+                *buf++ = (urelpos >>  0) & 0xFF;
             }
         }
         else
@@ -1406,6 +1429,56 @@ static void doReadSubchannel(bool time, bool subq, uint8_t parameter, uint8_t tr
 
 }
 
+static bool doReadCapacity(uint32_t lba, uint8_t pmi)
+{
+    image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
+
+    CUEParser parser;
+    if (!loadCueSheet(img, parser))
+    {
+        // basic image, let the disk handler resolve
+        return false;
+    }
+
+    // find the last track on the disk
+    CUETrackInfo lasttrack = {0};
+    const CUETrackInfo *trackinfo;
+    while ((trackinfo = parser.next_track()) != NULL)
+    {
+        lasttrack = *trackinfo;
+    }
+
+    uint32_t capacity = 0;
+    if (lasttrack.track_number != 0)
+    {
+        capacity = getLeadOutLBA(&lasttrack);
+        capacity--; // shift to last addressable LBA
+        if (pmi && lba && lba > capacity)
+        {
+            // MMC technically specifies that PMI should be zero, but SCSI-2 allows this
+            // potentially consider treating either out-of-bounds or PMI set as an error
+            // for now just ignore this
+        }
+        dbgmsg("----- Reporting capacity as ", capacity);
+    }
+    else
+    {
+        logmsg("WARNING: unable to find capacity, no cue file found for ID ", img.scsiId);
+    }
+
+    scsiDev.data[0] = capacity >> 24;
+    scsiDev.data[1] = capacity >> 16;
+    scsiDev.data[2] = capacity >> 8;
+    scsiDev.data[3] = capacity;
+    scsiDev.data[4] = 0;
+    scsiDev.data[5] = 0;
+    scsiDev.data[6] = 0x08; // rest of code assumes 2048 here
+    scsiDev.data[7] = 0x00;
+    scsiDev.dataLen = 8;
+    scsiDev.phase = DATA_IN;
+    return true;
+}
+
 /**************************************/
 /* CD-ROM command dispatching         */
 /**************************************/
@@ -1440,6 +1513,34 @@ extern "C" int scsiCDRomCommand()
             commandHandled = 0;
         }
     }
+    else if (command == 0x25)
+    {
+        // READ CAPACITY
+        uint8_t reladdr = scsiDev.cdb[1] & 1;
+        uint32_t lba = (((uint32_t) scsiDev.cdb[2]) << 24) +
+            (((uint32_t) scsiDev.cdb[3]) << 16) +
+            (((uint32_t) scsiDev.cdb[4]) << 8) +
+            scsiDev.cdb[5];
+        uint8_t pmi = scsiDev.cdb[8] & 1;
+
+        // allow PMI as long as LBA is specified, this is permitted in SCSI-2
+        // we don't link commands, do not allow RELADDR
+        if ((!pmi && lba != 0) || reladdr)
+        {
+            scsiDev.status = CHECK_CONDITION;
+            scsiDev.target->sense.code = ILLEGAL_REQUEST;
+            scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
+            scsiDev.phase = STATUS;
+        }
+        else
+        {
+            if (!doReadCapacity(lba, pmi))
+            {
+                // allow disk handler to resolve this one
+                commandHandled = 0;
+            }
+        }
+    }
     else if (command == 0x43)
     {
         // CD-ROM Read TOC
@@ -1449,16 +1550,34 @@ extern "C" int scsiCDRomCommand()
             (((uint32_t) scsiDev.cdb[7]) << 8) +
             scsiDev.cdb[8];
 
-        // Reject MMC commands for now, otherwise the TOC data format
-        // won't be understood.
         // The "format" field is reserved for SCSI-2
         uint8_t format = scsiDev.cdb[2] & 0x0F;
+
+        // Matshita SCSI-2 drives appear to use the high 2 bits of the CDB
+        // control byte to switch on session info (0x40) and full toc (0x80)
+        // responses that are very similar to the standard formats described
+        // in MMC-1. These vendor flags must have been pretty common because
+        // even a modern SATA drive (ASUS DRW-24B1ST j) responds to them
+        // (though it always replies in hex rather than bcd)
+        //
+        // The session information page is identical to MMC. The full TOC page
+        // is identical _except_ it returns addresses in bcd rather than hex.
+        bool useBCD = false;
+        if (format == 0 && scsiDev.cdb[9] == 0x80)
+        {
+            format = 2;
+            useBCD = true;
+        }
+        else if (format == 0 && scsiDev.cdb[9] == 0x40)
+        {
+            format = 1;
+        }
+
         switch (format)
         {
             case 0: doReadTOC(MSF, track, allocationLength); break; // SCSI-2
             case 1: doReadSessionInfo(MSF, allocationLength); break; // MMC2
-            case 2: doReadFullTOC(0, track, allocationLength); break; // MMC2
-            case 3: doReadFullTOC(1, track, allocationLength); break; // MMC2
+            case 2: doReadFullTOC(track, allocationLength, useBCD); break; // MMC2
             default:
             {
                 scsiDev.status = CHECK_CONDITION;
@@ -1524,8 +1643,8 @@ extern "C" int scsiCDRomCommand()
     else if (command == 0x47)
     {
         // PLAY AUDIO (MSF)
-        uint32_t start = MSF2LBA(scsiDev.cdb[3], scsiDev.cdb[4], scsiDev.cdb[5]);
-        uint32_t end   = MSF2LBA(scsiDev.cdb[6], scsiDev.cdb[7], scsiDev.cdb[8]);
+        uint32_t start = MSF2LBA(scsiDev.cdb[3], scsiDev.cdb[4], scsiDev.cdb[5], false);
+        uint32_t end   = MSF2LBA(scsiDev.cdb[6], scsiDev.cdb[7], scsiDev.cdb[8], false);
 
         uint32_t lba = start;
         if (scsiDev.cdb[3] == 0xFF
@@ -1573,18 +1692,18 @@ extern "C" int scsiCDRomCommand()
         uint8_t main_channel = scsiDev.cdb[9];
         uint8_t sub_channel = scsiDev.cdb[10];
 
-        doReadCD(lba, blocks, sector_type, main_channel, sub_channel);
+        doReadCD(lba, blocks, sector_type, main_channel, sub_channel, false);
     }
     else if (command == 0xB9)
     {
         // ReadCD MSF
         uint8_t sector_type = (scsiDev.cdb[1] >> 2) & 7;
-        uint32_t start = MSF2LBA(scsiDev.cdb[3], scsiDev.cdb[4], scsiDev.cdb[5]);
-        uint32_t end   = MSF2LBA(scsiDev.cdb[6], scsiDev.cdb[7], scsiDev.cdb[8]);
+        uint32_t start = MSF2LBA(scsiDev.cdb[3], scsiDev.cdb[4], scsiDev.cdb[5], false);
+        uint32_t end   = MSF2LBA(scsiDev.cdb[6], scsiDev.cdb[7], scsiDev.cdb[8], false);
         uint8_t main_channel = scsiDev.cdb[9];
         uint8_t sub_channel = scsiDev.cdb[10];
 
-        doReadCD(start, end - start, sector_type, main_channel, sub_channel);
+        doReadCD(start, end - start, sector_type, main_channel, sub_channel, false);
     }
     else if (command == 0x42)
     {
@@ -1597,6 +1716,17 @@ extern "C" int scsiCDRomCommand()
 
         doReadSubchannel(time, subq, parameter, track_number, allocationLength);
     }
+    else if (command == 0x08)
+    {
+        // READ(6) for CDs (may need sector translation for cue file handling)
+        uint32_t lba =
+            (((uint32_t) scsiDev.cdb[1] & 0x1F) << 16) +
+            (((uint32_t) scsiDev.cdb[2]) << 8) +
+            scsiDev.cdb[3];
+        uint32_t blocks = scsiDev.cdb[4];
+
+        doReadCD(lba, blocks, 0, 0x10, 0, true);
+    }
     else if (command == 0x28)
     {
         // READ(10) for CDs (may need sector translation for cue file handling)
@@ -1609,7 +1739,7 @@ extern "C" int scsiCDRomCommand()
             (((uint32_t) scsiDev.cdb[7]) << 8) +
             scsiDev.cdb[8];
 
-        doReadCD(lba, blocks, 0, 0x10, 0);
+        doReadCD(lba, blocks, 0, 0x10, 0, true);
     }
     else if (command == 0xA8)
     {
@@ -1625,7 +1755,7 @@ extern "C" int scsiCDRomCommand()
             (((uint32_t) scsiDev.cdb[8]) << 8) +
             scsiDev.cdb[9];
 
-        doReadCD(lba, blocks, 0, 0x10, 0);
+        doReadCD(lba, blocks, 0, 0x10, 0, true);
     }
     else if (command == 0x4E)
     {
@@ -1652,6 +1782,21 @@ extern "C" int scsiCDRomCommand()
         // expect a pickup move to the given LBA
         commandHandled = 0;
     }
+    else if (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE
+            && command == 0xCD)
+    {
+        // vendor-specific command issued by the AppleCD Audio Player in
+        // response to fast-forward or rewind commands. Might be seek,
+        // might be reposition. Exact MSF value below is unknown.
+        //
+        // Byte 0: 0xCD
+        // Byte 1: 0x10 for rewind, 0x00 for fast-forward
+        // Byte 2: 0x00
+        // Byte 3: 'M' in hex
+        // Byte 4: 'S' in hex
+        // Byte 5: 'F' in hex
+        commandHandled = 0;
+    }
     else
     {
         commandHandled = 0;

+ 1 - 1
src/ZuluSCSI_config.h

@@ -27,7 +27,7 @@
 #include <ZuluSCSI_platform.h>
 
 // Use variables for version number
-#define FW_VER_NUM      "23.05.17"
+#define FW_VER_NUM      "23.05.26"
 #define FW_VER_SUFFIX   "devel"
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 

+ 1 - 0
src/ZuluSCSI_disk.cpp

@@ -452,6 +452,7 @@ static void scsiDiskConfigDefaults(int target_idx)
     preset_config_t defaults = getSystemPreset(presetName);
 
     image_config_t &img = g_DiskImages[target_idx];
+    img.scsiId = target_idx;
     img.deviceType = S2S_CFG_FIXED;
     img.deviceTypeModifier = defaults.deviceTypeModifier;
     img.sectorsPerTrack = defaults.sectorsPerTrack;