diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 31df1d34b..e5d8fc8dd 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -14,6 +14,8 @@ add_library(common gl/stream_buffer.h gl/texture.cpp gl/texture.h + iso_reader.cpp + iso_reader.h jit_code_buffer.cpp jit_code_buffer.h rectangle.h diff --git a/src/common/cd_image.cpp b/src/common/cd_image.cpp index beeb5cab9..9b0765937 100644 --- a/src/common/cd_image.cpp +++ b/src/common/cd_image.cpp @@ -100,6 +100,15 @@ bool CDImage::Seek(const Position& pos) return Seek(pos.ToLBA()); } +bool CDImage::Seek(u32 track_number, LBA lba) +{ + if (track_number < 1 || track_number > m_tracks.size()) + return false; + + const Track& track = m_tracks[track_number - 1]; + return Seek(track.start_lba + lba); +} + u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer) { u8* buffer_ptr = static_cast(buffer); diff --git a/src/common/cd_image.h b/src/common/cd_image.h index f07b34d60..5c21a56a6 100644 --- a/src/common/cd_image.h +++ b/src/common/cd_image.h @@ -183,6 +183,9 @@ public: // Seek to track and position. bool Seek(u32 track_number, const Position& pos_in_track); + // Seek to track and LBA. + bool Seek(u32 track_number, LBA lba); + // Read from the current LBA. Returns the number of sectors read. u32 Read(ReadMode read_mode, u32 sector_count, void* buffer); diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 9284532f7..ecede6003 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -47,6 +47,7 @@ + @@ -65,6 +66,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 7c6c099ac..89a9bf9b3 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -32,6 +32,7 @@ d3d11 + @@ -62,6 +63,7 @@ d3d11 + diff --git a/src/common/iso_reader.cpp b/src/common/iso_reader.cpp new file mode 100644 index 000000000..d5910e316 --- /dev/null +++ b/src/common/iso_reader.cpp @@ -0,0 +1,280 @@ +#include "iso_reader.h" +#include "YBaseLib/Log.h" +#include "cd_image.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; + } + + return true; +} + +ISOReader::ISOReader() = default; + +ISOReader::~ISOReader() = default; + +bool ISOReader::Open(CDImage* image, u32 track_number) +{ + m_image = image; + m_track_number = track_number; + if (!ReadPVD()) + return false; + + return true; +} + +bool ISOReader::ReadPVD() +{ + // volume descriptor start at sector 16 + if (!m_image->Seek(m_track_number, 16)) + return false; + + // 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) + return false; + + const ISOVolumeDescriptorHeader* header = reinterpret_cast(buffer); + 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); + return true; + } + + Log_ErrorPrint("PVD not found"); + return false; +} + +std::optional ISOReader::LocateFile(const char* path) +{ + u8 sector_buffer[SECTOR_SIZE]; + + const ISODirectoryEntry* root_de = reinterpret_cast(m_pvd.root_directory_entry); + if (*path == '\0' || std::strcmp(path, "/") == 0) + { + // 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); +} + +std::optional ISOReader::LocateFile(const char* path, u8* sector_buffer, + u32 directory_record_lba, u32 directory_record_size) +{ + if (directory_record_size == 0) + { + Log_ErrorPrintf("Directory entry record size 0 while looking for '%s'", path); + return std::nullopt; + } + + // strip any leading slashes + const char* path_component_start = path; + while (*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_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)) + { + Log_ErrorPrintf("Seek to LBA %u failed", directory_record_lba); + return std::nullopt; + } + + 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); + 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)) + { + break; + } + + 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) + continue; + + // compare filename + if (!FilenamesEqual(de_filename, path_component_start, path_component_length) || + de_filename[path_component_length] != ';') + { + continue; + } + + // found it. is this the file we're looking for? + if (*path_component_end == '\0') + 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); + + // we're looking for a directory but got a file + Log_ErrorPrintf("Looking for directory but got file"); + return std::nullopt; + } + } + + std::string temp(path_component_start, path_component_length); + Log_ErrorPrintf("Path component '%s' not found", temp.c_str()); + return std::nullopt; +} + +std::vector ISOReader::GetFilesInDirectory(const char* path) +{ + std::string base_path = path; + u32 directory_record_lba; + 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_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); + return {}; + } + + if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0) + { + Log_ErrorPrintf("Path '%s' is not a directory, can't list", path); + return {}; + } + + directory_record_lba = 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; + 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); + 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)) + { + break; + } + + sector_offset += de->entry_length; + + // skip current/parent directory + if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1')) + 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()); + continue; + } + filename.erase(pos); + + if (!filename.empty()) + files.push_back(base_path + filename); + } + } + + return files; +} + +bool ISOReader::ReadFile(const char* path, std::vector* data) +{ + auto de = LocateFile(path); + 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 false; + } + + if (!m_image->Seek(m_track_number, de->location_le)) + 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; + + data->resize(de->length_le); + return true; +} diff --git a/src/common/iso_reader.h b/src/common/iso_reader.h new file mode 100644 index 000000000..0098e18ee --- /dev/null +++ b/src/common/iso_reader.h @@ -0,0 +1,151 @@ +#pragma once +#include "types.h" +#include +#include +#include +#include + +class CDImage; + +class ISOReader +{ +public: + enum : u32 + { + SECTOR_SIZE = 2048 + }; + + ISOReader(); + ~ISOReader(); + + bool Open(CDImage* image, u32 track_number); + + std::vector GetFilesInDirectory(const char* path); + + bool ReadFile(const char* path, std::vector* data); + +private: +#pragma pack(push, 1) + + struct ISOVolumeDescriptorHeader + { + u8 type_code; + char standard_identifier[5]; + u8 version; + }; + static_assert(sizeof(ISOVolumeDescriptorHeader) == 7); + + struct ISOBootRecord + { + ISOVolumeDescriptorHeader header; + char boot_system_identifier[32]; + char boot_identifier[32]; + u8 data[1977]; + }; + static_assert(sizeof(ISOBootRecord) == 2048); + + struct ISOPVDDateTime + { + char year[4]; + char month[2]; + char day[2]; + char hour[2]; + char minute[2]; + char second[2]; + char milliseconds[2]; + s8 gmt_offset; + }; + static_assert(sizeof(ISOPVDDateTime) == 17); + + struct ISOPrimaryVolumeDescriptor + { + ISOVolumeDescriptorHeader header; + u8 unused; + char system_identifier[32]; + char volume_identifier[32]; + char unused2[8]; + u32 total_sectors_le; + u32 total_sectors_be; + char unused3[32]; + u16 volume_set_size_le; + u16 volume_set_size_be; + u16 volume_sequence_number_le; + u16 volume_sequence_number_be; + u16 block_size_le; + u16 block_size_be; + u32 path_table_size_le; + u32 path_table_size_be; + u32 path_table_location_le; + u32 optional_path_table_location_le; + u32 path_table_location_be; + u32 optional_path_table_location_be; + u8 root_directory_entry[34]; + char volume_set_identifier[128]; + char publisher_identifier[128]; + char data_preparer_identifier[128]; + char application_identifier[128]; + char copyright_file_identifier[38]; + char abstract_file_identifier[36]; + char bibliographic_file_identifier[37]; + ISOPVDDateTime volume_creation_time; + ISOPVDDateTime volume_modification_time; + ISOPVDDateTime volume_expiration_time; + ISOPVDDateTime volume_effective_time; + u8 structure_version; + u8 unused4; + u8 application_used[512]; + u8 reserved[653]; + }; + static_assert(sizeof(ISOPrimaryVolumeDescriptor) == 2048); + + struct ISODirectoryEntryDateTime + { + u8 years_since_1900; + u8 month; + u8 day; + u8 hour; + u8 minute; + u8 second; + s8 gmt_offset; + }; + + enum ISODirectoryEntryFlags : u8 + { + ISODirectoryEntryFlag_Hidden = (1 << 0), + ISODirectoryEntryFlag_Directory = (1 << 1), + ISODirectoryEntryFlag_AssociatedFile = (1 << 2), + ISODirectoryEntryFlag_ExtendedAttributePresent = (1 << 3), + ISODirectoryEntryFlag_OwnerGroupPermissions = (1 << 4), + ISODirectoryEntryFlag_MoreExtents = (1 << 7), + }; + + struct ISODirectoryEntry + { + u8 entry_length; + u8 extended_attribute_length; + u32 location_le; + u32 location_be; + u32 length_le; + u32 length_be; + ISODirectoryEntryDateTime recoding_time; + ISODirectoryEntryFlags flags; + u8 interleaved_unit_size; + u8 interleaved_gap_size; + u16 sequence_le; + u16 sequence_be; + u8 filename_length; + }; + +#pragma pack(pop) + + bool ReadPVD(); + + std::optional LocateFile(const char* path); + std::optional LocateFile(const char* path, u8* sector_buffer, u32 directory_record_lba, + u32 directory_record_size); + + CDImage* m_image; + u32 m_track_number; + + ISOPrimaryVolumeDescriptor m_pvd = {}; +}; \ No newline at end of file