Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image.cpp
4802 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "cd_image.h"
5
6
#include "common/assert.h"
7
#include "common/bitutils.h"
8
#include "common/error.h"
9
#include "common/file_system.h"
10
#include "common/log.h"
11
#include "common/path.h"
12
#include "common/string_util.h"
13
14
#include <array>
15
16
LOG_CHANNEL(CDImage);
17
18
CDImage::CDImage() = default;
19
20
CDImage::~CDImage() = default;
21
22
u32 CDImage::GetBytesPerSector(TrackMode mode)
23
{
24
static constexpr std::array<u32, 8> sizes = {{2352, 2048, 2352, 2336, 2048, 2324, 2332, 2352}};
25
return sizes[static_cast<u32>(mode)];
26
}
27
28
// Adapted from
29
// https://github.com/saramibreak/DiscImageCreator/blob/5a8fe21730872d67991211f1319c87f0780f2d0f/DiscImageCreator/convert.cpp
30
void CDImage::DeinterleaveSubcode(const u8* subcode_in, u8* subcode_out)
31
{
32
std::memset(subcode_out, 0, ALL_SUBCODE_SIZE);
33
34
u32 row = 0;
35
for (u32 bitNum = 0; bitNum < 8; bitNum++)
36
{
37
for (u32 nColumn = 0; nColumn < ALL_SUBCODE_SIZE; row++)
38
{
39
u32 mask = 0x80;
40
for (int nShift = 0; nShift < 8; nShift++, nColumn++)
41
{
42
const s32 n = static_cast<s32>(nShift) - static_cast<s32>(bitNum);
43
if (n > 0)
44
subcode_out[row] |= static_cast<u8>((subcode_in[nColumn] >> n) & mask);
45
else
46
subcode_out[row] |= static_cast<u8>((subcode_in[nColumn] << std::abs(n)) & mask);
47
mask >>= 1;
48
}
49
}
50
}
51
}
52
53
std::unique_ptr<CDImage> CDImage::Open(const char* path, bool allow_patches, Error* error)
54
{
55
// Annoying handling because of storage access framework.
56
#ifdef __ANDROID__
57
const std::string path_display_name = FileSystem::GetDisplayNameFromPath(path);
58
const std::string_view extension = Path::GetExtension(path_display_name);
59
#else
60
const std::string_view extension = Path::GetExtension(path);
61
#endif
62
63
std::unique_ptr<CDImage> image;
64
if (extension.empty())
65
{
66
// Device filenames on Linux don't have extensions.
67
if (IsDeviceName(path))
68
{
69
image = OpenDeviceImage(path, error);
70
}
71
else
72
{
73
Error::SetStringFmt(error, "Invalid filename: '{}'", Path::GetFileName(path));
74
return nullptr;
75
}
76
}
77
else if (StringUtil::EqualNoCase(extension, "cue"))
78
{
79
image = OpenCueSheetImage(path, error);
80
}
81
else if (StringUtil::EqualNoCase(extension, "bin") || StringUtil::EqualNoCase(extension, "img") ||
82
StringUtil::EqualNoCase(extension, "iso") || StringUtil::EqualNoCase(extension, "ecm"))
83
{
84
image = OpenBinImage(path, error);
85
}
86
else if (StringUtil::EqualNoCase(extension, "chd"))
87
{
88
image = OpenCHDImage(path, error);
89
}
90
else if (StringUtil::EqualNoCase(extension, "mds"))
91
{
92
image = OpenMdsImage(path, error);
93
}
94
else if (StringUtil::EqualNoCase(extension, "pbp"))
95
{
96
image = OpenPBPImage(path, error);
97
}
98
else if (StringUtil::EqualNoCase(extension, "m3u"))
99
{
100
// skip applying patches to the main path, which isn't a real disc
101
image = OpenM3uImage(path, allow_patches, error);
102
allow_patches = false;
103
}
104
else if (IsDeviceName(path))
105
{
106
image = OpenDeviceImage(path, error);
107
}
108
else
109
{
110
Error::SetStringFmt(error, "Unknown extension '{}' from filename '{}'", extension, Path::GetFileName(path));
111
return nullptr;
112
}
113
114
if (allow_patches)
115
{
116
#ifdef __ANDROID__
117
const std::string ppf_path = Path::BuildRelativePath(path, Path::ReplaceExtension(path_display_name, "ppf"));
118
#else
119
const std::string ppf_path = Path::BuildRelativePath(path, Path::ReplaceExtension(Path::GetFileName(path), "ppf"));
120
#endif
121
if (FileSystem::FileExists(ppf_path.c_str()))
122
{
123
image = CDImage::OverlayPPFPatch(ppf_path.c_str(), std::move(image));
124
if (!image)
125
Error::SetStringFmt(error, "Failed to apply ppf patch from '{}'.", ppf_path);
126
}
127
}
128
129
return image;
130
}
131
132
CDImage::LBA CDImage::GetTrackStartPosition(u8 track) const
133
{
134
Assert(track > 0 && track <= m_tracks.size());
135
return m_tracks[track - 1].start_lba;
136
}
137
138
CDImage::Position CDImage::GetTrackStartMSFPosition(u8 track) const
139
{
140
Assert(track > 0 && track <= m_tracks.size());
141
return Position::FromLBA(m_tracks[track - 1].start_lba);
142
}
143
144
CDImage::LBA CDImage::GetTrackLength(u8 track) const
145
{
146
Assert(track > 0 && track <= m_tracks.size());
147
return m_tracks[track - 1].length;
148
}
149
150
CDImage::Position CDImage::GetTrackMSFLength(u8 track) const
151
{
152
Assert(track > 0 && track <= m_tracks.size());
153
return Position::FromLBA(m_tracks[track - 1].length);
154
}
155
156
CDImage::TrackMode CDImage::GetTrackMode(u8 track) const
157
{
158
Assert(track > 0 && track <= m_tracks.size());
159
return m_tracks[track - 1].mode;
160
}
161
162
CDImage::LBA CDImage::GetTrackIndexPosition(u8 track, u8 index) const
163
{
164
for (const Index& current_index : m_indices)
165
{
166
if (current_index.track_number == track && current_index.index_number == index)
167
return current_index.start_lba_on_disc;
168
}
169
170
return m_lba_count;
171
}
172
173
CDImage::LBA CDImage::GetTrackIndexLength(u8 track, u8 index) const
174
{
175
for (const Index& current_index : m_indices)
176
{
177
if (current_index.track_number == track && current_index.index_number == index)
178
return current_index.length;
179
}
180
181
return 0;
182
}
183
184
const CDImage::CDImage::Track& CDImage::GetTrack(u32 track) const
185
{
186
Assert(track > 0 && track <= m_tracks.size());
187
return m_tracks[track - 1];
188
}
189
190
const CDImage::CDImage::Index& CDImage::GetIndex(u32 i) const
191
{
192
return m_indices[i];
193
}
194
195
bool CDImage::Seek(LBA lba)
196
{
197
const Index* new_index;
198
if (m_current_index && lba >= m_current_index->start_lba_on_disc &&
199
(lba - m_current_index->start_lba_on_disc) < m_current_index->length)
200
{
201
new_index = m_current_index;
202
}
203
else
204
{
205
new_index = GetIndexForDiscPosition(lba);
206
if (!new_index)
207
return false;
208
}
209
210
const LBA new_index_offset = lba - new_index->start_lba_on_disc;
211
if (new_index_offset >= new_index->length)
212
return false;
213
214
m_current_index = new_index;
215
m_position_on_disc = lba;
216
m_position_in_index = new_index_offset;
217
m_position_in_track = new_index->start_lba_in_track + new_index_offset;
218
return true;
219
}
220
221
bool CDImage::Seek(u32 track_number, const Position& pos_in_track)
222
{
223
if (track_number < 1 || track_number > m_tracks.size())
224
return false;
225
226
const Track& track = m_tracks[track_number - 1];
227
const LBA pos_lba = pos_in_track.ToLBA();
228
if (pos_lba >= track.length)
229
return false;
230
231
return Seek(track.start_lba + pos_lba);
232
}
233
234
bool CDImage::Seek(const Position& pos)
235
{
236
return Seek(pos.ToLBA());
237
}
238
239
bool CDImage::Seek(u32 track_number, LBA lba)
240
{
241
if (track_number < 1 || track_number > m_tracks.size())
242
return false;
243
244
const Track& track = m_tracks[track_number - 1];
245
return Seek(track.start_lba + lba);
246
}
247
248
bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq)
249
{
250
if (m_position_in_index == m_current_index->length)
251
{
252
if (!Seek(m_position_on_disc))
253
return false;
254
}
255
256
if (buffer)
257
{
258
if (m_current_index->file_sector_size > 0)
259
{
260
// TODO: This is where we'd reconstruct the header for other mode tracks.
261
if (!ReadSectorFromIndex(buffer, *m_current_index, m_position_in_index))
262
{
263
ERROR_LOG("Read of LBA {} failed", m_position_on_disc);
264
Seek(m_position_on_disc);
265
return false;
266
}
267
}
268
else
269
{
270
if (m_current_index->track_number == LEAD_OUT_TRACK_NUMBER)
271
{
272
// Lead-out area.
273
std::fill(static_cast<u8*>(buffer), static_cast<u8*>(buffer) + RAW_SECTOR_SIZE, u8(0xAA));
274
}
275
else
276
{
277
// This in an implicit pregap. Return silence.
278
std::fill(static_cast<u8*>(buffer), static_cast<u8*>(buffer) + RAW_SECTOR_SIZE, u8(0));
279
}
280
}
281
}
282
283
if (subq && !ReadSubChannelQ(subq, *m_current_index, m_position_in_index))
284
{
285
ERROR_LOG("Subchannel read of LBA {} failed", m_position_on_disc);
286
Seek(m_position_on_disc);
287
return false;
288
}
289
290
m_position_on_disc++;
291
m_position_in_index++;
292
m_position_in_track++;
293
return true;
294
}
295
296
bool CDImage::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
297
{
298
GenerateSubChannelQ(subq, index, lba_in_index);
299
return true;
300
}
301
302
bool CDImage::HasSubchannelData() const
303
{
304
return false;
305
}
306
307
bool CDImage::HasSubImages() const
308
{
309
return false;
310
}
311
312
u32 CDImage::GetSubImageCount() const
313
{
314
return 0;
315
}
316
317
u32 CDImage::GetCurrentSubImage() const
318
{
319
return 0;
320
}
321
322
bool CDImage::SwitchSubImage(u32 index, Error* error)
323
{
324
return false;
325
}
326
327
std::string CDImage::GetSubImageTitle(u32 index) const
328
{
329
return {};
330
}
331
332
CDImage::PrecacheResult CDImage::Precache(ProgressCallback* progress /*= ProgressCallback::NullProgressCallback*/)
333
{
334
return PrecacheResult::Unsupported;
335
}
336
337
bool CDImage::IsPrecached() const
338
{
339
return false;
340
}
341
342
s64 CDImage::GetSizeOnDisk() const
343
{
344
return -1;
345
}
346
347
void CDImage::ClearTOC()
348
{
349
m_lba_count = 0;
350
m_indices.clear();
351
m_tracks.clear();
352
m_current_index = nullptr;
353
m_position_in_index = 0;
354
m_position_in_track = 0;
355
m_position_on_disc = 0;
356
}
357
358
void CDImage::CopyTOC(const CDImage* image)
359
{
360
m_lba_count = image->m_lba_count;
361
decltype(m_indices)().swap(m_indices);
362
decltype(m_tracks)().swap(m_tracks);
363
m_indices.reserve(image->m_indices.size());
364
m_tracks.reserve(image->m_tracks.size());
365
366
// Damn bitfield copy constructor...
367
for (const Index& index : image->m_indices)
368
{
369
Index new_index;
370
std::memcpy(&new_index, &index, sizeof(new_index));
371
m_indices.push_back(new_index);
372
}
373
for (const Track& track : image->m_tracks)
374
{
375
Track new_track;
376
std::memcpy(&new_track, &track, sizeof(new_track));
377
m_tracks.push_back(new_track);
378
}
379
m_current_index = nullptr;
380
m_position_in_index = 0;
381
m_position_in_track = 0;
382
m_position_on_disc = 0;
383
}
384
385
const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) const
386
{
387
for (const Index& index : m_indices)
388
{
389
if (pos < index.start_lba_on_disc)
390
continue;
391
392
const LBA index_offset = pos - index.start_lba_on_disc;
393
if (index_offset >= index.length)
394
continue;
395
396
return &index;
397
}
398
399
return nullptr;
400
}
401
402
const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos) const
403
{
404
if (track_number < 1 || track_number > m_tracks.size())
405
return nullptr;
406
407
const Track& track = m_tracks[track_number - 1];
408
if (track_pos >= track.length)
409
return nullptr;
410
411
return GetIndexForDiscPosition(track.start_lba + track_pos);
412
}
413
414
bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) const
415
{
416
const Index* index = GetIndexForDiscPosition(lba);
417
if (!index)
418
return false;
419
420
const u32 index_offset = lba - index->start_lba_on_disc;
421
GenerateSubChannelQ(subq, *index, index_offset);
422
return true;
423
}
424
425
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) const
426
{
427
subq->control_bits = index.control.bits;
428
subq->track_number_bcd = (index.track_number <= m_tracks.size() ? BinaryToBCD(static_cast<u8>(index.track_number)) :
429
static_cast<u8>(index.track_number));
430
subq->index_number_bcd = BinaryToBCD(static_cast<u8>(index.index_number));
431
432
Position relative_position;
433
if (index.is_pregap)
434
{
435
// position should count down to the end of the pregap
436
relative_position = Position::FromLBA(index.length - index_offset - 1);
437
}
438
else
439
{
440
// count up from the start of the track
441
relative_position = Position::FromLBA(index.start_lba_in_track + index_offset);
442
}
443
444
std::tie(subq->relative_minute_bcd, subq->relative_second_bcd, subq->relative_frame_bcd) = relative_position.ToBCD();
445
446
subq->reserved = 0;
447
448
const Position absolute_position = Position::FromLBA(index.start_lba_on_disc + index_offset);
449
std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD();
450
subq->crc = SubChannelQ::ComputeCRC(subq->data);
451
}
452
453
void CDImage::AddLeadOutIndex()
454
{
455
Assert(!m_indices.empty());
456
const Index& last_index = m_indices.back();
457
458
Index index = {};
459
index.start_lba_on_disc = last_index.start_lba_on_disc + last_index.length;
460
index.length = LEAD_OUT_SECTOR_COUNT;
461
index.track_number = LEAD_OUT_TRACK_NUMBER;
462
index.index_number = 0;
463
index.control.bits = last_index.control.bits;
464
m_indices.push_back(index);
465
}
466
467
u16 CDImage::SubChannelQ::ComputeCRC(const Data& data)
468
{
469
static constexpr std::array<u16, 256> crc16_table = {
470
{0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD,
471
0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A,
472
0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B,
473
0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
474
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861,
475
0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96,
476
0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87,
477
0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
478
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
479
0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3,
480
0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290,
481
0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
482
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E,
483
0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F,
484
0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C,
485
0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
486
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83,
487
0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
488
0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}};
489
490
u16 value = 0;
491
for (u32 i = 0; i < 10; i++)
492
value = crc16_table[(value >> 8) ^ data[i]] ^ (value << 8);
493
494
// Invert and swap
495
return ByteSwap(static_cast<u16>(~value));
496
}
497
498
bool CDImage::SubChannelQ::IsCRCValid() const
499
{
500
return crc == ComputeCRC(data);
501
}
502