Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image.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 "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
std::string CDImage::GetMetadata(std::string_view type) const
308
{
309
std::string result;
310
if (type == "title")
311
{
312
const std::string display_name(FileSystem::GetDisplayNameFromPath(m_filename));
313
result = Path::StripExtension(display_name);
314
}
315
316
return result;
317
}
318
319
bool CDImage::HasSubImages() const
320
{
321
return false;
322
}
323
324
u32 CDImage::GetSubImageCount() const
325
{
326
return 0;
327
}
328
329
u32 CDImage::GetCurrentSubImage() const
330
{
331
return 0;
332
}
333
334
bool CDImage::SwitchSubImage(u32 index, Error* error)
335
{
336
return false;
337
}
338
339
std::string CDImage::GetSubImageMetadata(u32 index, std::string_view type) const
340
{
341
return {};
342
}
343
344
CDImage::PrecacheResult CDImage::Precache(ProgressCallback* progress /*= ProgressCallback::NullProgressCallback*/)
345
{
346
return PrecacheResult::Unsupported;
347
}
348
349
bool CDImage::IsPrecached() const
350
{
351
return false;
352
}
353
354
s64 CDImage::GetSizeOnDisk() const
355
{
356
return -1;
357
}
358
359
void CDImage::ClearTOC()
360
{
361
m_lba_count = 0;
362
m_indices.clear();
363
m_tracks.clear();
364
m_current_index = nullptr;
365
m_position_in_index = 0;
366
m_position_in_track = 0;
367
m_position_on_disc = 0;
368
}
369
370
void CDImage::CopyTOC(const CDImage* image)
371
{
372
m_lba_count = image->m_lba_count;
373
decltype(m_indices)().swap(m_indices);
374
decltype(m_tracks)().swap(m_tracks);
375
m_indices.reserve(image->m_indices.size());
376
m_tracks.reserve(image->m_tracks.size());
377
378
// Damn bitfield copy constructor...
379
for (const Index& index : image->m_indices)
380
{
381
Index new_index;
382
std::memcpy(&new_index, &index, sizeof(new_index));
383
m_indices.push_back(new_index);
384
}
385
for (const Track& track : image->m_tracks)
386
{
387
Track new_track;
388
std::memcpy(&new_track, &track, sizeof(new_track));
389
m_tracks.push_back(new_track);
390
}
391
m_current_index = nullptr;
392
m_position_in_index = 0;
393
m_position_in_track = 0;
394
m_position_on_disc = 0;
395
}
396
397
const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) const
398
{
399
for (const Index& index : m_indices)
400
{
401
if (pos < index.start_lba_on_disc)
402
continue;
403
404
const LBA index_offset = pos - index.start_lba_on_disc;
405
if (index_offset >= index.length)
406
continue;
407
408
return &index;
409
}
410
411
return nullptr;
412
}
413
414
const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos) const
415
{
416
if (track_number < 1 || track_number > m_tracks.size())
417
return nullptr;
418
419
const Track& track = m_tracks[track_number - 1];
420
if (track_pos >= track.length)
421
return nullptr;
422
423
return GetIndexForDiscPosition(track.start_lba + track_pos);
424
}
425
426
bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) const
427
{
428
const Index* index = GetIndexForDiscPosition(lba);
429
if (!index)
430
return false;
431
432
const u32 index_offset = lba - index->start_lba_on_disc;
433
GenerateSubChannelQ(subq, *index, index_offset);
434
return true;
435
}
436
437
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) const
438
{
439
subq->control_bits = index.control.bits;
440
subq->track_number_bcd = (index.track_number <= m_tracks.size() ? BinaryToBCD(static_cast<u8>(index.track_number)) :
441
static_cast<u8>(index.track_number));
442
subq->index_number_bcd = BinaryToBCD(static_cast<u8>(index.index_number));
443
444
Position relative_position;
445
if (index.is_pregap)
446
{
447
// position should count down to the end of the pregap
448
relative_position = Position::FromLBA(index.length - index_offset - 1);
449
}
450
else
451
{
452
// count up from the start of the track
453
relative_position = Position::FromLBA(index.start_lba_in_track + index_offset);
454
}
455
456
std::tie(subq->relative_minute_bcd, subq->relative_second_bcd, subq->relative_frame_bcd) = relative_position.ToBCD();
457
458
subq->reserved = 0;
459
460
const Position absolute_position = Position::FromLBA(index.start_lba_on_disc + index_offset);
461
std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD();
462
subq->crc = SubChannelQ::ComputeCRC(subq->data);
463
}
464
465
void CDImage::AddLeadOutIndex()
466
{
467
Assert(!m_indices.empty());
468
const Index& last_index = m_indices.back();
469
470
Index index = {};
471
index.start_lba_on_disc = last_index.start_lba_on_disc + last_index.length;
472
index.length = LEAD_OUT_SECTOR_COUNT;
473
index.track_number = LEAD_OUT_TRACK_NUMBER;
474
index.index_number = 0;
475
index.control.bits = last_index.control.bits;
476
m_indices.push_back(index);
477
}
478
479
u16 CDImage::SubChannelQ::ComputeCRC(const Data& data)
480
{
481
static constexpr std::array<u16, 256> crc16_table = {
482
{0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD,
483
0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A,
484
0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B,
485
0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
486
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861,
487
0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96,
488
0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87,
489
0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
490
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
491
0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3,
492
0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290,
493
0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
494
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E,
495
0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F,
496
0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C,
497
0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
498
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83,
499
0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
500
0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}};
501
502
u16 value = 0;
503
for (u32 i = 0; i < 10; i++)
504
value = crc16_table[(value >> 8) ^ data[i]] ^ (value << 8);
505
506
// Invert and swap
507
return ByteSwap(static_cast<u16>(~value));
508
}
509
510
bool CDImage::SubChannelQ::IsCRCValid() const
511
{
512
return crc == ComputeCRC(data);
513
}
514