Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/util/cd_image_pbp.cpp
4223 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]> and contributors.
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/error.h"
8
#include "common/file_system.h"
9
#include "common/log.h"
10
#include "common/path.h"
11
#include "common/string_util.h"
12
13
#include "fmt/format.h"
14
#include "zlib.h"
15
16
#include <array>
17
#include <cstdio>
18
#include <cstring>
19
#include <map>
20
#include <string>
21
#include <variant>
22
#include <vector>
23
24
LOG_CHANNEL(CDImage);
25
26
namespace {
27
28
enum : u32
29
{
30
PBP_HEADER_OFFSET_COUNT = 8u,
31
TOC_NUM_ENTRIES = 102u,
32
BLOCK_TABLE_NUM_ENTRIES = 32256u,
33
DISC_TABLE_NUM_ENTRIES = 5u,
34
DECOMPRESSED_BLOCK_SIZE = 37632u // 2352 bytes per sector * 16 sectors per block
35
};
36
37
#pragma pack(push, 1)
38
39
struct PBPHeader
40
{
41
u8 magic[4]; // "\0PBP"
42
u32 version;
43
44
union
45
{
46
u32 offsets[PBP_HEADER_OFFSET_COUNT];
47
48
struct
49
{
50
u32 param_sfo_offset; // 0x00000028
51
u32 icon0_png_offset;
52
u32 icon1_png_offset;
53
u32 pic0_png_offset;
54
u32 pic1_png_offset;
55
u32 snd0_at3_offset;
56
u32 data_psp_offset;
57
u32 data_psar_offset;
58
};
59
};
60
};
61
static_assert(sizeof(PBPHeader) == 0x28);
62
63
struct SFOHeader
64
{
65
u8 magic[4]; // "\0PSF"
66
u32 version;
67
u32 key_table_offset; // Relative to start of SFOHeader, 0x000000A4 expected
68
u32 data_table_offset; // Relative to start of SFOHeader, 0x00000100 expected
69
u32 num_table_entries; // 0x00000009
70
};
71
static_assert(sizeof(SFOHeader) == 0x14);
72
73
struct SFOIndexTableEntry
74
{
75
u16 key_offset; // Relative to key_table_offset
76
u16 data_type;
77
u32 data_size; // Size of actual data in bytes
78
u32 data_total_size; // Size of data field in bytes, data_total_size >= data_size
79
u32 data_offset; // Relative to data_table_offset
80
};
81
static_assert(sizeof(SFOIndexTableEntry) == 0x10);
82
83
using SFOIndexTable = std::vector<SFOIndexTableEntry>;
84
using SFOTableDataValue = std::variant<std::string, u32>;
85
using SFOTable = std::map<std::string, SFOTableDataValue>;
86
87
struct BlockTableEntry
88
{
89
u32 offset;
90
u16 size;
91
u16 marker;
92
u8 checksum[0x10];
93
u64 padding;
94
};
95
static_assert(sizeof(BlockTableEntry) == 0x20);
96
97
struct TOCEntry
98
{
99
struct Timecode
100
{
101
u8 m;
102
u8 s;
103
u8 f;
104
};
105
106
u8 type;
107
u8 unknown;
108
u8 point;
109
Timecode pregap_start;
110
u8 zero;
111
Timecode userdata_start;
112
};
113
static_assert(sizeof(TOCEntry) == 0x0A);
114
115
#if 0
116
struct AudioTrackTableEntry
117
{
118
u32 block_offset;
119
u32 block_size;
120
u32 block_padding;
121
u32 block_checksum;
122
};
123
static_assert(sizeof(CDDATrackTableEntry) == 0x10);
124
#endif
125
126
#pragma pack(pop)
127
128
class CDImagePBP final : public CDImage
129
{
130
public:
131
CDImagePBP() = default;
132
~CDImagePBP() override;
133
134
bool Open(const char* filename, Error* error);
135
136
s64 GetSizeOnDisk() const override;
137
138
bool HasSubImages() const override;
139
u32 GetSubImageCount() const override;
140
u32 GetCurrentSubImage() const override;
141
bool SwitchSubImage(u32 index, Error* error) override;
142
std::string GetMetadata(std::string_view type) const override;
143
std::string GetSubImageMetadata(u32 index, std::string_view type) const override;
144
145
protected:
146
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
147
148
private:
149
struct BlockInfo
150
{
151
u32 offset; // Absolute offset from start of file
152
u16 size;
153
};
154
155
#if defined(_DEBUG) || defined(_DEVEL)
156
static void PrintPBPHeaderInfo(const PBPHeader& pbp_header);
157
static void PrintSFOHeaderInfo(const SFOHeader& sfo_header);
158
static void PrintSFOIndexTableEntry(const SFOIndexTableEntry& sfo_index_table_entry, size_t i);
159
static void PrintSFOTable(const SFOTable& sfo_table);
160
#endif
161
162
bool LoadPBPHeader(Error* error);
163
bool LoadSFOHeader(Error* error);
164
bool LoadSFOIndexTable(Error* error);
165
bool LoadSFOTable(Error* error);
166
167
bool IsValidEboot(Error* error);
168
169
bool InitDecompressionStream();
170
bool DecompressBlock(const BlockInfo& block_info);
171
172
bool OpenDisc(u32 index, Error* error);
173
174
static const std::string* LookupStringSFOTableEntry(const char* key, const SFOTable& table);
175
176
std::FILE* m_file = nullptr;
177
178
PBPHeader m_pbp_header;
179
SFOHeader m_sfo_header;
180
SFOTable m_sfo_table;
181
SFOIndexTable m_sfo_index_table;
182
183
// Absolute offsets to ISO headers, size is the number of discs in the file
184
std::vector<u32> m_disc_offsets;
185
u32 m_current_disc = 0;
186
187
// Absolute offsets and sizes of blocks in m_file
188
std::array<BlockInfo, BLOCK_TABLE_NUM_ENTRIES> m_blockinfo_table;
189
190
std::array<TOCEntry, TOC_NUM_ENTRIES> m_toc;
191
192
u32 m_current_block = static_cast<u32>(-1);
193
std::array<u8, DECOMPRESSED_BLOCK_SIZE> m_decompressed_block;
194
std::vector<u8> m_compressed_block;
195
196
z_stream m_inflate_stream;
197
};
198
} // namespace
199
200
CDImagePBP::~CDImagePBP()
201
{
202
if (m_file)
203
std::fclose(m_file);
204
205
inflateEnd(&m_inflate_stream);
206
}
207
208
bool CDImagePBP::LoadPBPHeader(Error* error)
209
{
210
if (std::fread(&m_pbp_header, sizeof(PBPHeader), 1, m_file) != 1)
211
{
212
Error::SetErrno(error, "fread() for PBP header failed: ", errno);
213
return false;
214
}
215
216
if (std::memcmp(m_pbp_header.magic, "\0PBP", 4) != 0)
217
{
218
Error::SetStringView(error, "PBP magic number mismatch");
219
return false;
220
}
221
222
#if defined(_DEBUG) || defined(_DEVEL)
223
PrintPBPHeaderInfo(m_pbp_header);
224
#endif
225
226
return true;
227
}
228
229
bool CDImagePBP::LoadSFOHeader(Error* error)
230
{
231
if (!FileSystem::FSeek64(m_file, m_pbp_header.param_sfo_offset, SEEK_SET, error))
232
return false;
233
234
if (std::fread(&m_sfo_header, sizeof(SFOHeader), 1, m_file) != 1)
235
{
236
Error::SetErrno(error, "fread() for SFO header failed: ", errno);
237
return false;
238
}
239
240
if (std::memcmp(m_sfo_header.magic, "\0PSF", 4) != 0)
241
{
242
Error::SetStringView(error, "SFO magic number mismatch");
243
return false;
244
}
245
246
#if defined(_DEBUG) || defined(_DEVEL)
247
PrintSFOHeaderInfo(m_sfo_header);
248
#endif
249
250
return true;
251
}
252
253
bool CDImagePBP::LoadSFOIndexTable(Error* error)
254
{
255
m_sfo_index_table.clear();
256
m_sfo_index_table.resize(m_sfo_header.num_table_entries);
257
258
if (!FileSystem::FSeek64(m_file, m_pbp_header.param_sfo_offset + sizeof(m_sfo_header), SEEK_SET, error))
259
return false;
260
261
if (std::fread(m_sfo_index_table.data(), sizeof(SFOIndexTableEntry), m_sfo_header.num_table_entries, m_file) !=
262
m_sfo_header.num_table_entries)
263
{
264
Error::SetErrno(error, "fread() for SFO index table failed: ", errno);
265
return false;
266
}
267
268
#if defined(_DEBUG) || defined(_DEVEL)
269
for (size_t i = 0; i < static_cast<size_t>(m_sfo_header.num_table_entries); ++i)
270
PrintSFOIndexTableEntry(m_sfo_index_table[i], i);
271
#endif
272
273
return true;
274
}
275
276
bool CDImagePBP::LoadSFOTable(Error* error)
277
{
278
m_sfo_table.clear();
279
280
for (size_t i = 0; i < static_cast<size_t>(m_sfo_header.num_table_entries); ++i)
281
{
282
u32 abs_key_offset =
283
m_pbp_header.param_sfo_offset + m_sfo_header.key_table_offset + m_sfo_index_table[i].key_offset;
284
u32 abs_data_offset =
285
m_pbp_header.param_sfo_offset + m_sfo_header.data_table_offset + m_sfo_index_table[i].data_offset;
286
287
if (!FileSystem::FSeek64(m_file, abs_key_offset, SEEK_SET, error))
288
{
289
Error::AddPrefixFmt(error, "Failed seek to key for SFO table entry {}: ", i);
290
return false;
291
}
292
293
// Longest known key string is 20 characters total, including the null character
294
char key_cstr[20] = {};
295
if (!std::fgets(key_cstr, sizeof(key_cstr), m_file))
296
{
297
Error::SetErrno(error, "fgets() failed: ", errno);
298
Error::AddPrefixFmt(error, "Failed to read key string for SFO table entry {}: ", i);
299
return false;
300
}
301
302
if (!FileSystem::FSeek64(m_file, abs_data_offset, SEEK_SET, error))
303
{
304
Error::AddPrefixFmt(error, "Failed seek to data for SFO table entry {}: ", i);
305
return false;
306
}
307
308
if (m_sfo_index_table[i].data_type == 0x0004) // "special mode" UTF-8 (not null terminated)
309
{
310
Error::SetStringFmt(error, "Unhandled special mode UTF-8 type found in SFO table for entry {}", i);
311
return false;
312
}
313
else if (m_sfo_index_table[i].data_type == 0x0204) // null-terminated UTF-8 character string
314
{
315
std::vector<char> data_cstr(m_sfo_index_table[i].data_size);
316
if (!std::fgets(data_cstr.data(), static_cast<int>(data_cstr.size() * sizeof(char)), m_file))
317
{
318
Error::SetErrno(error, "fgets() failed: ", errno);
319
Error::AddPrefixFmt(error, "Failed to read data string for SFO table entry {}: ", i);
320
return false;
321
}
322
323
m_sfo_table.emplace(std::string(key_cstr), std::string(data_cstr.data()));
324
}
325
else if (m_sfo_index_table[i].data_type == 0x0404) // uint32_t
326
{
327
u32 val;
328
if (std::fread(&val, sizeof(u32), 1, m_file) != 1)
329
{
330
Error::SetErrno(error, "fread() failed: ", errno);
331
Error::AddPrefixFmt(error, "Failed to read unsigned data value for SFO table entry {}: ", i);
332
return false;
333
}
334
335
m_sfo_table.emplace(std::string(key_cstr), val);
336
}
337
else
338
{
339
Error::SetStringFmt(error, "Unhandled SFO data type 0x{:04X} found in SFO table for entry {}", m_sfo_index_table[i].data_type, i);
340
return false;
341
}
342
}
343
344
#if defined(_DEBUG) || defined(_DEVEL)
345
PrintSFOTable(m_sfo_table);
346
#endif
347
348
return true;
349
}
350
351
bool CDImagePBP::IsValidEboot(Error* error)
352
{
353
// Check some fields to make sure this is a valid PS1 EBOOT.PBP
354
355
auto a_it = m_sfo_table.find("BOOTABLE");
356
if (a_it != m_sfo_table.end())
357
{
358
SFOTableDataValue data_value = a_it->second;
359
if (!std::holds_alternative<u32>(data_value) || std::get<u32>(data_value) != 1)
360
{
361
ERROR_LOG("Invalid BOOTABLE value");
362
Error::SetString(error, "Invalid BOOTABLE value");
363
return false;
364
}
365
}
366
else
367
{
368
ERROR_LOG("No BOOTABLE value found");
369
Error::SetString(error, "No BOOTABLE value found");
370
return false;
371
}
372
373
a_it = m_sfo_table.find("CATEGORY");
374
if (a_it != m_sfo_table.end())
375
{
376
SFOTableDataValue data_value = a_it->second;
377
if (!std::holds_alternative<std::string>(data_value) || std::get<std::string>(data_value) != "ME")
378
{
379
ERROR_LOG("Invalid CATEGORY value");
380
Error::SetString(error, "Invalid CATEGORY value");
381
return false;
382
}
383
}
384
else
385
{
386
ERROR_LOG("No CATEGORY value found");
387
Error::SetString(error, "No CATEGORY value found");
388
return false;
389
}
390
391
return true;
392
}
393
394
bool CDImagePBP::Open(const char* filename, Error* error)
395
{
396
m_file = FileSystem::OpenSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite, error);
397
if (!m_file)
398
{
399
Error::AddPrefixFmt(error, "Failed to open '{}': ", Path::GetFileName(filename));
400
return false;
401
}
402
403
m_filename = filename;
404
405
// Read in PBP header
406
if (!LoadPBPHeader(error))
407
return false;
408
409
// Read in SFO header
410
if (!LoadSFOHeader(error))
411
return false;
412
413
// Read in SFO index table
414
if (!LoadSFOIndexTable(error))
415
return false;
416
417
// Read in SFO table
418
if (!LoadSFOTable(error))
419
return false;
420
421
// Since PBP files can store things that aren't PS1 CD images, make sure we're loading the right kind
422
if (!IsValidEboot(error))
423
{
424
ERROR_LOG("Couldn't validate EBOOT");
425
return false;
426
}
427
428
// Start parsing ISO stuff
429
if (!FileSystem::FSeek64(m_file, m_pbp_header.data_psar_offset, SEEK_SET, error))
430
{
431
Error::AddPrefix(error, "Failed to seek to psar offset: ");
432
return false;
433
}
434
435
// Check "PSTITLEIMG000000" for multi-disc
436
char data_psar_magic[16] = {};
437
if (std::fread(data_psar_magic, sizeof(data_psar_magic), 1, m_file) != 1)
438
{
439
Error::SetErrno(error, "Failed to read data_psar_magic: ", errno);
440
return false;
441
}
442
443
if (std::memcmp(data_psar_magic, "PSTITLEIMG000000", 16) == 0) // Multi-disc header found
444
{
445
// For multi-disc, the five disc offsets are located at data_psar_offset + 0x200. Non-present discs have an offset
446
// of 0. There are also some disc hashes, a serial (from one of the discs, but used as an identifier for the entire
447
// "title image" header), and some other offsets, but we don't really need to check those
448
449
if (!FileSystem::FSeek64(m_file, m_pbp_header.data_psar_offset + 0x200, SEEK_SET, error))
450
{
451
Error::AddPrefix(error, "Failed to seek to multi-disc header: ");
452
return false;
453
}
454
455
u32 disc_table[DISC_TABLE_NUM_ENTRIES] = {};
456
if (std::fread(disc_table, sizeof(u32), DISC_TABLE_NUM_ENTRIES, m_file) != DISC_TABLE_NUM_ENTRIES)
457
{
458
Error::SetErrno(error, "Failed to read disc_table", errno);
459
return false;
460
}
461
462
// Ignore encrypted files
463
if (disc_table[0] == 0x44475000) // "\0PGD"
464
{
465
ERROR_LOG("Encrypted PBP images are not supported, skipping {}", m_filename);
466
Error::SetString(error, "Encrypted PBP images are not supported");
467
return false;
468
}
469
470
// Convert relative offsets to absolute offsets for available discs
471
for (u32 i = 0; i < DISC_TABLE_NUM_ENTRIES; i++)
472
{
473
if (disc_table[i] != 0)
474
m_disc_offsets.push_back(m_pbp_header.data_psar_offset + disc_table[i]);
475
else
476
break;
477
}
478
479
if (m_disc_offsets.size() < 1)
480
{
481
Error::SetStringFmt(error, "Invalid number of discs ({}) in multi-disc PBP file", m_disc_offsets.size());
482
return false;
483
}
484
}
485
else // Single-disc
486
{
487
m_disc_offsets.push_back(m_pbp_header.data_psar_offset);
488
}
489
490
// Default to first disc for now
491
return OpenDisc(0, error);
492
}
493
494
bool CDImagePBP::OpenDisc(u32 index, Error* error)
495
{
496
if (index >= m_disc_offsets.size())
497
{
498
ERROR_LOG("File does not contain disc {}", index + 1);
499
Error::SetStringFmt(error, "File does not contain disc {}", index + 1);
500
return false;
501
}
502
503
m_current_block = static_cast<u32>(-1);
504
m_blockinfo_table.fill({});
505
m_toc.fill({});
506
m_decompressed_block.fill(0x00);
507
m_compressed_block.clear();
508
509
// Go to ISO header
510
const u32 iso_header_start = m_disc_offsets[index];
511
if (FileSystem::FSeek64(m_file, iso_header_start, SEEK_SET) != 0)
512
return false;
513
514
char iso_header_magic[12] = {};
515
if (std::fread(iso_header_magic, sizeof(iso_header_magic), 1, m_file) != 1)
516
return false;
517
518
if (std::strncmp(iso_header_magic, "PSISOIMG0000", 12) != 0)
519
{
520
ERROR_LOG("ISO header magic number mismatch");
521
return false;
522
}
523
524
// Ignore encrypted files
525
u32 pgd_magic;
526
if (FileSystem::FSeek64(m_file, iso_header_start + 0x400, SEEK_SET) != 0)
527
return false;
528
529
if (std::fread(&pgd_magic, sizeof(pgd_magic), 1, m_file) != 1)
530
return false;
531
532
if (pgd_magic == 0x44475000) // "\0PGD"
533
{
534
ERROR_LOG("Encrypted PBP images are not supported, skipping {}", m_filename);
535
Error::SetString(error, "Encrypted PBP images are not supported");
536
return false;
537
}
538
539
// Read in the TOC
540
if (FileSystem::FSeek64(m_file, iso_header_start + 0x800, SEEK_SET) != 0)
541
return false;
542
543
for (u32 i = 0; i < TOC_NUM_ENTRIES; i++)
544
{
545
if (std::fread(&m_toc[i], sizeof(m_toc[i]), 1, m_file) != 1)
546
return false;
547
}
548
549
// For homebrew EBOOTs, audio track table doesn't exist -- the data track block table will point to compressed blocks
550
// for both data and audio
551
552
// Get the offset of the compressed iso
553
if (FileSystem::FSeek64(m_file, iso_header_start + 0xBFC, SEEK_SET) != 0)
554
return false;
555
556
u32 iso_offset;
557
if (std::fread(&iso_offset, sizeof(iso_offset), 1, m_file) != 1)
558
return false;
559
560
// Generate block info table
561
if (FileSystem::FSeek64(m_file, iso_header_start + 0x4000, SEEK_SET) != 0)
562
return false;
563
564
for (u32 i = 0; i < BLOCK_TABLE_NUM_ENTRIES; i++)
565
{
566
BlockTableEntry bte;
567
if (std::fread(&bte, sizeof(bte), 1, m_file) != 1)
568
return false;
569
570
// Only store absolute file offset into a BlockInfo if this is a valid block
571
m_blockinfo_table[i] = {(bte.size != 0) ? (iso_header_start + iso_offset + bte.offset) : 0, bte.size};
572
573
// printf("Block %u, file offset %u, size %u\n", i, m_blockinfo_table[i].offset, m_blockinfo_table[i].size);
574
}
575
576
// iso_header_start + 0x12D4, 0x12D6, 0x12D8 supposedly contain data on block size, num clusters, and num blocks
577
// Might be useful for error checking, but probably not that important as of now
578
579
// Ignore track types for first three TOC entries, these don't seem to be consistent, but check that the points are
580
// valid. Not sure what m_toc[0].userdata_start.s encodes on homebrew EBOOTs though, so ignore that
581
if (m_toc[0].point != 0xA0 || m_toc[1].point != 0xA1 || m_toc[2].point != 0xA2)
582
{
583
ERROR_LOG("Invalid points on information tracks");
584
return false;
585
}
586
587
const u8 first_track = PackedBCDToBinary(m_toc[0].userdata_start.m);
588
const u8 last_track = PackedBCDToBinary(m_toc[1].userdata_start.m);
589
const LBA sectors_on_file =
590
Position::FromBCD(m_toc[2].userdata_start.m, m_toc[2].userdata_start.s, m_toc[2].userdata_start.f).ToLBA();
591
592
if (first_track != 1 || last_track < first_track)
593
{
594
ERROR_LOG("Invalid starting track number or track count");
595
return false;
596
}
597
598
// We assume that the pregap for the data track (track 1) is not on file, but pregaps for any additional tracks are on
599
// file. Also, homebrew tools seem to create 2 second pregaps for audio tracks, even when the audio track has a pregap
600
// that isn't 2 seconds long. We don't have a good way to validate this, and have to assume the TOC is giving us
601
// correct pregap lengths...
602
603
ClearTOC();
604
m_lba_count = sectors_on_file;
605
LBA track1_pregap_frames = 0;
606
for (u32 curr_track = 1; curr_track <= last_track; curr_track++)
607
{
608
// Load in all the user stuff to m_tracks and m_indices
609
const TOCEntry& t = m_toc[static_cast<size_t>(curr_track) + 2];
610
const u8 track_num = PackedBCDToBinary(t.point);
611
if (track_num != curr_track)
612
WARNING_LOG("Mismatched TOC track number, expected {} but got {}", curr_track, track_num);
613
614
const bool is_audio_track = t.type == 0x01;
615
const bool is_first_track = curr_track == 1;
616
const bool is_last_track = curr_track == last_track;
617
const TrackMode track_mode = is_audio_track ? TrackMode::Audio : TrackMode::Mode2Raw;
618
const u32 track_sector_size = GetBytesPerSector(track_mode);
619
620
SubChannelQ::Control track_control = {};
621
track_control.data = !is_audio_track;
622
623
LBA pregap_start = Position::FromBCD(t.pregap_start.m, t.pregap_start.s, t.pregap_start.f).ToLBA();
624
LBA userdata_start = Position::FromBCD(t.userdata_start.m, t.userdata_start.s, t.userdata_start.f).ToLBA();
625
LBA pregap_frames;
626
u32 pregap_sector_size;
627
628
if (userdata_start < pregap_start)
629
{
630
if (!is_first_track || is_audio_track)
631
{
632
ERROR_LOG("Invalid TOC entry at index {}, user data ({}) should not start before pregap ({})", curr_track,
633
userdata_start, pregap_start);
634
return false;
635
}
636
637
WARNING_LOG(
638
"Invalid TOC entry at index {}, user data ({}) should not start before pregap ({}), assuming not in file.",
639
curr_track, userdata_start, pregap_start);
640
pregap_start = 0;
641
pregap_frames = userdata_start;
642
pregap_sector_size = 0;
643
}
644
else
645
{
646
pregap_frames = userdata_start - pregap_start;
647
pregap_sector_size = track_sector_size;
648
}
649
650
if (is_first_track)
651
{
652
m_lba_count += pregap_frames;
653
track1_pregap_frames = pregap_frames;
654
}
655
656
Index pregap_index = {};
657
pregap_index.file_offset =
658
is_first_track ? 0 : (static_cast<u64>(pregap_start - track1_pregap_frames) * pregap_sector_size);
659
pregap_index.file_index = 0;
660
pregap_index.file_sector_size = pregap_sector_size;
661
pregap_index.start_lba_on_disc = pregap_start;
662
pregap_index.track_number = curr_track;
663
pregap_index.index_number = 0;
664
pregap_index.start_lba_in_track = static_cast<LBA>(-static_cast<s32>(pregap_frames));
665
pregap_index.length = pregap_frames;
666
pregap_index.mode = track_mode;
667
pregap_index.submode = CDImage::SubchannelMode::None;
668
pregap_index.control.bits = track_control.bits;
669
pregap_index.is_pregap = true;
670
671
m_indices.push_back(pregap_index);
672
673
Index userdata_index = {};
674
userdata_index.file_offset = static_cast<u64>(userdata_start - track1_pregap_frames) * track_sector_size;
675
userdata_index.file_index = 0;
676
userdata_index.file_sector_size = track_sector_size;
677
userdata_index.start_lba_on_disc = userdata_start;
678
userdata_index.track_number = curr_track;
679
userdata_index.index_number = 1;
680
userdata_index.start_lba_in_track = 0;
681
userdata_index.mode = track_mode;
682
userdata_index.submode = CDImage::SubchannelMode::None;
683
userdata_index.control.bits = track_control.bits;
684
userdata_index.is_pregap = false;
685
686
if (is_last_track)
687
{
688
if (userdata_start >= m_lba_count)
689
{
690
ERROR_LOG("Last user data index on disc for TOC entry {} should not be 0 or less in length", curr_track);
691
return false;
692
}
693
userdata_index.length = m_lba_count - userdata_start;
694
}
695
else
696
{
697
const TOCEntry& next_track = m_toc[static_cast<size_t>(curr_track) + 3];
698
const LBA next_track_start =
699
Position::FromBCD(next_track.pregap_start.m, next_track.pregap_start.s, next_track.pregap_start.f).ToLBA();
700
const u8 next_track_num = PackedBCDToBinary(next_track.point);
701
702
if (next_track_num != curr_track + 1 || next_track_start < userdata_start)
703
{
704
ERROR_LOG("Unable to calculate user data index length for TOC entry {}", curr_track);
705
return false;
706
}
707
708
userdata_index.length = next_track_start - userdata_start;
709
}
710
711
m_indices.push_back(userdata_index);
712
713
m_tracks.push_back(Track{curr_track, userdata_start, 2 * curr_track - 1,
714
pregap_index.length + userdata_index.length, track_mode, SubchannelMode::None,
715
track_control});
716
}
717
718
AddLeadOutIndex();
719
720
// Initialize zlib stream
721
if (!InitDecompressionStream())
722
{
723
ERROR_LOG("Failed to initialize zlib decompression stream");
724
return false;
725
}
726
727
m_current_disc = index;
728
return Seek(1, Position{0, 0, 0});
729
}
730
731
const std::string* CDImagePBP::LookupStringSFOTableEntry(const char* key, const SFOTable& table)
732
{
733
auto iter = table.find(key);
734
if (iter == table.end())
735
return nullptr;
736
737
const SFOTableDataValue& data_value = iter->second;
738
if (!std::holds_alternative<std::string>(data_value))
739
return nullptr;
740
741
return &std::get<std::string>(data_value);
742
}
743
744
bool CDImagePBP::InitDecompressionStream()
745
{
746
m_inflate_stream = {};
747
m_inflate_stream.next_in = Z_NULL;
748
m_inflate_stream.avail_in = 0;
749
m_inflate_stream.zalloc = Z_NULL;
750
m_inflate_stream.zfree = Z_NULL;
751
m_inflate_stream.opaque = Z_NULL;
752
753
int ret = inflateInit2(&m_inflate_stream, -MAX_WBITS);
754
return ret == Z_OK;
755
}
756
757
bool CDImagePBP::DecompressBlock(const BlockInfo& block_info)
758
{
759
if (FileSystem::FSeek64(m_file, block_info.offset, SEEK_SET) != 0)
760
return false;
761
762
// Compression level 0 has compressed size == decompressed size.
763
if (block_info.size == m_decompressed_block.size())
764
{
765
return (std::fread(m_decompressed_block.data(), sizeof(u8), m_decompressed_block.size(), m_file) ==
766
m_decompressed_block.size());
767
}
768
769
m_compressed_block.resize(block_info.size);
770
771
if (std::fread(m_compressed_block.data(), sizeof(u8), m_compressed_block.size(), m_file) != m_compressed_block.size())
772
return false;
773
774
m_inflate_stream.next_in = m_compressed_block.data();
775
m_inflate_stream.avail_in = static_cast<uInt>(m_compressed_block.size());
776
m_inflate_stream.next_out = m_decompressed_block.data();
777
m_inflate_stream.avail_out = static_cast<uInt>(m_decompressed_block.size());
778
779
if (inflateReset(&m_inflate_stream) != Z_OK)
780
return false;
781
782
int err = inflate(&m_inflate_stream, Z_FINISH);
783
if (err != Z_STREAM_END) [[unlikely]]
784
{
785
ERROR_LOG("Inflate error {}", err);
786
return false;
787
}
788
789
return true;
790
}
791
792
bool CDImagePBP::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
793
{
794
const u32 offset_in_file = static_cast<u32>(index.file_offset) + (lba_in_index * index.file_sector_size);
795
const u32 offset_in_block = offset_in_file % DECOMPRESSED_BLOCK_SIZE;
796
const u32 requested_block = offset_in_file / DECOMPRESSED_BLOCK_SIZE;
797
798
BlockInfo& bi = m_blockinfo_table[requested_block];
799
800
if (bi.size == 0) [[unlikely]]
801
{
802
ERROR_LOG("Invalid block {} requested", requested_block);
803
return false;
804
}
805
806
if (m_current_block != requested_block && !DecompressBlock(bi)) [[unlikely]]
807
{
808
ERROR_LOG("Failed to decompress block {}", requested_block);
809
return false;
810
}
811
812
std::memcpy(buffer, &m_decompressed_block[offset_in_block], RAW_SECTOR_SIZE);
813
return true;
814
}
815
816
#if defined(_DEBUG) || defined(_DEVEL)
817
void CDImagePBP::PrintPBPHeaderInfo(const PBPHeader& pbp_header)
818
{
819
printf("PBP header info\n");
820
printf("PBP format version 0x%08X\n", pbp_header.version);
821
printf("File offsets\n");
822
printf("PARAM.SFO 0x%08X PARSE\n", pbp_header.param_sfo_offset);
823
printf("ICON0.PNG 0x%08X IGNORE\n", pbp_header.icon0_png_offset);
824
printf("ICON1.PNG 0x%08X IGNORE\n", pbp_header.icon1_png_offset);
825
printf("PIC0.PNG 0x%08X IGNORE\n", pbp_header.pic0_png_offset);
826
printf("PIC1.PNG 0x%08X IGNORE\n", pbp_header.pic1_png_offset);
827
printf("SND0.AT3 0x%08X IGNORE\n", pbp_header.snd0_at3_offset);
828
printf("DATA.PSP 0x%08X IGNORE\n", pbp_header.data_psp_offset);
829
printf("DATA.PSAR 0x%08X PARSE\n", pbp_header.data_psar_offset);
830
printf("\n");
831
}
832
833
void CDImagePBP::PrintSFOHeaderInfo(const SFOHeader& sfo_header)
834
{
835
printf("SFO header info\n");
836
printf("SFO format version 0x%08X\n", sfo_header.version);
837
printf("SFO key table offset 0x%08X\n", sfo_header.key_table_offset);
838
printf("SFO data table offset 0x%08X\n", sfo_header.data_table_offset);
839
printf("SFO table entry count 0x%08X\n", sfo_header.num_table_entries);
840
printf("\n");
841
}
842
843
void CDImagePBP::PrintSFOIndexTableEntry(const SFOIndexTableEntry& sfo_index_table_entry, size_t i)
844
{
845
printf("SFO index table entry %zu\n", i);
846
printf("Key offset 0x%08X\n", sfo_index_table_entry.key_offset);
847
printf("Data type 0x%08X\n", sfo_index_table_entry.data_type);
848
printf("Data size 0x%08X\n", sfo_index_table_entry.data_size);
849
printf("Total data size 0x%08X\n", sfo_index_table_entry.data_total_size);
850
printf("Data offset 0x%08X\n", sfo_index_table_entry.data_offset);
851
printf("\n");
852
}
853
854
void CDImagePBP::PrintSFOTable(const SFOTable& sfo_table)
855
{
856
for (auto it = sfo_table.begin(); it != sfo_table.end(); ++it)
857
{
858
std::string key_value = it->first;
859
SFOTableDataValue data_value = it->second;
860
861
if (std::holds_alternative<std::string>(data_value))
862
printf("Key: %s, Data: %s\n", key_value.c_str(), std::get<std::string>(data_value).c_str());
863
else if (std::holds_alternative<u32>(data_value))
864
printf("Key: %s, Data: %u\n", key_value.c_str(), std::get<u32>(data_value));
865
}
866
}
867
#endif
868
869
bool CDImagePBP::HasSubImages() const
870
{
871
return m_disc_offsets.size() > 1;
872
}
873
874
std::string CDImagePBP::GetMetadata(std::string_view type) const
875
{
876
if (type == "title")
877
{
878
const std::string* title = LookupStringSFOTableEntry("TITLE", m_sfo_table);
879
if (title && !title->empty())
880
return *title;
881
}
882
883
return CDImage::GetMetadata(type);
884
}
885
886
u32 CDImagePBP::GetSubImageCount() const
887
{
888
return static_cast<u32>(m_disc_offsets.size());
889
}
890
891
u32 CDImagePBP::GetCurrentSubImage() const
892
{
893
return m_current_disc;
894
}
895
896
bool CDImagePBP::SwitchSubImage(u32 index, Error* error)
897
{
898
if (index >= m_disc_offsets.size())
899
return false;
900
901
const u32 old_disc = m_current_disc;
902
if (!OpenDisc(index, error))
903
{
904
// return to old disc, this should never fail... in theory.
905
if (!OpenDisc(old_disc, nullptr))
906
Panic("Failed to reopen old disc after switch.");
907
}
908
909
return true;
910
}
911
912
std::string CDImagePBP::GetSubImageMetadata(u32 index, std::string_view type) const
913
{
914
if (type == "title")
915
{
916
const std::string* title = LookupStringSFOTableEntry("TITLE", m_sfo_table);
917
if (title && !title->empty())
918
return fmt::format("{} (Disc {})", *title, index + 1);
919
}
920
921
return CDImage::GetSubImageMetadata(index, type);
922
}
923
924
s64 CDImagePBP::GetSizeOnDisk() const
925
{
926
return FileSystem::FSize64(m_file);
927
}
928
929
std::unique_ptr<CDImage> CDImage::OpenPBPImage(const char* path, Error* error)
930
{
931
std::unique_ptr<CDImagePBP> image = std::make_unique<CDImagePBP>();
932
if (!image->Open(path, error))
933
return {};
934
935
return image;
936
}
937
938