duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }