Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image.cpp
7342 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), error);
124
if (!image)
125
Error::AddPrefixFmt(error, "Failed to apply ppf patch from '{}':\n", ppf_path);
126
}
127
}
128
129
return image;
130
}
131
132
bool CDImage::HasOverlayablePatch(const char* path)
133
{
134
// Annoying handling because of storage access framework.
135
#ifdef __ANDROID__
136
const std::string ppf_path =
137
Path::BuildRelativePath(path, Path::ReplaceExtension(FileSystem::GetDisplayNameFromPath(path), "ppf"));
138
#else
139
const std::string ppf_path = Path::BuildRelativePath(path, Path::ReplaceExtension(Path::GetFileName(path), "ppf"));
140
#endif
141
142
return FileSystem::FileExists(ppf_path.c_str());
143
}
144
145
CDImage::LBA CDImage::GetTrackStartPosition(u8 track) const
146
{
147
Assert(track > 0 && track <= m_tracks.size());
148
return m_tracks[track - 1].start_lba;
149
}
150
151
CDImage::Position CDImage::GetTrackStartMSFPosition(u8 track) const
152
{
153
Assert(track > 0 && track <= m_tracks.size());
154
return Position::FromLBA(m_tracks[track - 1].start_lba);
155
}
156
157
CDImage::LBA CDImage::GetTrackLength(u8 track) const
158
{
159
Assert(track > 0 && track <= m_tracks.size());
160
return m_tracks[track - 1].length;
161
}
162
163
CDImage::Position CDImage::GetTrackMSFLength(u8 track) const
164
{
165
Assert(track > 0 && track <= m_tracks.size());
166
return Position::FromLBA(m_tracks[track - 1].length);
167
}
168
169
CDImage::TrackMode CDImage::GetTrackMode(u8 track) const
170
{
171
Assert(track > 0 && track <= m_tracks.size());
172
return m_tracks[track - 1].mode;
173
}
174
175
CDImage::LBA CDImage::GetTrackIndexPosition(u8 track, u8 index) const
176
{
177
for (const Index& current_index : m_indices)
178
{
179
if (current_index.track_number == track && current_index.index_number == index)
180
return current_index.start_lba_on_disc;
181
}
182
183
return m_lba_count;
184
}
185
186
CDImage::LBA CDImage::GetTrackIndexLength(u8 track, u8 index) const
187
{
188
for (const Index& current_index : m_indices)
189
{
190
if (current_index.track_number == track && current_index.index_number == index)
191
return current_index.length;
192
}
193
194
return 0;
195
}
196
197
const CDImage::CDImage::Track& CDImage::GetTrack(u32 track) const
198
{
199
Assert(track > 0 && track <= m_tracks.size());
200
return m_tracks[track - 1];
201
}
202
203
const CDImage::CDImage::Index& CDImage::GetIndex(u32 i) const
204
{
205
return m_indices[i];
206
}
207
208
bool CDImage::Seek(LBA lba)
209
{
210
const Index* new_index;
211
if (m_current_index && lba >= m_current_index->start_lba_on_disc &&
212
(lba - m_current_index->start_lba_on_disc) < m_current_index->length)
213
{
214
new_index = m_current_index;
215
}
216
else
217
{
218
new_index = GetIndexForDiscPosition(lba);
219
if (!new_index)
220
return false;
221
}
222
223
const LBA new_index_offset = lba - new_index->start_lba_on_disc;
224
if (new_index_offset >= new_index->length)
225
return false;
226
227
m_current_index = new_index;
228
m_position_on_disc = lba;
229
m_position_in_index = new_index_offset;
230
m_position_in_track = new_index->start_lba_in_track + new_index_offset;
231
return true;
232
}
233
234
bool CDImage::Seek(u32 track_number, const Position& pos_in_track)
235
{
236
if (track_number < 1 || track_number > m_tracks.size())
237
return false;
238
239
const Track& track = m_tracks[track_number - 1];
240
const LBA pos_lba = pos_in_track.ToLBA();
241
if (pos_lba >= track.length)
242
return false;
243
244
return Seek(track.start_lba + pos_lba);
245
}
246
247
bool CDImage::Seek(const Position& pos)
248
{
249
return Seek(pos.ToLBA());
250
}
251
252
bool CDImage::Seek(u32 track_number, LBA lba)
253
{
254
if (track_number < 1 || track_number > m_tracks.size())
255
return false;
256
257
const Track& track = m_tracks[track_number - 1];
258
return Seek(track.start_lba + lba);
259
}
260
261
bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq)
262
{
263
if (m_position_in_index == m_current_index->length)
264
{
265
if (!Seek(m_position_on_disc))
266
return false;
267
}
268
269
if (buffer)
270
{
271
if (m_current_index->file_sector_size > 0)
272
{
273
// TODO: This is where we'd reconstruct the header for other mode tracks.
274
if (!ReadSectorFromIndex(buffer, *m_current_index, m_position_in_index))
275
{
276
ERROR_LOG("Read of LBA {} failed", m_position_on_disc);
277
Seek(m_position_on_disc);
278
return false;
279
}
280
}
281
else
282
{
283
if (m_current_index->track_number == LEAD_OUT_TRACK_NUMBER)
284
{
285
// Lead-out area.
286
std::fill(static_cast<u8*>(buffer), static_cast<u8*>(buffer) + RAW_SECTOR_SIZE, u8(0xAA));
287
}
288
else
289
{
290
// This in an implicit pregap. Return silence.
291
std::fill(static_cast<u8*>(buffer), static_cast<u8*>(buffer) + RAW_SECTOR_SIZE, u8(0));
292
}
293
}
294
}
295
296
if (subq && !ReadSubChannelQ(subq, *m_current_index, m_position_in_index))
297
{
298
ERROR_LOG("Subchannel read of LBA {} failed", m_position_on_disc);
299
Seek(m_position_on_disc);
300
return false;
301
}
302
303
m_position_on_disc++;
304
m_position_in_index++;
305
m_position_in_track++;
306
return true;
307
}
308
309
bool CDImage::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
310
{
311
GenerateSubChannelQ(subq, index, lba_in_index);
312
return true;
313
}
314
315
bool CDImage::HasSubchannelData() const
316
{
317
return false;
318
}
319
320
bool CDImage::HasSubImages() const
321
{
322
return false;
323
}
324
325
u32 CDImage::GetSubImageCount() const
326
{
327
return 0;
328
}
329
330
u32 CDImage::GetCurrentSubImage() const
331
{
332
return 0;
333
}
334
335
bool CDImage::SwitchSubImage(u32 index, Error* error)
336
{
337
return false;
338
}
339
340
std::string CDImage::GetSubImageTitle(u32 index) const
341
{
342
return {};
343
}
344
345
CDImage::PrecacheResult CDImage::Precache(ProgressCallback* progress, Error* error)
346
{
347
return PrecacheResult::Unsupported;
348
}
349
350
bool CDImage::IsPrecached() const
351
{
352
return false;
353
}
354
355
s64 CDImage::GetSizeOnDisk() const
356
{
357
return -1;
358
}
359
360
void CDImage::ClearTOC()
361
{
362
m_lba_count = 0;
363
m_indices.clear();
364
m_tracks.clear();
365
m_current_index = nullptr;
366
m_position_in_index = 0;
367
m_position_in_track = 0;
368
m_position_on_disc = 0;
369
}
370
371
void CDImage::CopyTOC(const CDImage* image)
372
{
373
m_lba_count = image->m_lba_count;
374
decltype(m_indices)().swap(m_indices);
375
decltype(m_tracks)().swap(m_tracks);
376
m_indices.reserve(image->m_indices.size());
377
m_tracks.reserve(image->m_tracks.size());
378
379
// Damn bitfield copy constructor...
380
for (const Index& index : image->m_indices)
381
{
382
Index new_index;
383
std::memcpy(&new_index, &index, sizeof(new_index));
384
m_indices.push_back(new_index);
385
}
386
for (const Track& track : image->m_tracks)
387
{
388
Track new_track;
389
std::memcpy(&new_track, &track, sizeof(new_track));
390
m_tracks.push_back(new_track);
391
}
392
m_current_index = nullptr;
393
m_position_in_index = 0;
394
m_position_in_track = 0;
395
m_position_on_disc = 0;
396
}
397
398
const CDImage::Index* CDImage::GetIndexForDiscPosition(LBA pos) const
399
{
400
for (const Index& index : m_indices)
401
{
402
if (pos < index.start_lba_on_disc)
403
continue;
404
405
const LBA index_offset = pos - index.start_lba_on_disc;
406
if (index_offset >= index.length)
407
continue;
408
409
return &index;
410
}
411
412
return nullptr;
413
}
414
415
const CDImage::Index* CDImage::GetIndexForTrackPosition(u32 track_number, LBA track_pos) const
416
{
417
if (track_number < 1 || track_number > m_tracks.size())
418
return nullptr;
419
420
const Track& track = m_tracks[track_number - 1];
421
if (track_pos >= track.length)
422
return nullptr;
423
424
return GetIndexForDiscPosition(track.start_lba + track_pos);
425
}
426
427
bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) const
428
{
429
const Index* index = GetIndexForDiscPosition(lba);
430
if (!index)
431
return false;
432
433
const u32 index_offset = lba - index->start_lba_on_disc;
434
GenerateSubChannelQ(subq, *index, index_offset);
435
return true;
436
}
437
438
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) const
439
{
440
subq->control_bits = index.control.bits;
441
subq->track_number_bcd = (index.track_number <= m_tracks.size() ? BinaryToBCD(static_cast<u8>(index.track_number)) :
442
static_cast<u8>(index.track_number));
443
subq->index_number_bcd = BinaryToBCD(static_cast<u8>(index.index_number));
444
445
Position relative_position;
446
if (index.is_pregap)
447
{
448
// position should count down to the end of the pregap
449
relative_position = Position::FromLBA(index.length - index_offset - 1);
450
}
451
else
452
{
453
// count up from the start of the track
454
relative_position = Position::FromLBA(index.start_lba_in_track + index_offset);
455
}
456
457
std::tie(subq->relative_minute_bcd, subq->relative_second_bcd, subq->relative_frame_bcd) = relative_position.ToBCD();
458
459
subq->reserved = 0;
460
461
const Position absolute_position = Position::FromLBA(index.start_lba_on_disc + index_offset);
462
std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD();
463
subq->crc = SubChannelQ::ComputeCRC(subq->data);
464
}
465
466
void CDImage::AddLeadOutIndex()
467
{
468
Assert(!m_indices.empty());
469
const Index& last_index = m_indices.back();
470
471
Index index = {};
472
index.start_lba_on_disc = last_index.start_lba_on_disc + last_index.length;
473
index.length = LEAD_OUT_SECTOR_COUNT;
474
index.track_number = LEAD_OUT_TRACK_NUMBER;
475
index.index_number = 0;
476
index.control.bits = last_index.control.bits;
477
m_indices.push_back(index);
478
}
479
480
u16 CDImage::SubChannelQ::ComputeCRC(const Data& data)
481
{
482
static constexpr std::array<u16, 256> crc16_table = {
483
{0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD,
484
0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A,
485
0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B,
486
0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
487
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861,
488
0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96,
489
0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87,
490
0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
491
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A,
492
0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3,
493
0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290,
494
0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
495
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E,
496
0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F,
497
0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C,
498
0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
499
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83,
500
0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74,
501
0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}};
502
503
u16 value = 0;
504
for (u32 i = 0; i < 10; i++)
505
value = crc16_table[(value >> 8) ^ data[i]] ^ (value << 8);
506
507
// Invert and swap
508
return ByteSwap(static_cast<u16>(~value));
509
}
510
511
bool CDImage::SubChannelQ::IsCRCValid() const
512
{
513
return crc == ComputeCRC(data);
514
}
515