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_ppf.cpp (12865B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      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/file_system.h"
      9 #include "common/log.h"
     10 #include "common/path.h"
     11 
     12 #include <algorithm>
     13 #include <cerrno>
     14 #include <map>
     15 #include <unordered_map>
     16 
     17 Log_SetChannel(CDImagePPF);
     18 
     19 namespace {
     20 
     21 enum : u32
     22 {
     23   DESC_SIZE = 50,
     24   BLOCKCHECK_SIZE = 1024
     25 };
     26 
     27 class CDImagePPF : public CDImage
     28 {
     29 public:
     30   CDImagePPF();
     31   ~CDImagePPF() override;
     32 
     33   bool Open(const char* filename, std::unique_ptr<CDImage> parent_image);
     34 
     35   bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
     36   bool HasNonStandardSubchannel() const override;
     37   s64 GetSizeOnDisk() const override;
     38 
     39   std::string GetMetadata(std::string_view type) const override;
     40   std::string GetSubImageMetadata(u32 index, std::string_view type) const override;
     41 
     42   PrecacheResult Precache(ProgressCallback* progress = ProgressCallback::NullProgressCallback) override;
     43 
     44 protected:
     45   bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
     46 
     47 private:
     48   bool ReadV1Patch(std::FILE* fp);
     49   bool ReadV2Patch(std::FILE* fp);
     50   bool ReadV3Patch(std::FILE* fp);
     51   u32 ReadFileIDDiz(std::FILE* fp, u32 version);
     52 
     53   bool AddPatch(u64 offset, const u8* patch, u32 patch_size);
     54 
     55   std::unique_ptr<CDImage> m_parent_image;
     56   std::vector<u8> m_replacement_data;
     57   std::unordered_map<u32, u32> m_replacement_map;
     58   s64 m_patch_size = 0;
     59   u32 m_replacement_offset = 0;
     60 };
     61 
     62 } // namespace
     63 
     64 CDImagePPF::CDImagePPF() = default;
     65 
     66 CDImagePPF::~CDImagePPF() = default;
     67 
     68 bool CDImagePPF::Open(const char* filename, std::unique_ptr<CDImage> parent_image)
     69 {
     70   auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite);
     71   if (!fp)
     72   {
     73     ERROR_LOG("Failed to open '{}'", Path::GetFileName(filename));
     74     return false;
     75   }
     76 
     77   m_patch_size = FileSystem::FSize64(fp.get());
     78 
     79   u32 magic;
     80   if (std::fread(&magic, sizeof(magic), 1, fp.get()) != 1)
     81   {
     82     ERROR_LOG("Failed to read magic from '{}'", Path::GetFileName(filename));
     83     return false;
     84   }
     85 
     86   // work out the offset from the start of the parent image which we need to patch
     87   // i.e. the two second implicit pregap on data sectors
     88   if (parent_image->GetTrack(1).mode != TrackMode::Audio)
     89     m_replacement_offset = parent_image->GetIndex(1).start_lba_on_disc;
     90 
     91   // copy all the stuff from the parent image
     92   m_filename = parent_image->GetFileName();
     93   m_tracks = parent_image->GetTracks();
     94   m_indices = parent_image->GetIndices();
     95   m_parent_image = std::move(parent_image);
     96 
     97   if (magic == 0x33465050) // PPF3
     98     return ReadV3Patch(fp.get());
     99   else if (magic == 0x32465050) // PPF2
    100     return ReadV2Patch(fp.get());
    101   else if (magic == 0x31465050) // PPF1
    102     return ReadV1Patch(fp.get());
    103 
    104   ERROR_LOG("Unknown PPF magic {:08X}", magic);
    105   return false;
    106 }
    107 
    108 u32 CDImagePPF::ReadFileIDDiz(std::FILE* fp, u32 version)
    109 {
    110   const int lenidx = (version == 2) ? 4 : 2;
    111 
    112   u32 magic;
    113   if (std::fseek(fp, -(lenidx + 4), SEEK_END) != 0 || std::fread(&magic, sizeof(magic), 1, fp) != 1) [[unlikely]]
    114   {
    115     WARNING_LOG("Failed to read diz magic");
    116     return 0;
    117   }
    118 
    119   if (magic != 0x5A49442E) // .DIZ
    120     return 0;
    121 
    122   u32 dlen = 0;
    123   if (std::fseek(fp, -lenidx, SEEK_END) != 0 || std::fread(&dlen, lenidx, 1, fp) != 1) [[unlikely]]
    124   {
    125     WARNING_LOG("Failed to read diz length");
    126     return 0;
    127   }
    128 
    129   if (dlen > static_cast<u32>(std::ftell(fp))) [[unlikely]]
    130   {
    131     WARNING_LOG("diz length out of range");
    132     return 0;
    133   }
    134 
    135   std::string fdiz;
    136   fdiz.resize(dlen);
    137   if (std::fseek(fp, -(lenidx + 16 + static_cast<int>(dlen)), SEEK_END) != 0 ||
    138       std::fread(fdiz.data(), 1, dlen, fp) != dlen) [[unlikely]]
    139   {
    140     WARNING_LOG("Failed to read fdiz");
    141     return 0;
    142   }
    143 
    144   INFO_LOG("File_Id.diz: {}", fdiz);
    145   return dlen;
    146 }
    147 
    148 bool CDImagePPF::ReadV1Patch(std::FILE* fp)
    149 {
    150   char desc[DESC_SIZE + 1] = {};
    151   if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]]
    152   {
    153     ERROR_LOG("Failed to read description");
    154     return false;
    155   }
    156 
    157   u32 filelen;
    158   if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 56)
    159     [[unlikely]]
    160   {
    161     ERROR_LOG("Invalid ppf file");
    162     return false;
    163   }
    164 
    165   u32 count = filelen - 56;
    166   if (count <= 0)
    167     return false;
    168 
    169   if (std::fseek(fp, 56, SEEK_SET) != 0)
    170     return false;
    171 
    172   std::vector<u8> temp;
    173   while (count > 0)
    174   {
    175     u32 offset;
    176     u8 chunk_size;
    177     if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
    178       [[unlikely]]
    179     {
    180       ERROR_LOG("Incomplete ppf");
    181       return false;
    182     }
    183 
    184     temp.resize(chunk_size);
    185     if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]]
    186     {
    187       ERROR_LOG("Failed to read patch data");
    188       return false;
    189     }
    190 
    191     if (!AddPatch(offset, temp.data(), chunk_size)) [[unlikely]]
    192       return false;
    193 
    194     count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
    195   }
    196 
    197   INFO_LOG("Loaded {} replacement sectors from version 1 PPF", m_replacement_map.size());
    198   return true;
    199 }
    200 
    201 bool CDImagePPF::ReadV2Patch(std::FILE* fp)
    202 {
    203   char desc[DESC_SIZE + 1] = {};
    204   if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]]
    205   {
    206     ERROR_LOG("Failed to read description");
    207     return false;
    208   }
    209 
    210   INFO_LOG("Patch description: {}", desc);
    211 
    212   const u32 idlen = ReadFileIDDiz(fp, 2);
    213 
    214   u32 origlen;
    215   if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&origlen, sizeof(origlen), 1, fp) != 1) [[unlikely]]
    216   {
    217     ERROR_LOG("Failed to read size");
    218     return false;
    219   }
    220 
    221   std::vector<u8> temp;
    222   temp.resize(BLOCKCHECK_SIZE);
    223   if (std::fread(temp.data(), 1, BLOCKCHECK_SIZE, fp) != BLOCKCHECK_SIZE) [[unlikely]]
    224   {
    225     ERROR_LOG("Failed to read blockcheck data");
    226     return false;
    227   }
    228 
    229   // do blockcheck
    230   {
    231     u32 blockcheck_src_sector = 16 + m_replacement_offset;
    232     u32 blockcheck_src_offset = 32;
    233 
    234     std::vector<u8> src_sector(RAW_SECTOR_SIZE);
    235     if (m_parent_image->Seek(blockcheck_src_sector) && m_parent_image->ReadRawSector(src_sector.data(), nullptr))
    236     {
    237       if (std::memcmp(&src_sector[blockcheck_src_offset], temp.data(), BLOCKCHECK_SIZE) != 0)
    238         WARNING_LOG("Blockcheck failed. The patch may not apply correctly.");
    239     }
    240     else
    241     {
    242       WARNING_LOG("Failed to read blockcheck sector {}", blockcheck_src_sector);
    243     }
    244   }
    245 
    246   u32 filelen;
    247   if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 1084)
    248     [[unlikely]]
    249   {
    250     ERROR_LOG("Invalid ppf file");
    251     return false;
    252   }
    253 
    254   u32 count = filelen - 1084;
    255   if (idlen > 0)
    256     count -= (idlen + 38);
    257 
    258   if (count <= 0)
    259     return false;
    260 
    261   if (std::fseek(fp, 1084, SEEK_SET) != 0)
    262     return false;
    263 
    264   while (count > 0)
    265   {
    266     u32 offset;
    267     u8 chunk_size;
    268     if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
    269       [[unlikely]]
    270     {
    271       ERROR_LOG("Incomplete ppf");
    272       return false;
    273     }
    274 
    275     temp.resize(chunk_size);
    276     if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]]
    277     {
    278       ERROR_LOG("Failed to read patch data");
    279       return false;
    280     }
    281 
    282     if (!AddPatch(offset, temp.data(), chunk_size))
    283       return false;
    284 
    285     count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
    286   }
    287 
    288   INFO_LOG("Loaded {} replacement sectors from version 2 PPF", m_replacement_map.size());
    289   return true;
    290 }
    291 
    292 bool CDImagePPF::ReadV3Patch(std::FILE* fp)
    293 {
    294   char desc[DESC_SIZE + 1] = {};
    295   if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE)
    296   {
    297     ERROR_LOG("Failed to read description");
    298     return false;
    299   }
    300 
    301   INFO_LOG("Patch description: {}", desc);
    302 
    303   u32 idlen = ReadFileIDDiz(fp, 3);
    304 
    305   u8 image_type;
    306   u8 block_check;
    307   u8 undo;
    308   if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&image_type, sizeof(image_type), 1, fp) != 1 ||
    309       std::fread(&block_check, sizeof(block_check), 1, fp) != 1 || std::fread(&undo, sizeof(undo), 1, fp) != 1)
    310   {
    311     ERROR_LOG("Failed to read headers");
    312     return false;
    313   }
    314 
    315   // TODO: Blockcheck
    316 
    317   std::fseek(fp, 0, SEEK_END);
    318   u32 count = static_cast<u32>(std::ftell(fp));
    319 
    320   u32 seekpos = (block_check) ? 1084 : 60;
    321   if (seekpos >= count)
    322   {
    323     ERROR_LOG("File is too short");
    324     return false;
    325   }
    326 
    327   count -= seekpos;
    328   if (idlen > 0)
    329   {
    330     const u32 extralen = idlen + 18 + 16 + 2;
    331     if (count < extralen)
    332     {
    333       ERROR_LOG("File is too short (diz)");
    334       return false;
    335     }
    336 
    337     count -= extralen;
    338   }
    339 
    340   if (std::fseek(fp, seekpos, SEEK_SET) != 0)
    341     return false;
    342 
    343   std::vector<u8> temp;
    344 
    345   while (count > 0)
    346   {
    347     u64 offset;
    348     u8 chunk_size;
    349     if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
    350     {
    351       ERROR_LOG("Incomplete ppf");
    352       return false;
    353     }
    354 
    355     temp.resize(chunk_size);
    356     if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size)
    357     {
    358       ERROR_LOG("Failed to read patch data");
    359       return false;
    360     }
    361 
    362     if (!AddPatch(offset, temp.data(), chunk_size))
    363       return false;
    364 
    365     count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
    366   }
    367 
    368   INFO_LOG("Loaded {} replacement sectors from version 3 PPF", m_replacement_map.size());
    369   return true;
    370 }
    371 
    372 bool CDImagePPF::AddPatch(u64 offset, const u8* patch, u32 patch_size)
    373 {
    374   DEBUG_LOG("Starting applying patch of {} bytes at at offset {}", patch_size, offset);
    375 
    376   while (patch_size > 0)
    377   {
    378     const u32 sector_index = Truncate32(offset / RAW_SECTOR_SIZE) + m_replacement_offset;
    379     const u32 sector_offset = Truncate32(offset % RAW_SECTOR_SIZE);
    380     if (sector_index >= m_parent_image->GetLBACount())
    381     {
    382       ERROR_LOG("Sector {} in patch is out of range", sector_index);
    383       return false;
    384     }
    385 
    386     const u32 bytes_to_patch = std::min(patch_size, RAW_SECTOR_SIZE - sector_offset);
    387 
    388     auto iter = m_replacement_map.find(sector_index);
    389     if (iter == m_replacement_map.end())
    390     {
    391       const u32 replacement_buffer_start = static_cast<u32>(m_replacement_data.size());
    392       m_replacement_data.resize(m_replacement_data.size() + RAW_SECTOR_SIZE);
    393       if (!m_parent_image->Seek(sector_index) ||
    394           !m_parent_image->ReadRawSector(&m_replacement_data[replacement_buffer_start], nullptr))
    395       {
    396         ERROR_LOG("Failed to read sector {} from parent image", sector_index);
    397         return false;
    398       }
    399 
    400       iter = m_replacement_map.emplace(sector_index, replacement_buffer_start).first;
    401     }
    402 
    403     // patch it!
    404     DEBUG_LOG("  Patching {} bytes at sector {} offset {}", bytes_to_patch, sector_index, sector_offset);
    405     std::memcpy(&m_replacement_data[iter->second + sector_offset], patch, bytes_to_patch);
    406     offset += bytes_to_patch;
    407     patch += bytes_to_patch;
    408     patch_size -= bytes_to_patch;
    409   }
    410 
    411   return true;
    412 }
    413 
    414 bool CDImagePPF::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
    415 {
    416   return m_parent_image->ReadSubChannelQ(subq, index, lba_in_index);
    417 }
    418 
    419 bool CDImagePPF::HasNonStandardSubchannel() const
    420 {
    421   return m_parent_image->HasNonStandardSubchannel();
    422 }
    423 
    424 std::string CDImagePPF::GetMetadata(std::string_view type) const
    425 {
    426   return m_parent_image->GetMetadata(type);
    427 }
    428 
    429 std::string CDImagePPF::GetSubImageMetadata(u32 index, std::string_view type) const
    430 {
    431   // We only support a single sub-image for patched games.
    432   std::string ret;
    433   if (index == 0)
    434     ret = m_parent_image->GetSubImageMetadata(index, type);
    435 
    436   return ret;
    437 }
    438 
    439 CDImage::PrecacheResult CDImagePPF::Precache(ProgressCallback* progress /*= ProgressCallback::NullProgressCallback*/)
    440 {
    441   return m_parent_image->Precache(progress);
    442 }
    443 
    444 bool CDImagePPF::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
    445 {
    446   DebugAssert(index.file_index == 0);
    447 
    448   const u32 sector_number = index.start_lba_on_disc + lba_in_index;
    449   const auto it = m_replacement_map.find(sector_number);
    450   if (it == m_replacement_map.end())
    451     return m_parent_image->ReadSectorFromIndex(buffer, index, lba_in_index);
    452 
    453   std::memcpy(buffer, &m_replacement_data[it->second], RAW_SECTOR_SIZE);
    454   return true;
    455 }
    456 
    457 s64 CDImagePPF::GetSizeOnDisk() const
    458 {
    459   return m_patch_size + m_parent_image->GetSizeOnDisk();
    460 }
    461 
    462 std::unique_ptr<CDImage>
    463 CDImage::OverlayPPFPatch(const char* filename, std::unique_ptr<CDImage> parent_image,
    464                          ProgressCallback* progress /* = ProgressCallback::NullProgressCallback */)
    465 {
    466   std::unique_ptr<CDImagePPF> ppf_image = std::make_unique<CDImagePPF>();
    467   if (!ppf_image->Open(filename, std::move(parent_image)))
    468     return {};
    469 
    470   return ppf_image;
    471 }