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