From 5442242c64682c3c89142a7fde6b952b4c94da9e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 29 Nov 2023 20:43:39 +1000 Subject: [PATCH] IsoReader: Add GetEntriesInDirectory() --- src/core/system.cpp | 18 +- src/util/iso_reader.cpp | 361 ++++++++++++++++++++++++---------------- src/util/iso_reader.h | 43 +++-- 3 files changed, 261 insertions(+), 161 deletions(-) diff --git a/src/core/system.cpp b/src/core/system.cpp index 5d3e93972..7d3a03834 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -96,8 +96,8 @@ static std::optional InternalGetExtendedSaveStateInfo(Byt static bool LoadEXE(const char* filename); -static std::string GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories); -static bool ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, +static std::string GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories); +static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name, std::vector* out_executable_data); static bool LoadBIOS(const std::string& override_bios_path); @@ -561,7 +561,7 @@ std::string System::GetGameHashId(GameHash hash) bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash) { - ISOReader iso; + IsoReader iso; if (!iso.Open(cdi, 1)) { if (out_id) @@ -589,7 +589,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash XXH64_reset(state, 0x4242D00C); XXH64_update(state, exe_name.c_str(), exe_name.size()); XXH64_update(state, exe_buffer.data(), exe_buffer.size()); - XXH64_update(state, &iso.GetPVD(), sizeof(ISOReader::ISOPrimaryVolumeDescriptor)); + XXH64_update(state, &iso.GetPVD(), sizeof(IsoReader::ISOPrimaryVolumeDescriptor)); XXH64_update(state, &track_1_length, sizeof(track_1_length)); const GameHash hash = XXH64_digest(state); XXH64_freeState(state); @@ -636,7 +636,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash return true; } -std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories) +std::string System::GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories) { // Read SYSTEM.CNF std::vector system_cnf_data; @@ -728,7 +728,7 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire std::string System::GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories) { - ISOReader iso; + IsoReader iso; if (!iso.Open(cdi, 1)) return {}; @@ -738,14 +738,14 @@ std::string System::GetExecutableNameForImage(CDImage* cdi, bool strip_subdirect bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, std::vector* out_executable_data) { - ISOReader iso; + IsoReader iso; if (!iso.Open(cdi, 1)) return false; return ReadExecutableFromImage(iso, out_executable_name, out_executable_data); } -bool System::ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, +bool System::ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name, std::vector* out_executable_data) { const std::string executable_path = GetExecutableNameForImage(iso, false); @@ -815,7 +815,7 @@ DiscRegion System::GetRegionForImage(CDImage* cdi) if (system_area_region != DiscRegion::Other) return system_area_region; - ISOReader iso; + IsoReader iso; if (!iso.Open(cdi, 1)) return DiscRegion::NonPS1; diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp index 2797ed474..fecfc6c86 100644 --- a/src/util/iso_reader.cpp +++ b/src/util/iso_reader.cpp @@ -1,207 +1,235 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "iso_reader.h" #include "cd_image.h" + +#include "common/error.h" #include "common/log.h" +#include "common/string_util.h" + +#include "fmt/format.h" + #include -Log_SetChannel(ISOReader); -static bool FilenamesEqual(const char* a, const char* b, u32 length) -{ - u32 pos = 0; - for (; pos < length && *a != '\0' && *b != '\0'; pos++) - { - if (std::tolower(*(a++)) != std::tolower(*(b++))) - return false; - } +Log_SetChannel(IsoReader); - return true; -} +IsoReader::IsoReader() = default; -ISOReader::ISOReader() = default; +IsoReader::~IsoReader() = default; -ISOReader::~ISOReader() = default; +std::string_view IsoReader::RemoveVersionIdentifierFromPath(const std::string_view& path) +{ + const std::string_view::size_type pos = path.find(';'); + return (pos != std::string_view::npos) ? path.substr(0, pos) : path; +} -bool ISOReader::Open(CDImage* image, u32 track_number) +bool IsoReader::Open(CDImage* image, u32 track_number, Error* error) { m_image = image; m_track_number = track_number; - if (!ReadPVD()) + + if (!ReadPVD(error)) return false; return true; } -bool ISOReader::ReadPVD() +bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error) { - // volume descriptor start at sector 16 - if (!m_image->Seek(m_track_number, 16)) + if (!m_image->Seek(m_track_number, lsn)) + { + Error::SetString(error, fmt::format("Failed to seek to LSN #{}", lsn)); + return false; + } + + if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buf) != 1) + { + Error::SetString(error, fmt::format("Failed to read LSN #{}", lsn)); return false; + } + + return true; +} + +bool IsoReader::ReadPVD(Error* error) +{ + // volume descriptor start at sector 16 + static constexpr u32 START_SECTOR = 16; // try only a maximum of 256 volume descriptors for (u32 i = 0; i < 256; i++) { u8 buffer[SECTOR_SIZE]; - if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buffer) != 1) + if (!ReadSector(buffer, START_SECTOR + i, error)) return false; const ISOVolumeDescriptorHeader* header = reinterpret_cast(buffer); - if (header->type_code != 1) + if (std::memcmp(header->standard_identifier, "CD001", 5) != 0) + continue; + else if (header->type_code != 1) continue; else if (header->type_code == 255) break; std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor)); - Log_DebugPrintf("PVD found at index %u", i); + Log_DevFmt("ISOReader: PVD found at index {}", i); return true; } - Log_ErrorPrint("PVD not found"); + Error::SetString(error, "Failed to find the Primary Volume Descriptor."); return false; } -std::optional ISOReader::LocateFile(const char* path) +std::optional IsoReader::LocateFile(const std::string_view& path, Error* error) { - u8 sector_buffer[SECTOR_SIZE]; - const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); - if (*path == '\0' || std::strcmp(path, "/") == 0) + if (path.empty() || path == "/" || path == "\\") { // locating the root directory return *root_de; } // start at the root directory - return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le); + u8 sector_buffer[SECTOR_SIZE]; + return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le, error); } -std::optional ISOReader::LocateFile(const char* path, u8* sector_buffer, - u32 directory_record_lba, u32 directory_record_size) +std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset) +{ + const ISODirectoryEntry* de = reinterpret_cast(sector + de_sector_offset); + if ((sizeof(ISODirectoryEntry) + de->filename_length) > de->entry_length || + (sizeof(ISODirectoryEntry) + de->filename_length + de_sector_offset) > SECTOR_SIZE) + { + return std::string_view(); + } + + const char* str = reinterpret_cast(sector + de_sector_offset + sizeof(ISODirectoryEntry)); + if (de->filename_length == 1) + { + if (str[0] == '\0') + return "."; + else if (str[0] == '\1') + return ".."; + } + + // Strip any version information like the PS2 BIOS does. + u32 length_without_version = 0; + for (; length_without_version < de->filename_length; length_without_version++) + { + if (str[length_without_version] == ';' || str[length_without_version] == '\0') + break; + } + + return std::string_view(str, length_without_version); +} + +std::optional IsoReader::LocateFile(const std::string_view& path, u8* sector_buffer, + u32 directory_record_lba, u32 directory_record_size, + Error* error) { if (directory_record_size == 0) { - Log_ErrorPrintf("Directory entry record size 0 while looking for '%s'", path); + Error::SetString(error, fmt::format("Directory entry record size 0 while looking for '{}'", path)); return std::nullopt; } // strip any leading slashes - const char* path_component_start = path; - while (*path_component_start == '/' || *path_component_start == '\\') + size_t path_component_start = 0; + while (path_component_start < path.length() && + (path[path_component_start] == '/' || path[path_component_start] == '\\')) + { path_component_start++; + } - u32 path_component_length = 0; - const char* path_component_end = path_component_start; - while (*path_component_end != '\0' && *path_component_end != '/' && *path_component_end != '\\') + size_t path_component_length = 0; + while ((path_component_start + path_component_length) < path.length() && + path[path_component_start + path_component_length] != '/' && + path[path_component_start + path_component_length] != '\\') { path_component_length++; - path_component_end++; } - // start reading directory entries - const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE; - if (!m_image->Seek(m_track_number, directory_record_lba)) + const std::string_view path_component = path.substr(path_component_start, path_component_length); + if (path_component.empty()) { - Log_ErrorPrintf("Seek to LBA %u failed", directory_record_lba); + Error::SetString(error, fmt::format("Empty path component in {}", path)); return std::nullopt; } + // start reading directory entries + const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE; for (u32 i = 0; i < num_sectors; i++) { - if (m_image->Read(CDImage::ReadMode::DataOnly, 1, sector_buffer) != 1) - { - Log_ErrorPrintf("Failed to read LBA %u", directory_record_lba + i); + if (!ReadSector(sector_buffer, directory_record_lba + i, error)) return std::nullopt; - } u32 sector_offset = 0; while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) { const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); - const char* de_filename = - reinterpret_cast(§or_buffer[sector_offset + sizeof(ISODirectoryEntry)]); - if ((sector_offset + de->entry_length) > SECTOR_SIZE || de->filename_length > de->entry_length || - de->entry_length < sizeof(ISODirectoryEntry)) - { + if (de->entry_length < sizeof(ISODirectoryEntry)) break; - } + const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset); sector_offset += de->entry_length; - // skip current/parent directory - if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1')) - continue; - - // check filename length - if (de->filename_length < path_component_length) + // Empty file would be pretty strange.. + if (de_filename.empty() || de_filename == "." || de_filename == "..") continue; - if (de->flags & ISODirectoryEntryFlag_Directory) - { - // directories don't have the version? so check the length instead - if (de->filename_length != path_component_length || - !FilenamesEqual(de_filename, path_component_start, path_component_length)) - { - continue; - } - } - else + if (de_filename.length() != path_component.length() || + StringUtil::Strncasecmp(de_filename.data(), path_component.data(), path_component.length()) != 0) { - // compare filename - if (!FilenamesEqual(de_filename, path_component_start, path_component_length) || - de_filename[path_component_length] != ';') - { - continue; - } + continue; } // found it. is this the file we're looking for? - if (*path_component_end == '\0') + if ((path_component_start + path_component_length) == path.length()) return *de; // if it is a directory, recurse into it if (de->flags & ISODirectoryEntryFlag_Directory) - return LocateFile(path_component_end, sector_buffer, de->location_le, de->length_le); + { + return LocateFile(path.substr(path_component_start + path_component_length), sector_buffer, de->location_le, + de->length_le, error); + } // we're looking for a directory but got a file - Log_ErrorPrintf("Looking for directory but got file"); + Error::SetString(error, fmt::format("Looking for directory '{}' but got file", path_component)); return std::nullopt; } } - std::string temp(path_component_start, path_component_length); - Log_ErrorPrintf("Path component '%s' not found", temp.c_str()); + Error::SetString(error, fmt::format("Path component '{}' not found", path_component)); return std::nullopt; } -std::vector ISOReader::GetFilesInDirectory(const char* path) +std::vector IsoReader::GetFilesInDirectory(const std::string_view& path, Error* error) { - std::string base_path = path; - u32 directory_record_lba; + std::string base_path(path); + u32 directory_record_lsn; u32 directory_record_length; if (base_path.empty()) { // root directory const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); - directory_record_lba = root_de->location_le; + directory_record_lsn = root_de->location_le; directory_record_length = root_de->length_le; } else { - auto directory_de = LocateFile(base_path.c_str()); - if (!directory_de) - { - Log_ErrorPrintf("Directory entry not found for '%s'", path); + auto directory_de = LocateFile(base_path, error); + if (!directory_de.has_value()) return {}; - } if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0) { - Log_ErrorPrintf("Path '%s' is not a directory, can't list", path); + Error::SetString(error, fmt::format("Path '{}' is not a directory, can't list", path)); return {}; } - directory_record_lba = directory_de->location_le; + directory_record_lsn = directory_de->location_le; directory_record_length = directory_de->length_le; if (base_path[base_path.size() - 1] != '/') @@ -210,92 +238,147 @@ std::vector ISOReader::GetFilesInDirectory(const char* path) // start reading directory entries const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE; - if (!m_image->Seek(m_track_number, directory_record_lba)) - { - Log_ErrorPrintf("Seek to LBA %u failed", directory_record_lba); - return {}; - } - std::vector files; u8 sector_buffer[SECTOR_SIZE]; for (u32 i = 0; i < num_sectors; i++) { - if (m_image->Read(CDImage::ReadMode::DataOnly, 1, sector_buffer) != 1) - { - Log_ErrorPrintf("Failed to read LBA %u", directory_record_lba + i); + if (!ReadSector(sector_buffer, directory_record_lsn + i, error)) break; - } u32 sector_offset = 0; while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) { const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); - const char* de_filename = - reinterpret_cast(§or_buffer[sector_offset + sizeof(ISODirectoryEntry)]); - if ((sector_offset + de->entry_length) > SECTOR_SIZE || de->filename_length > de->entry_length || - de->entry_length < sizeof(ISODirectoryEntry)) - { + if (de->entry_length < sizeof(ISODirectoryEntry)) break; - } + const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset); sector_offset += de->entry_length; - // skip current/parent directory - if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1')) + // Empty file would be pretty strange.. + if (de_filename.empty() || de_filename == "." || de_filename == "..") continue; - // strip off terminator/file version - std::string filename(de_filename, de->filename_length); - std::string::size_type pos = filename.rfind(';'); - if (pos == std::string::npos) - { - Log_ErrorPrintf("Invalid filename '%s'", filename.c_str()); + files.push_back(fmt::format("{}{}", base_path, de_filename)); + } + } + + return files; +} + +std::vector> +IsoReader::GetEntriesInDirectory(const std::string_view& path, Error* error /*= nullptr*/) +{ + std::string base_path(path); + u32 directory_record_lsn; + u32 directory_record_length; + if (base_path.empty()) + { + // root directory + const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); + directory_record_lsn = root_de->location_le; + directory_record_length = root_de->length_le; + } + else + { + auto directory_de = LocateFile(base_path, error); + if (!directory_de.has_value()) + return {}; + + if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0) + { + Error::SetString(error, fmt::format("Path '{}' is not a directory, can't list", path)); + return {}; + } + + directory_record_lsn = directory_de->location_le; + directory_record_length = directory_de->length_le; + + if (base_path[base_path.size() - 1] != '/') + base_path += '/'; + } + + // start reading directory entries + const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + std::vector> files; + u8 sector_buffer[SECTOR_SIZE]; + for (u32 i = 0; i < num_sectors; i++) + { + if (!ReadSector(sector_buffer, directory_record_lsn + i, error)) + break; + + u32 sector_offset = 0; + while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE) + { + const ISODirectoryEntry* de = reinterpret_cast(§or_buffer[sector_offset]); + if (de->entry_length < sizeof(ISODirectoryEntry)) + break; + + const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset); + sector_offset += de->entry_length; + + // Empty file would be pretty strange.. + if (de_filename.empty() || de_filename == "." || de_filename == "..") continue; - } - filename.erase(pos); - if (!filename.empty()) - files.push_back(base_path + filename); + files.emplace_back(fmt::format("{}{}", base_path, de_filename), *de); } } return files; } -bool ISOReader::ReadFile(const char* path, std::vector* data) +bool IsoReader::FileExists(const std::string_view& path, Error* error) { - auto de = LocateFile(path); + auto de = LocateFile(path, error); if (!de) - { - Log_ErrorPrintf("File not found: '%s'", path); return false; - } - if (de->flags & ISODirectoryEntryFlag_Directory) - { - Log_ErrorPrintf("File is a directory: '%s'", path); + + return (de->flags & ISODirectoryEntryFlag_Directory) == 0; +} + +bool IsoReader::DirectoryExists(const std::string_view& path, Error* error) +{ + auto de = LocateFile(path, error); + if (!de) return false; - } - if (!m_image->Seek(m_track_number, de->location_le)) + return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory; +} + +bool IsoReader::ReadFile(const std::string_view& path, std::vector* data, Error* error) +{ + auto de = LocateFile(path, error); + if (!de) return false; - if (de->length_le == 0) + return ReadFile(de.value(), data, error); +} + +bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error /*= nullptr*/) +{ + if (de.flags & ISODirectoryEntryFlag_Directory) + { + Error::SetString(error, "File is a directory"); + return false; + } + + if (de.length_le == 0) { data->clear(); return true; } - const u32 num_sectors = (de->length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; - data->resize(num_sectors * u64(SECTOR_SIZE)); - if (m_image->Read(CDImage::ReadMode::DataOnly, num_sectors, data->data()) != num_sectors) - return false; + static_assert(sizeof(size_t) == sizeof(u64)); + const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; + data->resize(num_sectors * static_cast(SECTOR_SIZE)); + for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++) + { + if (!ReadSector(data->data() + (i * SECTOR_SIZE), lsn, error)) + return false; + } - data->resize(de->length_le); + // Might not be sector aligned, so reduce it back. + data->resize(de.length_le); return true; } - -bool ISOReader::FileExists(const char* path) -{ - auto de = LocateFile(path); - return de.has_value(); -} diff --git a/src/util/iso_reader.h b/src/util/iso_reader.h index c757869f3..c38dda110 100644 --- a/src/util/iso_reader.h +++ b/src/util/iso_reader.h @@ -1,8 +1,10 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once + #include "common/types.h" + #include #include #include @@ -10,7 +12,9 @@ class CDImage; -class ISOReader +class Error; + +class IsoReader { public: enum : u32 @@ -127,33 +131,46 @@ public: u16 sequence_le; u16 sequence_be; u8 filename_length; + + ALWAYS_INLINE bool IsDirectory() const { return (flags & ISODirectoryEntryFlag_Directory); } + ALWAYS_INLINE u32 GetSizeInSectors() const { return (length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; } }; #pragma pack(pop) - ISOReader(); - ~ISOReader(); + IsoReader(); + ~IsoReader(); + + static std::string_view RemoveVersionIdentifierFromPath(const std::string_view& path); ALWAYS_INLINE const CDImage* GetImage() const { return m_image; } ALWAYS_INLINE u32 GetTrackNumber() const { return m_track_number; } ALWAYS_INLINE const ISOPrimaryVolumeDescriptor& GetPVD() const { return m_pvd; } - bool Open(CDImage* image, u32 track_number); + bool Open(CDImage* image, u32 track_number, Error* error = nullptr); + + std::vector GetFilesInDirectory(const std::string_view& path, Error* error = nullptr); + std::vector> GetEntriesInDirectory(const std::string_view& path, + Error* error = nullptr); - std::vector GetFilesInDirectory(const char* path); + std::optional LocateFile(const std::string_view& path, Error* error); - bool ReadFile(const char* path, std::vector* data); - bool FileExists(const char* path); + bool FileExists(const std::string_view& path, Error* error = nullptr); + bool DirectoryExists(const std::string_view& path, Error* error = nullptr); + bool ReadFile(const std::string_view& path, std::vector* data, Error* error = nullptr); + bool ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error = nullptr); private: - bool ReadPVD(); + static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset); + + bool ReadSector(u8* buf, u32 lsn, Error* error); + bool ReadPVD(Error* error); - std::optional LocateFile(const char* path); - std::optional LocateFile(const char* path, u8* sector_buffer, u32 directory_record_lba, - u32 directory_record_size); + std::optional LocateFile(const std::string_view& path, u8* sector_buffer, u32 directory_record_lba, + u32 directory_record_size, Error* error); CDImage* m_image; u32 m_track_number; ISOPrimaryVolumeDescriptor m_pvd = {}; -}; \ No newline at end of file +};