duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

cd_image_device.cpp (51657B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "assert.h"
      5 #include "cd_image.h"
      6 
      7 // TODO: Remove me..
      8 #include "core/host.h"
      9 
     10 #include "common/assert.h"
     11 #include "common/error.h"
     12 #include "common/log.h"
     13 #include "common/path.h"
     14 #include "common/small_string.h"
     15 #include "common/string_util.h"
     16 
     17 #include <algorithm>
     18 #include <cerrno>
     19 #include <cinttypes>
     20 #include <cmath>
     21 #include <optional>
     22 #include <span>
     23 
     24 Log_SetChannel(CDImageDevice);
     25 
     26 // Common code
     27 [[maybe_unused]] static constexpr u32 MAX_TRACK_NUMBER = 99;
     28 [[maybe_unused]] static constexpr u32 SCSI_CMD_LENGTH = 12;
     29 
     30 enum class SCSIReadMode : u8
     31 {
     32   None,
     33   Raw,
     34   Full,
     35   SubQOnly,
     36 };
     37 
     38 [[maybe_unused]] static void FillSCSIReadCommand(u8 cmd[SCSI_CMD_LENGTH], u32 sector_number, SCSIReadMode mode)
     39 {
     40   cmd[0] = 0xBE;                           // READ CD
     41   cmd[1] = 0x00;                           // sector type
     42   cmd[2] = Truncate8(sector_number >> 24); // Starting LBA
     43   cmd[3] = Truncate8(sector_number >> 16);
     44   cmd[4] = Truncate8(sector_number >> 8);
     45   cmd[5] = Truncate8(sector_number);
     46   cmd[6] = 0x00; // Transfer Count
     47   cmd[7] = 0x00;
     48   cmd[8] = 0x01;
     49   cmd[9] = (1 << 7) |    // include sync
     50            (0b11 << 5) | // include header codes
     51            (1 << 4) |    // include user data
     52            (1 << 3) |    // edc/ecc
     53            (0 << 2);     // don't include C2 data
     54 
     55   if (mode == SCSIReadMode::None || mode == SCSIReadMode::Raw)
     56     cmd[10] = 0b000;
     57   else if (mode == SCSIReadMode::Full)
     58     cmd[10] = 0b001;
     59   else // if (mode == SCSIReadMode::SubQOnly)
     60     cmd[10] = 0b010;
     61   cmd[11] = 0;
     62 }
     63 
     64 [[maybe_unused]] static void FillSCSISetSpeedCommand(u8 cmd[SCSI_CMD_LENGTH], u32 speed_multiplier)
     65 {
     66   DebugAssert(speed_multiplier > 0);
     67 
     68   cmd[0] = 0xDA; // SET CD-ROM SPEED
     69   cmd[1] = 0x00;
     70   cmd[2] = Truncate8(speed_multiplier - 1);
     71   cmd[3] = 0x00;
     72   cmd[4] = 0x00;
     73   cmd[5] = 0x00;
     74   cmd[6] = 0x00;
     75   cmd[7] = 0x00;
     76   cmd[8] = 0x00;
     77   cmd[9] = 0x00;
     78   cmd[10] = 0x00;
     79   cmd[11] = 0x00;
     80 }
     81 
     82 [[maybe_unused]] static constexpr u32 SCSIReadCommandOutputSize(SCSIReadMode mode)
     83 {
     84   switch (mode)
     85   {
     86     case SCSIReadMode::None:
     87     case SCSIReadMode::Raw:
     88       return CDImage::RAW_SECTOR_SIZE;
     89     case SCSIReadMode::Full:
     90       return CDImage::RAW_SECTOR_SIZE + CDImage::ALL_SUBCODE_SIZE;
     91     case SCSIReadMode::SubQOnly:
     92       return CDImage::RAW_SECTOR_SIZE + CDImage::SUBCHANNEL_BYTES_PER_FRAME;
     93     default:
     94       UnreachableCode();
     95   }
     96 }
     97 
     98 [[maybe_unused]] static bool VerifySCSIReadData(std::span<const u8> buffer, SCSIReadMode mode,
     99                                                 CDImage::LBA expected_sector)
    100 {
    101   const u32 expected_size = SCSIReadCommandOutputSize(mode);
    102   if (buffer.size() != expected_size)
    103   {
    104     ERROR_LOG("SCSI returned {} bytes, expected {}", buffer.size(), expected_size);
    105     return false;
    106   }
    107 
    108   const CDImage::Position expected_pos = CDImage::Position::FromLBA(expected_sector);
    109 
    110   if (mode == SCSIReadMode::Full)
    111   {
    112     // Validate subcode.
    113     u8 deinterleaved_subcode[CDImage::ALL_SUBCODE_SIZE];
    114     CDImage::SubChannelQ subq;
    115     CDImage::DeinterleaveSubcode(buffer.data() + CDImage::RAW_SECTOR_SIZE, deinterleaved_subcode);
    116     std::memcpy(&subq, &deinterleaved_subcode[CDImage::SUBCHANNEL_BYTES_PER_FRAME], sizeof(subq));
    117 
    118     DEV_LOG("SCSI full subcode read returned [{}] for {:02d}:{:02d}:{:02d}",
    119             StringUtil::EncodeHex(subq.data.data(), static_cast<int>(subq.data.size())), expected_pos.minute,
    120             expected_pos.second, expected_pos.frame);
    121 
    122     if (!subq.IsCRCValid())
    123     {
    124       WARNING_LOG("SCSI full subcode read returned invalid SubQ CRC (got {:02X} expected {:02X})", subq.crc,
    125                   CDImage::SubChannelQ::ComputeCRC(subq.data));
    126       return false;
    127     }
    128 
    129     const CDImage::Position got_pos =
    130       CDImage::Position::FromBCD(subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd);
    131     if (expected_pos != got_pos)
    132     {
    133       WARNING_LOG(
    134         "SCSI full subcode read returned invalid MSF (got {:02x}:{:02x}:{:02x}, expected {:02d}:{:02d}:{:02d})",
    135         subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd, expected_pos.minute,
    136         expected_pos.second, expected_pos.frame);
    137       return false;
    138     }
    139 
    140     return true;
    141   }
    142   else if (mode == SCSIReadMode::SubQOnly)
    143   {
    144     CDImage::SubChannelQ subq;
    145     std::memcpy(&subq, buffer.data() + CDImage::RAW_SECTOR_SIZE, sizeof(subq));
    146     DEV_LOG("SCSI subq read returned [{}] for {:02d}:{:02d}:{:02d}",
    147             StringUtil::EncodeHex(subq.data.data(), static_cast<int>(subq.data.size())), expected_pos.minute,
    148             expected_pos.second, expected_pos.frame);
    149 
    150     if (!subq.IsCRCValid())
    151     {
    152       WARNING_LOG("SCSI subq read returned invalid SubQ CRC (got {:02X} expected {:02X})", subq.crc,
    153                   CDImage::SubChannelQ::ComputeCRC(subq.data));
    154       return false;
    155     }
    156 
    157     const CDImage::Position got_pos =
    158       CDImage::Position::FromBCD(subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd);
    159     if (expected_pos != got_pos)
    160     {
    161       WARNING_LOG("SCSI subq read returned invalid MSF (got {:02x}:{:02x}:{:02x}, expected {:02d}:{:02d}:{:02d})",
    162                   subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd, expected_pos.minute,
    163                   expected_pos.second, expected_pos.frame);
    164       return false;
    165     }
    166 
    167     return true;
    168   }
    169   else // if (mode == SCSIReadMode::None || mode == SCSIReadMode::Raw)
    170   {
    171     // I guess we could check the sector sync data too...
    172     return true;
    173   }
    174 }
    175 
    176 [[maybe_unused]] static bool ShouldTryReadingSubcode()
    177 {
    178   return !Host::GetBaseBoolSettingValue("CDROM", "IgnoreHostSubcode", false);
    179 }
    180 
    181 #if defined(_WIN32)
    182 
    183 // The include order here is critical.
    184 // clang-format off
    185 #include "common/windows_headers.h"
    186 #include <winioctl.h>
    187 #include <ntddcdrm.h>
    188 #include <ntddscsi.h>
    189 // clang-format on
    190 
    191 static u32 BEToU32(const u8* val)
    192 {
    193   return (static_cast<u32>(val[0]) << 24) | (static_cast<u32>(val[1]) << 16) | (static_cast<u32>(val[2]) << 8) |
    194          static_cast<u32>(val[3]);
    195 }
    196 
    197 static void U16ToBE(u8* beval, u16 leval)
    198 {
    199   beval[0] = static_cast<u8>(leval >> 8);
    200   beval[1] = static_cast<u8>(leval);
    201 }
    202 
    203 namespace {
    204 
    205 class CDImageDeviceWin32 : public CDImage
    206 {
    207 public:
    208   CDImageDeviceWin32();
    209   ~CDImageDeviceWin32() override;
    210 
    211   bool Open(const char* filename, Error* error);
    212 
    213   bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
    214   bool HasNonStandardSubchannel() const override;
    215 
    216 protected:
    217   bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
    218 
    219 private:
    220   std::optional<u32> DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer);
    221   std::optional<u32> DoSCSIRead(LBA lba, SCSIReadMode read_mode);
    222   bool DoRawRead(LBA lba);
    223   bool DoSetSpeed(u32 speed_multiplier);
    224 
    225   bool ReadSectorToBuffer(LBA lba);
    226   bool DetermineReadMode(bool try_sptd);
    227 
    228   HANDLE m_hDevice = INVALID_HANDLE_VALUE;
    229 
    230   u32 m_current_lba = ~static_cast<LBA>(0);
    231 
    232   SCSIReadMode m_scsi_read_mode = SCSIReadMode::None;
    233   bool m_has_valid_subcode = false;
    234 
    235   std::array<u8, CD_RAW_SECTOR_WITH_SUBCODE_SIZE> m_buffer;
    236 };
    237 
    238 } // namespace
    239 
    240 CDImageDeviceWin32::CDImageDeviceWin32() = default;
    241 
    242 CDImageDeviceWin32::~CDImageDeviceWin32()
    243 {
    244   if (m_hDevice != INVALID_HANDLE_VALUE)
    245     CloseHandle(m_hDevice);
    246 }
    247 
    248 bool CDImageDeviceWin32::Open(const char* filename, Error* error)
    249 {
    250   bool try_sptd = true;
    251 
    252   m_filename = filename;
    253   m_hDevice = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
    254                          OPEN_EXISTING, 0, NULL);
    255   if (m_hDevice == INVALID_HANDLE_VALUE)
    256   {
    257     m_hDevice = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, NULL);
    258     if (m_hDevice != INVALID_HANDLE_VALUE)
    259     {
    260       WARNING_LOG("Could not open '{}' as read/write, can't use SPTD", filename);
    261       try_sptd = false;
    262     }
    263     else
    264     {
    265       ERROR_LOG("CreateFile('{}') failed: %08X", filename, GetLastError());
    266       if (error)
    267         error->SetWin32(GetLastError());
    268 
    269       return false;
    270     }
    271   }
    272 
    273   // Set it to 4x speed. A good balance between readahead and spinning up way too high.
    274   static constexpr u32 READ_SPEED_MULTIPLIER = 8;
    275   static constexpr u32 READ_SPEED_KBS = (DATA_SECTOR_SIZE * FRAMES_PER_SECOND * READ_SPEED_MULTIPLIER) / 1024;
    276   CDROM_SET_SPEED set_speed = {CdromSetSpeed, READ_SPEED_KBS, 0, CdromDefaultRotation};
    277   if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_SET_SPEED, &set_speed, sizeof(set_speed), nullptr, 0, nullptr, nullptr))
    278     WARNING_LOG("DeviceIoControl(IOCTL_CDROM_SET_SPEED) failed: {:08X}", GetLastError());
    279 
    280   CDROM_READ_TOC_EX read_toc_ex = {};
    281   read_toc_ex.Format = CDROM_READ_TOC_EX_FORMAT_TOC;
    282   read_toc_ex.Msf = 0;
    283   read_toc_ex.SessionTrack = 1;
    284 
    285   CDROM_TOC toc = {};
    286   U16ToBE(toc.Length, sizeof(toc) - sizeof(UCHAR) * 2);
    287 
    288   DWORD bytes_returned;
    289   if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_READ_TOC_EX, &read_toc_ex, sizeof(read_toc_ex), &toc, sizeof(toc),
    290                        &bytes_returned, nullptr) ||
    291       toc.LastTrack < toc.FirstTrack)
    292   {
    293     ERROR_LOG("DeviceIoCtl(IOCTL_CDROM_READ_TOC_EX) failed: {:08X}", GetLastError());
    294     if (error)
    295       error->SetWin32(GetLastError());
    296 
    297     return false;
    298   }
    299 
    300   DWORD last_track_address = 0;
    301   LBA disc_lba = 0;
    302   DEV_LOG("FirstTrack={}, LastTrack={}", toc.FirstTrack, toc.LastTrack);
    303 
    304   const u32 num_tracks_to_check = (toc.LastTrack - toc.FirstTrack) + 1 + 1;
    305   for (u32 track_index = 0; track_index < num_tracks_to_check; track_index++)
    306   {
    307     const TRACK_DATA& td = toc.TrackData[track_index];
    308     const u8 track_num = td.TrackNumber;
    309     const DWORD track_address = BEToU32(td.Address);
    310     DEV_LOG("  [{}]: Num={:02X}, Address={}", track_index, track_num, track_address);
    311 
    312     // fill in the previous track's length
    313     if (!m_tracks.empty())
    314     {
    315       if (track_num < m_tracks.back().track_number)
    316       {
    317         ERROR_LOG("Invalid TOC, track {} less than {}", track_num, m_tracks.back().track_number);
    318         return false;
    319       }
    320 
    321       const LBA previous_track_length = static_cast<LBA>(track_address - last_track_address);
    322       m_tracks.back().length += previous_track_length;
    323       m_indices.back().length += previous_track_length;
    324       disc_lba += previous_track_length;
    325     }
    326 
    327     last_track_address = track_address;
    328     if (track_num == LEAD_OUT_TRACK_NUMBER)
    329     {
    330       AddLeadOutIndex();
    331       break;
    332     }
    333 
    334     // precompute subchannel q flags for the whole track
    335     SubChannelQ::Control control{};
    336     control.bits = td.Adr | (td.Control << 4);
    337 
    338     const LBA track_lba = static_cast<LBA>(track_address);
    339     const TrackMode track_mode = control.data ? CDImage::TrackMode::Mode2Raw : CDImage::TrackMode::Audio;
    340 
    341     // TODO: How the hell do we handle pregaps here?
    342     const u32 pregap_frames = (track_index == 0) ? (FRAMES_PER_SECOND * 2) : 0;
    343     if (pregap_frames > 0)
    344     {
    345       Index pregap_index = {};
    346       pregap_index.start_lba_on_disc = disc_lba;
    347       pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(pregap_frames));
    348       pregap_index.length = pregap_frames;
    349       pregap_index.track_number = track_num;
    350       pregap_index.index_number = 0;
    351       pregap_index.mode = track_mode;
    352       pregap_index.submode = CDImage::SubchannelMode::None;
    353       pregap_index.control.bits = control.bits;
    354       pregap_index.is_pregap = true;
    355       m_indices.push_back(pregap_index);
    356       disc_lba += pregap_frames;
    357     }
    358 
    359     // index 1, will be filled in next iteration
    360     if (track_num <= MAX_TRACK_NUMBER)
    361     {
    362       // add the track itself
    363       m_tracks.push_back(
    364         Track{track_num, disc_lba, static_cast<u32>(m_indices.size()), 0, track_mode, SubchannelMode::None, control});
    365 
    366       Index index1;
    367       index1.start_lba_on_disc = disc_lba;
    368       index1.start_lba_in_track = 0;
    369       index1.length = 0;
    370       index1.track_number = track_num;
    371       index1.index_number = 1;
    372       index1.file_index = 0;
    373       index1.file_sector_size = RAW_SECTOR_SIZE;
    374       index1.file_offset = static_cast<u64>(track_lba);
    375       index1.mode = track_mode;
    376       index1.submode = CDImage::SubchannelMode::None;
    377       index1.control.bits = control.bits;
    378       index1.is_pregap = false;
    379       m_indices.push_back(index1);
    380     }
    381   }
    382 
    383   if (m_tracks.empty())
    384   {
    385     ERROR_LOG("File '{}' contains no tracks", filename);
    386     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
    387     return false;
    388   }
    389 
    390   m_lba_count = disc_lba;
    391 
    392   DEV_LOG("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
    393   for (u32 i = 0; i < m_tracks.size(); i++)
    394   {
    395     DEV_LOG(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
    396             m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
    397   }
    398   for (u32 i = 0; i < m_indices.size(); i++)
    399   {
    400     DEV_LOG(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
    401             m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc, m_indices[i].length,
    402             m_indices[i].file_sector_size, m_indices[i].file_offset);
    403   }
    404 
    405   if (!DetermineReadMode(try_sptd))
    406   {
    407     ERROR_LOG("Could not determine read mode");
    408     Error::SetString(error, "Could not determine read mode");
    409     return false;
    410   }
    411 
    412   return Seek(1, Position{0, 0, 0});
    413 }
    414 
    415 bool CDImageDeviceWin32::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
    416 {
    417   if (index.file_sector_size == 0 || !m_has_valid_subcode)
    418     return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
    419 
    420   const LBA offset = static_cast<LBA>(index.file_offset) + lba_in_index;
    421   if (m_current_lba != offset && !ReadSectorToBuffer(offset))
    422     return false;
    423 
    424   if (m_scsi_read_mode == SCSIReadMode::SubQOnly)
    425   {
    426     // copy out subq
    427     std::memcpy(subq->data.data(), m_buffer.data() + RAW_SECTOR_SIZE, SUBCHANNEL_BYTES_PER_FRAME);
    428     return true;
    429   }
    430   else // if (m_scsi_read_mode == SCSIReadMode::Full || m_scsi_read_mode == SCSIReadMode::None)
    431   {
    432     // need to deinterleave the subcode
    433     u8 deinterleaved_subcode[ALL_SUBCODE_SIZE];
    434     DeinterleaveSubcode(m_buffer.data() + RAW_SECTOR_SIZE, deinterleaved_subcode);
    435 
    436     // P, Q, ...
    437     std::memcpy(subq->data.data(), deinterleaved_subcode + SUBCHANNEL_BYTES_PER_FRAME, SUBCHANNEL_BYTES_PER_FRAME);
    438     return true;
    439   }
    440 }
    441 
    442 bool CDImageDeviceWin32::HasNonStandardSubchannel() const
    443 {
    444   return m_has_valid_subcode;
    445 }
    446 
    447 bool CDImageDeviceWin32::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
    448 {
    449   if (index.file_sector_size == 0)
    450     return false;
    451 
    452   const LBA offset = static_cast<LBA>(index.file_offset) + lba_in_index;
    453   if (m_current_lba != offset && !ReadSectorToBuffer(offset))
    454     return false;
    455 
    456   std::memcpy(buffer, m_buffer.data(), RAW_SECTOR_SIZE);
    457   return true;
    458 }
    459 
    460 std::optional<u32> CDImageDeviceWin32::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer)
    461 {
    462   struct SPTDBuffer
    463   {
    464     SCSI_PASS_THROUGH_DIRECT cmd;
    465     u8 sense[20];
    466   };
    467   SPTDBuffer sptd = {};
    468   sptd.cmd.Length = sizeof(sptd.cmd);
    469   sptd.cmd.CdbLength = SCSI_CMD_LENGTH;
    470   sptd.cmd.SenseInfoLength = sizeof(sptd.sense);
    471   sptd.cmd.DataIn = out_buffer.empty() ? SCSI_IOCTL_DATA_UNSPECIFIED : SCSI_IOCTL_DATA_IN;
    472   sptd.cmd.DataTransferLength = static_cast<u32>(out_buffer.size());
    473   sptd.cmd.TimeOutValue = 10;
    474   sptd.cmd.SenseInfoOffset = OFFSETOF(SPTDBuffer, sense);
    475   sptd.cmd.DataBuffer = out_buffer.empty() ? nullptr : out_buffer.data();
    476   std::memcpy(sptd.cmd.Cdb, cmd, SCSI_CMD_LENGTH);
    477 
    478   DWORD bytes_returned;
    479   if (!DeviceIoControl(m_hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd),
    480                        &bytes_returned, nullptr))
    481   {
    482     ERROR_LOG("DeviceIoControl() for SCSI 0x{:02X} failed: {}", cmd[0], GetLastError());
    483     return std::nullopt;
    484   }
    485 
    486   if (sptd.cmd.ScsiStatus != 0)
    487   {
    488     ERROR_LOG("SCSI command 0x{:02X} failed: {}", cmd[0], sptd.cmd.ScsiStatus);
    489     return std::nullopt;
    490   }
    491 
    492   if (sptd.cmd.DataTransferLength != out_buffer.size())
    493     WARNING_LOG("Only read {} of {} bytes", sptd.cmd.DataTransferLength, out_buffer.size());
    494 
    495   return sptd.cmd.DataTransferLength;
    496 }
    497 
    498 std::optional<u32> CDImageDeviceWin32::DoSCSIRead(LBA lba, SCSIReadMode read_mode)
    499 {
    500   u8 cmd[SCSI_CMD_LENGTH];
    501   FillSCSIReadCommand(cmd, lba, read_mode);
    502 
    503   const u32 size = SCSIReadCommandOutputSize(read_mode);
    504   return DoSCSICommand(cmd, std::span<u8>(m_buffer.data(), size));
    505 }
    506 
    507 bool CDImageDeviceWin32::DoSetSpeed(u32 speed_multiplier)
    508 {
    509   u8 cmd[SCSI_CMD_LENGTH];
    510   FillSCSISetSpeedCommand(cmd, speed_multiplier);
    511   return DoSCSICommand(cmd, {}).has_value();
    512 }
    513 
    514 bool CDImageDeviceWin32::DoRawRead(LBA lba)
    515 {
    516   const DWORD expected_size = RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE;
    517 
    518   RAW_READ_INFO rri;
    519   rri.DiskOffset.QuadPart = static_cast<u64>(lba) * 2048;
    520   rri.SectorCount = 1;
    521   rri.TrackMode = RawWithSubCode;
    522 
    523   DWORD bytes_returned;
    524   if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_RAW_READ, &rri, sizeof(rri), m_buffer.data(),
    525                        static_cast<DWORD>(m_buffer.size()), &bytes_returned, nullptr))
    526   {
    527     ERROR_LOG("DeviceIoControl(IOCTL_CDROM_RAW_READ) for LBA {} failed: {:08X}", lba, GetLastError());
    528     return false;
    529   }
    530 
    531   if (bytes_returned != expected_size)
    532     WARNING_LOG("Only read {} of {} bytes", bytes_returned, expected_size);
    533 
    534   return true;
    535 }
    536 
    537 bool CDImageDeviceWin32::ReadSectorToBuffer(LBA lba)
    538 {
    539   if (m_scsi_read_mode != SCSIReadMode::None)
    540   {
    541     const std::optional<u32> size = DoSCSIRead(lba, m_scsi_read_mode);
    542     const u32 expected_size = SCSIReadCommandOutputSize(m_scsi_read_mode);
    543     if (size.value_or(0) != expected_size)
    544     {
    545       ERROR_LOG("Read of LBA {} failed: only got {} of {} bytes", lba, size.value(), expected_size);
    546       return false;
    547     }
    548   }
    549   else
    550   {
    551     if (!DoRawRead(lba))
    552       return false;
    553   }
    554 
    555   m_current_lba = lba;
    556   return true;
    557 }
    558 
    559 bool CDImageDeviceWin32::DetermineReadMode(bool try_sptd)
    560 {
    561   // Prefer raw reads if we can use them
    562   const Index& first_index = m_indices[m_tracks[0].first_index];
    563   const LBA track_1_lba = static_cast<LBA>(first_index.file_offset);
    564   const LBA track_1_subq_lba = first_index.start_lba_on_disc;
    565   const bool check_subcode = ShouldTryReadingSubcode();
    566 
    567   if (try_sptd)
    568   {
    569     std::optional<u32> transfer_size;
    570 
    571     DEV_LOG("Trying SCSI read with full subcode...");
    572     if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Full)).has_value())
    573     {
    574       if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Full,
    575                              track_1_subq_lba))
    576       {
    577         VERBOSE_LOG("Using SCSI reads with subcode");
    578         m_scsi_read_mode = SCSIReadMode::Full;
    579         m_has_valid_subcode = true;
    580         return true;
    581       }
    582     }
    583 
    584     WARNING_LOG("Full subcode failed, trying SCSI read with only subq...");
    585     if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::SubQOnly)).has_value())
    586     {
    587       if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::SubQOnly,
    588                              track_1_subq_lba))
    589       {
    590         VERBOSE_LOG("Using SCSI reads with subq only");
    591         m_scsi_read_mode = SCSIReadMode::SubQOnly;
    592         m_has_valid_subcode = true;
    593         return true;
    594       }
    595     }
    596 
    597     // As a last ditch effort, try SCSI without subcode.
    598     WARNING_LOG("Subq only failed failed, trying SCSI without subcode...");
    599     if ((transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Raw)).has_value())
    600     {
    601       if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Raw,
    602                              track_1_subq_lba))
    603       {
    604         WARNING_LOG("Using SCSI raw reads, libcrypt games will not run correctly");
    605         m_scsi_read_mode = SCSIReadMode::Raw;
    606         m_has_valid_subcode = false;
    607         return true;
    608       }
    609     }
    610   }
    611 
    612   WARNING_LOG("SCSI reads failed, trying raw read...");
    613   if (DoRawRead(track_1_lba))
    614   {
    615     // verify subcode
    616     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), SCSIReadCommandOutputSize(SCSIReadMode::Full)),
    617                            SCSIReadMode::Full, track_1_subq_lba))
    618     {
    619       VERBOSE_LOG("Using raw reads with full subcode");
    620       m_scsi_read_mode = SCSIReadMode::None;
    621       m_has_valid_subcode = true;
    622       return true;
    623     }
    624 
    625     WARNING_LOG("Using raw reads without subcode, libcrypt games will not run correctly");
    626     m_scsi_read_mode = SCSIReadMode::None;
    627     m_has_valid_subcode = false;
    628     return true;
    629   }
    630 
    631   ERROR_LOG("No read modes were successful, cannot use device.");
    632   return false;
    633 }
    634 
    635 std::unique_ptr<CDImage> CDImage::OpenDeviceImage(const char* filename, Error* error)
    636 {
    637   std::unique_ptr<CDImageDeviceWin32> image = std::make_unique<CDImageDeviceWin32>();
    638   if (!image->Open(filename, error))
    639     return {};
    640 
    641   return image;
    642 }
    643 
    644 std::vector<std::pair<std::string, std::string>> CDImage::GetDeviceList()
    645 {
    646   std::vector<std::pair<std::string, std::string>> ret;
    647 
    648   char buf[256];
    649   if (GetLogicalDriveStringsA(sizeof(buf), buf) != 0)
    650   {
    651     const char* ptr = buf;
    652     while (*ptr != '\0')
    653     {
    654       std::size_t len = std::strlen(ptr);
    655       const DWORD type = GetDriveTypeA(ptr);
    656       if (type != DRIVE_CDROM)
    657       {
    658         ptr += len + 1u;
    659         continue;
    660       }
    661 
    662       // Drop the trailing slash.
    663       const std::size_t append_len = (ptr[len - 1] == '\\') ? (len - 1) : len;
    664 
    665       std::string path;
    666       path.append("\\\\.\\");
    667       path.append(ptr, append_len);
    668 
    669       std::string name(ptr, append_len);
    670 
    671       ret.emplace_back(std::move(path), std::move(name));
    672 
    673       ptr += len + 1u;
    674     }
    675   }
    676 
    677   return ret;
    678 }
    679 
    680 bool CDImage::IsDeviceName(const char* filename)
    681 {
    682   return std::string_view(filename).starts_with("\\\\.\\");
    683 }
    684 
    685 #elif defined(__linux__) && !defined(__ANDROID__)
    686 
    687 #include <fcntl.h>
    688 #include <libudev.h>
    689 #include <linux/cdrom.h>
    690 #include <scsi/sg.h>
    691 #include <sys/ioctl.h>
    692 #include <unistd.h>
    693 
    694 namespace {
    695 
    696 class CDImageDeviceLinux : public CDImage
    697 {
    698 public:
    699   CDImageDeviceLinux();
    700   ~CDImageDeviceLinux() override;
    701 
    702   bool Open(const char* filename, Error* error);
    703 
    704   bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
    705   bool HasNonStandardSubchannel() const override;
    706 
    707 protected:
    708   bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
    709 
    710 private:
    711   // Raw reads use an offset of 00:02:00
    712   static constexpr LBA RAW_READ_OFFSET = 2 * FRAMES_PER_SECOND;
    713 
    714   bool ReadSectorToBuffer(LBA lba);
    715   bool DetermineReadMode(Error* error);
    716 
    717   std::optional<u32> DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer);
    718   std::optional<u32> DoSCSIRead(LBA lba, SCSIReadMode read_mode);
    719   bool DoRawRead(LBA lba);
    720   bool DoSetSpeed(u32 speed_multiplier);
    721 
    722   int m_fd = -1;
    723   LBA m_current_lba = ~static_cast<LBA>(0);
    724 
    725   SCSIReadMode m_scsi_read_mode = SCSIReadMode::None;
    726 
    727   std::array<u8, RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE> m_buffer;
    728 };
    729 
    730 } // namespace
    731 
    732 CDImageDeviceLinux::CDImageDeviceLinux() = default;
    733 
    734 CDImageDeviceLinux::~CDImageDeviceLinux()
    735 {
    736   if (m_fd >= 0)
    737     close(m_fd);
    738 }
    739 
    740 bool CDImageDeviceLinux::Open(const char* filename, Error* error)
    741 {
    742   m_filename = filename;
    743 
    744   m_fd = open(filename, O_RDONLY);
    745   if (m_fd < 0)
    746   {
    747     Error::SetErrno(error, "Failed to open device: ", errno);
    748     return false;
    749   }
    750 
    751   // Set it to 4x speed. A good balance between readahead and spinning up way too high.
    752   const int read_speed = 4;
    753   if (!DoSetSpeed(read_speed) && ioctl(m_fd, CDROM_SELECT_SPEED, &read_speed) != 0)
    754     WARNING_LOG("ioctl(CDROM_SELECT_SPEED) failed: {}", errno);
    755 
    756   // Read ToC
    757   cdrom_tochdr toc_hdr = {};
    758   if (ioctl(m_fd, CDROMREADTOCHDR, &toc_hdr) != 0)
    759   {
    760     Error::SetErrno(error, "ioctl(CDROMREADTOCHDR) failed: ", errno);
    761     return false;
    762   }
    763 
    764   DEV_LOG("FirstTrack={}, LastTrack={}", toc_hdr.cdth_trk0, toc_hdr.cdth_trk1);
    765   if (toc_hdr.cdth_trk1 < toc_hdr.cdth_trk0)
    766   {
    767     Error::SetStringFmt(error, "Last track {} is before first track {}", toc_hdr.cdth_trk1, toc_hdr.cdth_trk0);
    768     return false;
    769   }
    770 
    771   cdrom_tocentry toc_ent = {};
    772   toc_ent.cdte_format = CDROM_LBA;
    773 
    774   LBA disc_lba = 0;
    775   int last_track_lba = 0;
    776   const u32 num_tracks_to_check = (toc_hdr.cdth_trk1 - toc_hdr.cdth_trk0) + 1;
    777   for (u32 track_index = 0; track_index < num_tracks_to_check; track_index++)
    778   {
    779     const u32 track_num = toc_hdr.cdth_trk0 + track_index;
    780 
    781     toc_ent.cdte_track = static_cast<u8>(track_num);
    782     if (ioctl(m_fd, CDROMREADTOCENTRY, &toc_ent) < 0)
    783     {
    784       Error::SetErrno(error, "ioctl(CDROMREADTOCENTRY) failed: ", errno);
    785       return false;
    786     }
    787 
    788     DEV_LOG("  [{}]: Num={}, LBA={}", track_index, track_num, toc_ent.cdte_addr.lba);
    789 
    790     // fill in the previous track's length
    791     if (!m_tracks.empty())
    792     {
    793       if (track_num < m_tracks.back().track_number)
    794       {
    795         ERROR_LOG("Invalid TOC, track {} less than {}", track_num, m_tracks.back().track_number);
    796         return false;
    797       }
    798 
    799       const LBA previous_track_length = static_cast<LBA>(toc_ent.cdte_addr.lba - last_track_lba);
    800       m_tracks.back().length += previous_track_length;
    801       m_indices.back().length += previous_track_length;
    802       disc_lba += previous_track_length;
    803     }
    804 
    805     last_track_lba = toc_ent.cdte_addr.lba;
    806 
    807     // precompute subchannel q flags for the whole track
    808     SubChannelQ::Control control{};
    809     control.bits = toc_ent.cdte_adr | (toc_ent.cdte_ctrl << 4);
    810 
    811     const LBA track_lba = static_cast<LBA>(toc_ent.cdte_addr.lba);
    812     const TrackMode track_mode = control.data ? CDImage::TrackMode::Mode2Raw : CDImage::TrackMode::Audio;
    813 
    814     // TODO: How the hell do we handle pregaps here?
    815     const u32 pregap_frames = (track_index == 0) ? 150 : 0;
    816     if (pregap_frames > 0)
    817     {
    818       Index pregap_index = {};
    819       pregap_index.start_lba_on_disc = disc_lba;
    820       pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(pregap_frames));
    821       pregap_index.length = pregap_frames;
    822       pregap_index.track_number = track_num;
    823       pregap_index.index_number = 0;
    824       pregap_index.mode = track_mode;
    825       pregap_index.submode = CDImage::SubchannelMode::None;
    826       pregap_index.control.bits = control.bits;
    827       pregap_index.is_pregap = true;
    828       m_indices.push_back(pregap_index);
    829       disc_lba += pregap_frames;
    830     }
    831 
    832     // index 1, will be filled in next iteration
    833     if (track_num <= MAX_TRACK_NUMBER)
    834     {
    835       // add the track itself
    836       m_tracks.push_back(
    837         Track{track_num, disc_lba, static_cast<u32>(m_indices.size()), 0, track_mode, SubchannelMode::None, control});
    838 
    839       Index index1;
    840       index1.start_lba_on_disc = disc_lba;
    841       index1.start_lba_in_track = 0;
    842       index1.length = 0;
    843       index1.track_number = track_num;
    844       index1.index_number = 1;
    845       index1.file_index = 0;
    846       index1.file_sector_size = RAW_SECTOR_SIZE;
    847       index1.file_offset = static_cast<u64>(track_lba);
    848       index1.mode = track_mode;
    849       index1.submode = CDImage::SubchannelMode::None;
    850       index1.control.bits = control.bits;
    851       index1.is_pregap = false;
    852       m_indices.push_back(index1);
    853     }
    854   }
    855 
    856   if (m_tracks.empty())
    857   {
    858     ERROR_LOG("File '{}' contains no tracks", filename);
    859     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
    860     return false;
    861   }
    862 
    863   // Read lead-out.
    864   toc_ent.cdte_track = 0xAA;
    865   if (ioctl(m_fd, CDROMREADTOCENTRY, &toc_ent) < 0)
    866   {
    867     Error::SetErrno(error, "ioctl(CDROMREADTOCENTRY) for lead-out failed: ", errno);
    868     return false;
    869   }
    870   if (toc_ent.cdte_addr.lba < last_track_lba)
    871   {
    872     Error::SetStringFmt(error, "Lead-out LBA {} is less than last track {}", toc_ent.cdte_addr.lba, last_track_lba);
    873     return false;
    874   }
    875 
    876   // Fill last track length from lead-out.
    877   const LBA previous_track_length = static_cast<LBA>(toc_ent.cdte_addr.lba - last_track_lba);
    878   m_tracks.back().length += previous_track_length;
    879   m_indices.back().length += previous_track_length;
    880   disc_lba += previous_track_length;
    881 
    882   // And add the lead-out itself.
    883   AddLeadOutIndex();
    884 
    885   m_lba_count = disc_lba;
    886 
    887   DEV_LOG("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
    888   for (u32 i = 0; i < m_tracks.size(); i++)
    889   {
    890     DEV_LOG(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
    891             m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
    892   }
    893   for (u32 i = 0; i < m_indices.size(); i++)
    894   {
    895     DEV_LOG(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
    896             m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc, m_indices[i].length,
    897             m_indices[i].file_sector_size, m_indices[i].file_offset);
    898   }
    899 
    900   if (!DetermineReadMode(error))
    901     return false;
    902 
    903   return Seek(1, Position{0, 0, 0});
    904 }
    905 
    906 bool CDImageDeviceLinux::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
    907 {
    908   if (index.file_sector_size == 0 || m_scsi_read_mode < SCSIReadMode::Full)
    909     return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
    910 
    911   const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
    912   if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
    913     return false;
    914 
    915   if (m_scsi_read_mode == SCSIReadMode::SubQOnly)
    916   {
    917     // copy out subq
    918     std::memcpy(subq->data.data(), m_buffer.data() + RAW_SECTOR_SIZE, SUBCHANNEL_BYTES_PER_FRAME);
    919     return true;
    920   }
    921   else // if (m_scsi_read_mode == SCSIReadMode::Full)
    922   {
    923     // need to deinterleave the subcode
    924     u8 deinterleaved_subcode[ALL_SUBCODE_SIZE];
    925     DeinterleaveSubcode(m_buffer.data() + RAW_SECTOR_SIZE, deinterleaved_subcode);
    926 
    927     // P, Q, ...
    928     std::memcpy(subq->data.data(), deinterleaved_subcode + SUBCHANNEL_BYTES_PER_FRAME, SUBCHANNEL_BYTES_PER_FRAME);
    929     return true;
    930   }
    931 }
    932 
    933 bool CDImageDeviceLinux::HasNonStandardSubchannel() const
    934 {
    935   // Can only read subchannel through SPTD.
    936   return m_scsi_read_mode >= SCSIReadMode::Full;
    937 }
    938 
    939 bool CDImageDeviceLinux::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
    940 {
    941   if (index.file_sector_size == 0)
    942     return false;
    943 
    944   const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
    945   if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
    946     return false;
    947 
    948   std::memcpy(buffer, m_buffer.data(), RAW_SECTOR_SIZE);
    949   return true;
    950 }
    951 
    952 std::optional<u32> CDImageDeviceLinux::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer)
    953 {
    954   sg_io_hdr_t hdr;
    955   std::memset(&hdr, 0, sizeof(hdr));
    956   hdr.cmd_len = SCSI_CMD_LENGTH;
    957   hdr.interface_id = 'S';
    958   hdr.dxfer_direction = out_buffer.empty() ? SG_DXFER_NONE : SG_DXFER_FROM_DEV;
    959   hdr.mx_sb_len = 0;
    960   hdr.dxfer_len = static_cast<u32>(out_buffer.size());
    961   hdr.dxferp = out_buffer.empty() ? nullptr : out_buffer.data();
    962   hdr.cmdp = cmd;
    963   hdr.timeout = 10000; // milliseconds
    964 
    965   if (ioctl(m_fd, SG_IO, &hdr) != 0)
    966   {
    967     ERROR_LOG("ioctl(SG_IO) for command {:02X} failed: {}", cmd[0], errno);
    968     return std::nullopt;
    969   }
    970   else if (hdr.status != 0)
    971   {
    972     ERROR_LOG("SCSI command {:02X} failed with status {}", cmd[0], hdr.status);
    973     return std::nullopt;
    974   }
    975 
    976   return hdr.dxfer_len;
    977 }
    978 
    979 std::optional<u32> CDImageDeviceLinux::DoSCSIRead(LBA lba, SCSIReadMode read_mode)
    980 {
    981   u8 cmd[SCSI_CMD_LENGTH];
    982   FillSCSIReadCommand(cmd, lba, read_mode);
    983 
    984   const u32 size = SCSIReadCommandOutputSize(read_mode);
    985   return DoSCSICommand(cmd, std::span<u8>(m_buffer.data(), size));
    986 }
    987 
    988 bool CDImageDeviceLinux::DoSetSpeed(u32 speed_multiplier)
    989 {
    990   u8 cmd[SCSI_CMD_LENGTH];
    991   FillSCSISetSpeedCommand(cmd, speed_multiplier);
    992   return DoSCSICommand(cmd, {}).has_value();
    993 }
    994 
    995 bool CDImageDeviceLinux::DoRawRead(LBA lba)
    996 {
    997   const Position msf = Position::FromLBA(lba + RAW_READ_OFFSET);
    998   std::memcpy(m_buffer.data(), &msf, sizeof(msf));
    999   if (ioctl(m_fd, CDROMREADRAW, m_buffer.data()) != 0)
   1000   {
   1001     ERROR_LOG("CDROMREADRAW for LBA {} (MSF {}:{}:{}) failed: {}", lba, msf.minute, msf.second, msf.frame, errno);
   1002     return false;
   1003   }
   1004 
   1005   return true;
   1006 }
   1007 
   1008 bool CDImageDeviceLinux::ReadSectorToBuffer(LBA lba)
   1009 {
   1010   if (m_scsi_read_mode != SCSIReadMode::None)
   1011   {
   1012     const std::optional<u32> size = DoSCSIRead(lba, m_scsi_read_mode);
   1013     const u32 expected_size = SCSIReadCommandOutputSize(m_scsi_read_mode);
   1014     if (size.value_or(0) != expected_size)
   1015     {
   1016       ERROR_LOG("Read of LBA {} failed: only got {} of {} bytes", lba, size.value(), expected_size);
   1017       return false;
   1018     }
   1019   }
   1020   else
   1021   {
   1022     if (!DoRawRead(lba))
   1023       return false;
   1024   }
   1025 
   1026   m_current_lba = lba;
   1027   return true;
   1028 }
   1029 
   1030 bool CDImageDeviceLinux::DetermineReadMode(Error* error)
   1031 {
   1032   const LBA track_1_lba = static_cast<LBA>(m_indices[m_tracks[0].first_index].file_offset);
   1033   const LBA track_1_subq_lba = track_1_lba + FRAMES_PER_SECOND * 2;
   1034   const bool check_subcode = ShouldTryReadingSubcode();
   1035   std::optional<u32> transfer_size;
   1036 
   1037   DEV_LOG("Trying SCSI read with full subcode...");
   1038   if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Full)).has_value())
   1039   {
   1040     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Full, track_1_subq_lba))
   1041     {
   1042       VERBOSE_LOG("Using SCSI reads with subcode");
   1043       m_scsi_read_mode = SCSIReadMode::Full;
   1044       return true;
   1045     }
   1046   }
   1047 
   1048   WARNING_LOG("Full subcode failed, trying SCSI read with only subq...");
   1049   if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::SubQOnly)).has_value())
   1050   {
   1051     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::SubQOnly,
   1052                            track_1_subq_lba))
   1053     {
   1054       VERBOSE_LOG("Using SCSI reads with subq only");
   1055       m_scsi_read_mode = SCSIReadMode::SubQOnly;
   1056       return true;
   1057     }
   1058   }
   1059 
   1060   WARNING_LOG("SCSI subcode reads failed, trying CDROMREADRAW...");
   1061   if (DoRawRead(track_1_lba))
   1062   {
   1063     WARNING_LOG("Using CDROMREADRAW, libcrypt games will not run correctly");
   1064     m_scsi_read_mode = SCSIReadMode::None;
   1065     return true;
   1066   }
   1067 
   1068   // As a last ditch effort, try SCSI without subcode.
   1069   WARNING_LOG("CDROMREADRAW failed, trying SCSI without subcode...");
   1070   if ((transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Raw)).has_value())
   1071   {
   1072     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Raw, track_1_subq_lba))
   1073     {
   1074       WARNING_LOG("Using SCSI raw reads, libcrypt games will not run correctly");
   1075       m_scsi_read_mode = SCSIReadMode::Raw;
   1076       return true;
   1077     }
   1078   }
   1079 
   1080   ERROR_LOG("No read modes were successful, cannot use device.");
   1081   return false;
   1082 }
   1083 
   1084 std::unique_ptr<CDImage> CDImage::OpenDeviceImage(const char* filename, Error* error)
   1085 {
   1086   std::unique_ptr<CDImageDeviceLinux> image = std::make_unique<CDImageDeviceLinux>();
   1087   if (!image->Open(filename, error))
   1088     return {};
   1089 
   1090   return image;
   1091 }
   1092 
   1093 std::vector<std::pair<std::string, std::string>> CDImage::GetDeviceList()
   1094 {
   1095   std::vector<std::pair<std::string, std::string>> ret;
   1096 
   1097   // borrowed from PCSX2
   1098   udev* udev_context = udev_new();
   1099   if (udev_context)
   1100   {
   1101     udev_enumerate* enumerate = udev_enumerate_new(udev_context);
   1102     if (enumerate)
   1103     {
   1104       udev_enumerate_add_match_subsystem(enumerate, "block");
   1105       udev_enumerate_add_match_property(enumerate, "ID_CDROM", "1");
   1106       udev_enumerate_scan_devices(enumerate);
   1107       udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate);
   1108 
   1109       udev_list_entry* dev_list_entry;
   1110       udev_list_entry_foreach(dev_list_entry, devices)
   1111       {
   1112         const char* path = udev_list_entry_get_name(dev_list_entry);
   1113         udev_device* device = udev_device_new_from_syspath(udev_context, path);
   1114         const char* devnode = udev_device_get_devnode(device);
   1115         if (devnode)
   1116           ret.emplace_back(devnode, devnode);
   1117         udev_device_unref(device);
   1118       }
   1119       udev_enumerate_unref(enumerate);
   1120     }
   1121     udev_unref(udev_context);
   1122   }
   1123 
   1124   return ret;
   1125 }
   1126 
   1127 bool CDImage::IsDeviceName(const char* filename)
   1128 {
   1129   if (!std::string_view(filename).starts_with("/dev"))
   1130     return false;
   1131 
   1132   const int fd = open(filename, O_RDONLY | O_NONBLOCK);
   1133   if (fd < 0)
   1134     return false;
   1135 
   1136   const bool is_cdrom = (ioctl(fd, CDROM_GET_CAPABILITY, 0) >= 0);
   1137   close(fd);
   1138   return is_cdrom;
   1139 }
   1140 
   1141 #elif defined(__APPLE__)
   1142 
   1143 #include <CoreFoundation/CoreFoundation.h>
   1144 #include <IOKit/IOBSD.h>
   1145 #include <IOKit/IOKitLib.h>
   1146 #include <IOKit/storage/IOCDMedia.h>
   1147 #include <IOKit/storage/IOCDMediaBSDClient.h>
   1148 #include <IOKit/storage/IODVDMedia.h>
   1149 #include <IOKit/storage/IODVDMediaBSDClient.h>
   1150 #include <IOKit/storage/IOMedia.h>
   1151 #include <fcntl.h>
   1152 #include <sys/ioctl.h>
   1153 #include <unistd.h>
   1154 
   1155 namespace {
   1156 
   1157 class CDImageDeviceMacOS : public CDImage
   1158 {
   1159 public:
   1160   CDImageDeviceMacOS();
   1161   ~CDImageDeviceMacOS() override;
   1162 
   1163   bool Open(const char* filename, Error* error);
   1164 
   1165   bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
   1166   bool HasNonStandardSubchannel() const override;
   1167 
   1168 protected:
   1169   bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
   1170 
   1171 private:
   1172   // Raw reads should subtract 00:02:00.
   1173   static constexpr u32 RAW_READ_OFFSET = 2 * FRAMES_PER_SECOND;
   1174 
   1175   bool ReadSectorToBuffer(LBA lba);
   1176   bool DetermineReadMode(Error* error);
   1177 
   1178   bool DoSetSpeed(u32 speed_multiplier);
   1179 
   1180   int m_fd = -1;
   1181   LBA m_current_lba = ~static_cast<LBA>(0);
   1182 
   1183   SCSIReadMode m_read_mode = SCSIReadMode::None;
   1184 
   1185   std::array<u8, RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE> m_buffer;
   1186 };
   1187 
   1188 } // namespace
   1189 
   1190 static io_service_t GetDeviceMediaService(std::string_view devname)
   1191 {
   1192   std::string_view filename = Path::GetFileName(devname);
   1193   if (filename.starts_with("r"))
   1194     filename = filename.substr(1);
   1195   if (filename.empty())
   1196     return 0;
   1197 
   1198   TinyString rdevname(filename);
   1199   io_iterator_t iterator;
   1200   kern_return_t ret = IOServiceGetMatchingServices(0, IOBSDNameMatching(0, 0, rdevname.c_str()), &iterator);
   1201   if (ret != KERN_SUCCESS)
   1202   {
   1203     ERROR_LOG("IOServiceGetMatchingService() returned {}", ret);
   1204     return 0;
   1205   }
   1206 
   1207   // search up the heirarchy
   1208   for (;;)
   1209   {
   1210     io_service_t service = IOIteratorNext(iterator);
   1211     IOObjectRelease(iterator);
   1212 
   1213     if (IOObjectConformsTo(service, kIOCDMediaClass) || IOObjectConformsTo(service, kIODVDMediaClass))
   1214     {
   1215       return service;
   1216     }
   1217 
   1218     ret = IORegistryEntryGetParentIterator(service, kIOServicePlane, &iterator);
   1219     IOObjectRelease(service);
   1220     if (ret != KERN_SUCCESS)
   1221       return 0;
   1222   }
   1223 }
   1224 
   1225 CDImageDeviceMacOS::CDImageDeviceMacOS() = default;
   1226 
   1227 CDImageDeviceMacOS::~CDImageDeviceMacOS()
   1228 {
   1229   if (m_fd >= 0)
   1230     close(m_fd);
   1231 }
   1232 
   1233 bool CDImageDeviceMacOS::Open(const char* filename, Error* error)
   1234 {
   1235   m_filename = filename;
   1236 
   1237   m_fd = open(filename, O_RDONLY);
   1238   if (m_fd < 0)
   1239   {
   1240     Error::SetErrno(error, "Failed to open device: ", errno);
   1241     return false;
   1242   }
   1243 
   1244   constexpr int read_speed = 8;
   1245   DoSetSpeed(read_speed);
   1246 
   1247   // Read ToC
   1248   static constexpr u32 TOC_BUFFER_SIZE = 2048;
   1249   std::unique_ptr<CDTOC, void (*)(void*)> toc(static_cast<CDTOC*>(std::malloc(TOC_BUFFER_SIZE)), std::free);
   1250   dk_cd_read_toc_t toc_read = {};
   1251   toc_read.format = kCDTOCFormatTOC;
   1252   toc_read.formatAsTime = true;
   1253   toc_read.buffer = toc.get();
   1254   toc_read.bufferLength = TOC_BUFFER_SIZE;
   1255   if (ioctl(m_fd, DKIOCCDREADTOC, &toc_read) != 0)
   1256   {
   1257     Error::SetErrno(error, "ioctl(DKIOCCDREADTOC) failed: ", errno);
   1258     return false;
   1259   }
   1260 
   1261   const u32 desc_count = CDTOCGetDescriptorCount(toc.get());
   1262   DEV_LOG("sessionFirst={}, sessionLast={}, count={}", toc->sessionFirst, toc->sessionLast, desc_count);
   1263   if (toc->sessionLast < toc->sessionFirst)
   1264   {
   1265     Error::SetStringFmt(error, "Last track {} is before first track {}", toc->sessionLast, toc->sessionFirst);
   1266     return false;
   1267   }
   1268 
   1269   // find track range
   1270   u32 leadout_index = desc_count;
   1271   u32 first_track = MAX_TRACK_NUMBER;
   1272   u32 last_track = 1;
   1273   for (u32 i = 0; i < desc_count; i++)
   1274   {
   1275     const CDTOCDescriptor& desc = toc->descriptors[i];
   1276     DEV_LOG("  [{}]: Num={}, Point=0x{:02X} ADR={} MSF={}:{}:{}", i, desc.tno, desc.point, desc.adr, desc.p.minute,
   1277             desc.p.second, desc.p.frame);
   1278 
   1279     // Why does MacOS use 0xA2 instead of 0xAA for leadout??
   1280     if (desc.point == 0xA2)
   1281     {
   1282       leadout_index = i;
   1283     }
   1284     else if (desc.point >= 1 && desc.point <= MAX_TRACK_NUMBER)
   1285     {
   1286       first_track = std::min<u32>(first_track, desc.point);
   1287       last_track = std::max<u32>(last_track, desc.point);
   1288     }
   1289   }
   1290   if (leadout_index == desc_count)
   1291   {
   1292     Error::SetStringView(error, "Lead-out track not found.");
   1293     return false;
   1294   }
   1295 
   1296   LBA disc_lba = 0;
   1297   LBA last_track_lba = 0;
   1298   for (u32 track_num = first_track; track_num <= last_track; track_num++)
   1299   {
   1300     u32 toc_index;
   1301     for (toc_index = 0; toc_index < desc_count; toc_index++)
   1302     {
   1303       const CDTOCDescriptor& desc = toc->descriptors[toc_index];
   1304       if (desc.point == track_num)
   1305         break;
   1306     }
   1307     if (toc_index == desc_count)
   1308     {
   1309       Error::SetStringFmt(error, "Track {} not found in TOC", track_num);
   1310       return false;
   1311     }
   1312 
   1313     const CDTOCDescriptor& desc = toc->descriptors[toc_index];
   1314     const u32 track_lba = Position{desc.p.minute, desc.p.second, desc.p.frame}.ToLBA();
   1315 
   1316     // fill in the previous track's length
   1317     if (!m_tracks.empty())
   1318     {
   1319       const LBA previous_track_length = track_lba - last_track_lba;
   1320       m_tracks.back().length += previous_track_length;
   1321       m_indices.back().length += previous_track_length;
   1322       disc_lba += previous_track_length;
   1323     }
   1324 
   1325     last_track_lba = track_lba;
   1326 
   1327     // precompute subchannel q flags for the whole track
   1328     SubChannelQ::Control control{};
   1329     control.bits = desc.adr | (desc.control << 4);
   1330 
   1331     const TrackMode track_mode = control.data ? CDImage::TrackMode::Mode2Raw : CDImage::TrackMode::Audio;
   1332 
   1333     // TODO: How the hell do we handle pregaps here?
   1334     const u32 pregap_frames = (track_num == 1) ? 150 : 0;
   1335     if (pregap_frames > 0)
   1336     {
   1337       Index pregap_index = {};
   1338       pregap_index.start_lba_on_disc = disc_lba;
   1339       pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(pregap_frames));
   1340       pregap_index.length = pregap_frames;
   1341       pregap_index.track_number = track_num;
   1342       pregap_index.index_number = 0;
   1343       pregap_index.mode = track_mode;
   1344       pregap_index.submode = CDImage::SubchannelMode::None;
   1345       pregap_index.control.bits = control.bits;
   1346       pregap_index.is_pregap = true;
   1347       m_indices.push_back(pregap_index);
   1348       disc_lba += pregap_frames;
   1349     }
   1350 
   1351     // index 1, will be filled in next iteration
   1352     if (track_num <= MAX_TRACK_NUMBER)
   1353     {
   1354       // add the track itself
   1355       m_tracks.push_back(
   1356         Track{track_num, disc_lba, static_cast<u32>(m_indices.size()), 0, track_mode, SubchannelMode::None, control});
   1357 
   1358       Index index1;
   1359       index1.start_lba_on_disc = disc_lba;
   1360       index1.start_lba_in_track = 0;
   1361       index1.length = 0;
   1362       index1.track_number = track_num;
   1363       index1.index_number = 1;
   1364       index1.file_index = 0;
   1365       index1.file_sector_size = RAW_SECTOR_SIZE;
   1366       index1.file_offset = static_cast<u64>(track_lba);
   1367       index1.mode = track_mode;
   1368       index1.submode = CDImage::SubchannelMode::None;
   1369       index1.control.bits = control.bits;
   1370       index1.is_pregap = false;
   1371       m_indices.push_back(index1);
   1372     }
   1373   }
   1374 
   1375   if (m_tracks.empty())
   1376   {
   1377     ERROR_LOG("File '{}' contains no tracks", filename);
   1378     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
   1379     return false;
   1380   }
   1381 
   1382   // Fill last track length from lead-out.
   1383   const CDTOCDescriptor& leadout_desc = toc->descriptors[leadout_index];
   1384   const u32 leadout_lba = Position{leadout_desc.p.minute, leadout_desc.p.second, leadout_desc.p.frame}.ToLBA();
   1385   const LBA previous_track_length = static_cast<LBA>(leadout_lba - last_track_lba);
   1386   m_tracks.back().length += previous_track_length;
   1387   m_indices.back().length += previous_track_length;
   1388   disc_lba += previous_track_length;
   1389 
   1390   // And add the lead-out itself.
   1391   AddLeadOutIndex();
   1392 
   1393   m_lba_count = disc_lba;
   1394 
   1395   DEV_LOG("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
   1396   for (u32 i = 0; i < m_tracks.size(); i++)
   1397   {
   1398     DEV_LOG(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
   1399             m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
   1400   }
   1401   for (u32 i = 0; i < m_indices.size(); i++)
   1402   {
   1403     DEV_LOG(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
   1404             m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc, m_indices[i].length,
   1405             m_indices[i].file_sector_size, m_indices[i].file_offset);
   1406   }
   1407 
   1408   if (!DetermineReadMode(error))
   1409     return false;
   1410 
   1411   return Seek(1, Position{0, 0, 0});
   1412 }
   1413 
   1414 bool CDImageDeviceMacOS::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
   1415 {
   1416   if (index.file_sector_size == 0 || m_read_mode < SCSIReadMode::Full)
   1417     return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
   1418 
   1419   const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
   1420   if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
   1421     return false;
   1422 
   1423   if (m_read_mode == SCSIReadMode::SubQOnly)
   1424   {
   1425     // copy out subq
   1426     std::memcpy(subq->data.data(), m_buffer.data() + RAW_SECTOR_SIZE, SUBCHANNEL_BYTES_PER_FRAME);
   1427     return true;
   1428   }
   1429   else // if (m_scsi_read_mode == SCSIReadMode::Full)
   1430   {
   1431     // need to deinterleave the subcode
   1432     u8 deinterleaved_subcode[ALL_SUBCODE_SIZE];
   1433     DeinterleaveSubcode(m_buffer.data() + RAW_SECTOR_SIZE, deinterleaved_subcode);
   1434 
   1435     // P, Q, ...
   1436     std::memcpy(subq->data.data(), deinterleaved_subcode + SUBCHANNEL_BYTES_PER_FRAME, SUBCHANNEL_BYTES_PER_FRAME);
   1437     return true;
   1438   }
   1439 }
   1440 
   1441 bool CDImageDeviceMacOS::HasNonStandardSubchannel() const
   1442 {
   1443   // Can only read subchannel through SPTD.
   1444   return m_read_mode >= SCSIReadMode::Full;
   1445 }
   1446 
   1447 bool CDImageDeviceMacOS::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
   1448 {
   1449   if (index.file_sector_size == 0)
   1450     return false;
   1451 
   1452   const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
   1453   if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
   1454     return false;
   1455 
   1456   std::memcpy(buffer, m_buffer.data(), RAW_SECTOR_SIZE);
   1457   return true;
   1458 }
   1459 
   1460 bool CDImageDeviceMacOS::DoSetSpeed(u32 speed_multiplier)
   1461 {
   1462   const u16 speed = static_cast<u16>((FRAMES_PER_SECOND * RAW_SECTOR_SIZE * speed_multiplier) / 1024);
   1463   if (ioctl(m_fd, DKIOCCDSETSPEED, &speed) != 0)
   1464   {
   1465     ERROR_LOG("DKIOCCDSETSPEED for speed {} failed: {}", speed, errno);
   1466     return false;
   1467   }
   1468 
   1469   return true;
   1470 }
   1471 
   1472 bool CDImageDeviceMacOS::ReadSectorToBuffer(LBA lba)
   1473 {
   1474   if (lba < RAW_READ_OFFSET)
   1475   {
   1476     ERROR_LOG("Out of bounds LBA {}", lba);
   1477     return false;
   1478   }
   1479 
   1480   const u32 sector_size =
   1481     RAW_SECTOR_SIZE + ((m_read_mode == SCSIReadMode::Full) ?
   1482                          ALL_SUBCODE_SIZE :
   1483                          ((m_read_mode == SCSIReadMode::SubQOnly) ? SUBCHANNEL_BYTES_PER_FRAME : 0));
   1484   dk_cd_read_t desc = {};
   1485   desc.sectorArea =
   1486     kCDSectorAreaSync | kCDSectorAreaHeader | kCDSectorAreaSubHeader | kCDSectorAreaUser | kCDSectorAreaAuxiliary |
   1487     ((m_read_mode == SCSIReadMode::Full) ? kCDSectorAreaSubChannel :
   1488                                            ((m_read_mode == SCSIReadMode::SubQOnly) ? kCDSectorAreaSubChannelQ : 0));
   1489   desc.sectorType = kCDSectorTypeUnknown;
   1490   desc.offset = static_cast<u64>(lba - RAW_READ_OFFSET) * sector_size;
   1491   desc.buffer = m_buffer.data();
   1492   desc.bufferLength = sector_size;
   1493   if (ioctl(m_fd, DKIOCCDREAD, &desc) != 0)
   1494   {
   1495     const Position msf = Position::FromLBA(lba);
   1496     ERROR_LOG("DKIOCCDREAD for LBA {} (MSF {}:{}:{}) failed: {}", lba, msf.minute, msf.second, msf.frame, errno);
   1497     return false;
   1498   }
   1499 
   1500   m_current_lba = lba;
   1501   return true;
   1502 }
   1503 
   1504 bool CDImageDeviceMacOS::DetermineReadMode(Error* error)
   1505 {
   1506   const LBA track_1_lba = static_cast<LBA>(m_indices[m_tracks[0].first_index].file_offset);
   1507   const bool check_subcode = ShouldTryReadingSubcode();
   1508 
   1509   DEV_LOG("Trying read with full subcode...");
   1510   m_read_mode = SCSIReadMode::Full;
   1511   m_current_lba = m_lba_count;
   1512   if (check_subcode && ReadSectorToBuffer(track_1_lba))
   1513   {
   1514     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE), SCSIReadMode::Full,
   1515                            track_1_lba))
   1516     {
   1517       VERBOSE_LOG("Using reads with subcode");
   1518       return true;
   1519     }
   1520   }
   1521 
   1522 #if 0
   1523   // This seems to lock up on my drive... :/
   1524   Log_WarningPrint("Full subcode failed, trying SCSI read with only subq...");
   1525   m_read_mode = SCSIReadMode::SubQOnly;
   1526   m_current_lba = m_lba_count;
   1527   if (check_subcode && ReadSectorToBuffer(track_1_lba))
   1528   {
   1529     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), RAW_SECTOR_SIZE + SUBCHANNEL_BYTES_PER_FRAME),
   1530                            SCSIReadMode::SubQOnly, track_1_lba))
   1531     {
   1532       Log_VerbosePrint("Using reads with subq only");
   1533       return true;
   1534     }
   1535   }
   1536 #endif
   1537 
   1538   WARNING_LOG("SCSI reads failed, trying without subcode...");
   1539   m_read_mode = SCSIReadMode::Raw;
   1540   m_current_lba = m_lba_count;
   1541   if (ReadSectorToBuffer(track_1_lba))
   1542   {
   1543     WARNING_LOG("Using non-subcode reads, libcrypt games will not run correctly");
   1544     return true;
   1545   }
   1546 
   1547   ERROR_LOG("No read modes were successful, cannot use device.");
   1548   return false;
   1549 }
   1550 
   1551 std::unique_ptr<CDImage> CDImage::OpenDeviceImage(const char* filename, Error* error)
   1552 {
   1553   std::unique_ptr<CDImageDeviceMacOS> image = std::make_unique<CDImageDeviceMacOS>();
   1554   if (!image->Open(filename, error))
   1555     return {};
   1556 
   1557   return image;
   1558 }
   1559 
   1560 std::vector<std::pair<std::string, std::string>> CDImage::GetDeviceList()
   1561 {
   1562   std::vector<std::pair<std::string, std::string>> ret;
   1563 
   1564   // borrowed from PCSX2
   1565   auto append_list = [&ret](const char* classes_name) {
   1566     CFMutableDictionaryRef classes = IOServiceMatching(kIOCDMediaClass);
   1567     if (!classes)
   1568       return;
   1569 
   1570     CFDictionarySetValue(classes, CFSTR(kIOMediaEjectableKey), kCFBooleanTrue);
   1571 
   1572     io_iterator_t iter;
   1573     kern_return_t result = IOServiceGetMatchingServices(0, classes, &iter);
   1574     if (result != KERN_SUCCESS)
   1575     {
   1576       CFRelease(classes);
   1577       return;
   1578     }
   1579 
   1580     while (io_object_t media = IOIteratorNext(iter))
   1581     {
   1582       CFTypeRef path = IORegistryEntryCreateCFProperty(media, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
   1583       if (path)
   1584       {
   1585         char buf[PATH_MAX];
   1586         if (CFStringGetCString((CFStringRef)path, buf, sizeof(buf), kCFStringEncodingUTF8))
   1587         {
   1588           if (std::none_of(ret.begin(), ret.end(), [&buf](const auto& it) { return it.second == buf; }))
   1589             ret.emplace_back(fmt::format("/dev/r{}", buf), buf);
   1590         }
   1591         CFRelease(path);
   1592         IOObjectRelease(media);
   1593       }
   1594       IOObjectRelease(media);
   1595     }
   1596 
   1597     IOObjectRelease(iter);
   1598   };
   1599 
   1600   append_list(kIOCDMediaClass);
   1601   append_list(kIODVDMediaClass);
   1602 
   1603   return ret;
   1604 }
   1605 
   1606 bool CDImage::IsDeviceName(const char* filename)
   1607 {
   1608   if (!std::string_view(filename).starts_with("/dev"))
   1609     return false;
   1610 
   1611   io_service_t service = GetDeviceMediaService(filename);
   1612   const bool valid = (service != 0);
   1613   if (valid)
   1614     IOObjectRelease(service);
   1615 
   1616   return valid;
   1617 }
   1618 
   1619 #else
   1620 
   1621 std::unique_ptr<CDImage> CDImage::OpenDeviceImage(const char* filename, Error* error)
   1622 {
   1623   return {};
   1624 }
   1625 
   1626 std::vector<std::pair<std::string, std::string>> CDImage::GetDeviceList()
   1627 {
   1628   return {};
   1629 }
   1630 
   1631 bool CDImage::IsDeviceName(const char* filename)
   1632 {
   1633   return false;
   1634 }
   1635 
   1636 #endif