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

iso_reader.cpp (11886B)


      1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "iso_reader.h"
      5 #include "cd_image.h"
      6 
      7 #include "common/error.h"
      8 #include "common/log.h"
      9 #include "common/string_util.h"
     10 
     11 #include "fmt/format.h"
     12 
     13 #include <cctype>
     14 
     15 Log_SetChannel(IsoReader);
     16 
     17 IsoReader::IsoReader() = default;
     18 
     19 IsoReader::~IsoReader() = default;
     20 
     21 std::string_view IsoReader::RemoveVersionIdentifierFromPath(std::string_view path)
     22 {
     23   const std::string_view::size_type pos = path.find(';');
     24   return (pos != std::string_view::npos) ? path.substr(0, pos) : path;
     25 }
     26 
     27 bool IsoReader::Open(CDImage* image, u32 track_number, Error* error)
     28 {
     29   m_image = image;
     30   m_track_number = track_number;
     31 
     32   if (!ReadPVD(error))
     33     return false;
     34 
     35   return true;
     36 }
     37 
     38 bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error)
     39 {
     40   if (!m_image->Seek(m_track_number, lsn))
     41   {
     42     Error::SetString(error, fmt::format("Failed to seek to LSN #{}", lsn));
     43     return false;
     44   }
     45 
     46   if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buf) != 1)
     47   {
     48     Error::SetString(error, fmt::format("Failed to read LSN #{}", lsn));
     49     return false;
     50   }
     51 
     52   return true;
     53 }
     54 
     55 bool IsoReader::ReadPVD(Error* error)
     56 {
     57   // volume descriptor start at sector 16
     58   static constexpr u32 START_SECTOR = 16;
     59 
     60   // try only a maximum of 256 volume descriptors
     61   for (u32 i = 0; i < 256; i++)
     62   {
     63     u8 buffer[SECTOR_SIZE];
     64     if (!ReadSector(buffer, START_SECTOR + i, error))
     65       return false;
     66 
     67     const ISOVolumeDescriptorHeader* header = reinterpret_cast<ISOVolumeDescriptorHeader*>(buffer);
     68     if (std::memcmp(header->standard_identifier, "CD001", 5) != 0)
     69       continue;
     70     else if (header->type_code != 1)
     71       continue;
     72     else if (header->type_code == 255)
     73       break;
     74 
     75     m_pvd_lba = START_SECTOR + i;
     76     std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor));
     77     DEV_LOG("ISOReader: PVD found at index {}", i);
     78     return true;
     79   }
     80 
     81   Error::SetString(error, "Failed to find the Primary Volume Descriptor.");
     82   return false;
     83 }
     84 
     85 std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(std::string_view path, Error* error)
     86 {
     87   const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
     88   if (path.empty() || path == "/" || path == "\\")
     89   {
     90     // locating the root directory
     91     return *root_de;
     92   }
     93 
     94   // start at the root directory
     95   u8 sector_buffer[SECTOR_SIZE];
     96   return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le, error);
     97 }
     98 
     99 std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset)
    100 {
    101   const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(sector + de_sector_offset);
    102   if ((sizeof(ISODirectoryEntry) + de->filename_length) > de->entry_length ||
    103       (sizeof(ISODirectoryEntry) + de->filename_length + de_sector_offset) > SECTOR_SIZE)
    104   {
    105     return std::string_view();
    106   }
    107 
    108   const char* str = reinterpret_cast<const char*>(sector + de_sector_offset + sizeof(ISODirectoryEntry));
    109   if (de->filename_length == 1)
    110   {
    111     if (str[0] == '\0')
    112       return ".";
    113     else if (str[0] == '\1')
    114       return "..";
    115   }
    116 
    117   // Strip any version information like the PS2 BIOS does.
    118   u32 length_without_version = 0;
    119   for (; length_without_version < de->filename_length; length_without_version++)
    120   {
    121     if (str[length_without_version] == ';' || str[length_without_version] == '\0')
    122       break;
    123   }
    124 
    125   return std::string_view(str, length_without_version);
    126 }
    127 
    128 std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(std::string_view path, u8* sector_buffer,
    129                                                                   u32 directory_record_lba, u32 directory_record_size,
    130                                                                   Error* error)
    131 {
    132   if (directory_record_size == 0)
    133   {
    134     Error::SetString(error, fmt::format("Directory entry record size 0 while looking for '{}'", path));
    135     return std::nullopt;
    136   }
    137 
    138   // strip any leading slashes
    139   size_t path_component_start = 0;
    140   while (path_component_start < path.length() &&
    141          (path[path_component_start] == '/' || path[path_component_start] == '\\'))
    142   {
    143     path_component_start++;
    144   }
    145 
    146   size_t path_component_length = 0;
    147   while ((path_component_start + path_component_length) < path.length() &&
    148          path[path_component_start + path_component_length] != '/' &&
    149          path[path_component_start + path_component_length] != '\\')
    150   {
    151     path_component_length++;
    152   }
    153 
    154   const std::string_view path_component = path.substr(path_component_start, path_component_length);
    155   if (path_component.empty())
    156   {
    157     Error::SetString(error, fmt::format("Empty path component in {}", path));
    158     return std::nullopt;
    159   }
    160 
    161   // start reading directory entries
    162   const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
    163   for (u32 i = 0; i < num_sectors; i++)
    164   {
    165     if (!ReadSector(sector_buffer, directory_record_lba + i, error))
    166       return std::nullopt;
    167 
    168     u32 sector_offset = 0;
    169     while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
    170     {
    171       const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(&sector_buffer[sector_offset]);
    172       if (de->entry_length < sizeof(ISODirectoryEntry))
    173         break;
    174 
    175       const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
    176       sector_offset += de->entry_length;
    177 
    178       // Empty file would be pretty strange..
    179       if (de_filename.empty() || de_filename == "." || de_filename == "..")
    180         continue;
    181 
    182       if (de_filename.length() != path_component.length() ||
    183           StringUtil::Strncasecmp(de_filename.data(), path_component.data(), path_component.length()) != 0)
    184       {
    185         continue;
    186       }
    187 
    188       // found it. is this the file we're looking for?
    189       if ((path_component_start + path_component_length) == path.length())
    190         return *de;
    191 
    192       // if it is a directory, recurse into it
    193       if (de->flags & ISODirectoryEntryFlag_Directory)
    194       {
    195         return LocateFile(path.substr(path_component_start + path_component_length), sector_buffer, de->location_le,
    196                           de->length_le, error);
    197       }
    198 
    199       // we're looking for a directory but got a file
    200       Error::SetString(error, fmt::format("Looking for directory '{}' but got file", path_component));
    201       return std::nullopt;
    202     }
    203   }
    204 
    205   Error::SetString(error, fmt::format("Path component '{}' not found", path_component));
    206   return std::nullopt;
    207 }
    208 
    209 std::vector<std::string> IsoReader::GetFilesInDirectory(std::string_view path, Error* error)
    210 {
    211   std::string base_path(path);
    212   u32 directory_record_lsn;
    213   u32 directory_record_length;
    214   if (base_path.empty())
    215   {
    216     // root directory
    217     const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
    218     directory_record_lsn = root_de->location_le;
    219     directory_record_length = root_de->length_le;
    220   }
    221   else
    222   {
    223     auto directory_de = LocateFile(base_path, error);
    224     if (!directory_de.has_value())
    225       return {};
    226 
    227     if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0)
    228     {
    229       Error::SetString(error, fmt::format("Path '{}' is not a directory, can't list", path));
    230       return {};
    231     }
    232 
    233     directory_record_lsn = directory_de->location_le;
    234     directory_record_length = directory_de->length_le;
    235 
    236     if (base_path[base_path.size() - 1] != '/')
    237       base_path += '/';
    238   }
    239 
    240   // start reading directory entries
    241   const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
    242   std::vector<std::string> files;
    243   u8 sector_buffer[SECTOR_SIZE];
    244   for (u32 i = 0; i < num_sectors; i++)
    245   {
    246     if (!ReadSector(sector_buffer, directory_record_lsn + i, error))
    247       break;
    248 
    249     u32 sector_offset = 0;
    250     while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
    251     {
    252       const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(&sector_buffer[sector_offset]);
    253       if (de->entry_length < sizeof(ISODirectoryEntry))
    254         break;
    255 
    256       const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
    257       sector_offset += de->entry_length;
    258 
    259       // Empty file would be pretty strange..
    260       if (de_filename.empty() || de_filename == "." || de_filename == "..")
    261         continue;
    262 
    263       files.push_back(fmt::format("{}{}", base_path, de_filename));
    264     }
    265   }
    266 
    267   return files;
    268 }
    269 
    270 std::vector<std::pair<std::string, IsoReader::ISODirectoryEntry>>
    271 IsoReader::GetEntriesInDirectory(std::string_view path, Error* error /*= nullptr*/)
    272 {
    273   std::string base_path(path);
    274   u32 directory_record_lsn;
    275   u32 directory_record_length;
    276   if (base_path.empty())
    277   {
    278     // root directory
    279     const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
    280     directory_record_lsn = root_de->location_le;
    281     directory_record_length = root_de->length_le;
    282   }
    283   else
    284   {
    285     auto directory_de = LocateFile(base_path, error);
    286     if (!directory_de.has_value())
    287       return {};
    288 
    289     if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0)
    290     {
    291       Error::SetString(error, fmt::format("Path '{}' is not a directory, can't list", path));
    292       return {};
    293     }
    294 
    295     directory_record_lsn = directory_de->location_le;
    296     directory_record_length = directory_de->length_le;
    297 
    298     if (base_path[base_path.size() - 1] != '/')
    299       base_path += '/';
    300   }
    301 
    302   // start reading directory entries
    303   const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
    304   std::vector<std::pair<std::string, IsoReader::ISODirectoryEntry>> files;
    305   u8 sector_buffer[SECTOR_SIZE];
    306   for (u32 i = 0; i < num_sectors; i++)
    307   {
    308     if (!ReadSector(sector_buffer, directory_record_lsn + i, error))
    309       break;
    310 
    311     u32 sector_offset = 0;
    312     while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
    313     {
    314       const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(&sector_buffer[sector_offset]);
    315       if (de->entry_length < sizeof(ISODirectoryEntry))
    316         break;
    317 
    318       const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
    319       sector_offset += de->entry_length;
    320 
    321       // Empty file would be pretty strange..
    322       if (de_filename.empty() || de_filename == "." || de_filename == "..")
    323         continue;
    324 
    325       files.emplace_back(fmt::format("{}{}", base_path, de_filename), *de);
    326     }
    327   }
    328 
    329   return files;
    330 }
    331 
    332 bool IsoReader::FileExists(std::string_view path, Error* error)
    333 {
    334   auto de = LocateFile(path, error);
    335   if (!de)
    336     return false;
    337 
    338   return (de->flags & ISODirectoryEntryFlag_Directory) == 0;
    339 }
    340 
    341 bool IsoReader::DirectoryExists(std::string_view path, Error* error)
    342 {
    343   auto de = LocateFile(path, error);
    344   if (!de)
    345     return false;
    346 
    347   return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory;
    348 }
    349 
    350 bool IsoReader::ReadFile(std::string_view path, std::vector<u8>* data, Error* error)
    351 {
    352   auto de = LocateFile(path, error);
    353   if (!de)
    354     return false;
    355 
    356   return ReadFile(de.value(), data, error);
    357 }
    358 
    359 bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error /*= nullptr*/)
    360 {
    361   if (de.flags & ISODirectoryEntryFlag_Directory)
    362   {
    363     Error::SetString(error, "File is a directory");
    364     return false;
    365   }
    366 
    367   if (de.length_le == 0)
    368   {
    369     data->clear();
    370     return true;
    371   }
    372 
    373   const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
    374   data->resize(num_sectors * static_cast<size_t>(SECTOR_SIZE));
    375   for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++)
    376   {
    377     if (!ReadSector(data->data() + (i * SECTOR_SIZE), lsn, error))
    378       return false;
    379   }
    380 
    381   // Might not be sector aligned, so reduce it back.
    382   data->resize(de.length_le);
    383   return true;
    384 }