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