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