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