mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-31 11:55:37 +00:00
IsoReader: Add GetEntriesInDirectory()
This commit is contained in:
parent
325dcc81ca
commit
5442242c64
|
@ -96,8 +96,8 @@ static std::optional<ExtendedSaveStateInfo> InternalGetExtendedSaveStateInfo(Byt
|
||||||
|
|
||||||
static bool LoadEXE(const char* filename);
|
static bool LoadEXE(const char* filename);
|
||||||
|
|
||||||
static std::string GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories);
|
static std::string GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories);
|
||||||
static bool ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name,
|
static bool ReadExecutableFromImage(IsoReader& iso, std::string* out_executable_name,
|
||||||
std::vector<u8>* out_executable_data);
|
std::vector<u8>* out_executable_data);
|
||||||
|
|
||||||
static bool LoadBIOS(const std::string& override_bios_path);
|
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)
|
bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash)
|
||||||
{
|
{
|
||||||
ISOReader iso;
|
IsoReader iso;
|
||||||
if (!iso.Open(cdi, 1))
|
if (!iso.Open(cdi, 1))
|
||||||
{
|
{
|
||||||
if (out_id)
|
if (out_id)
|
||||||
|
@ -589,7 +589,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash
|
||||||
XXH64_reset(state, 0x4242D00C);
|
XXH64_reset(state, 0x4242D00C);
|
||||||
XXH64_update(state, exe_name.c_str(), exe_name.size());
|
XXH64_update(state, exe_name.c_str(), exe_name.size());
|
||||||
XXH64_update(state, exe_buffer.data(), exe_buffer.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));
|
XXH64_update(state, &track_1_length, sizeof(track_1_length));
|
||||||
const GameHash hash = XXH64_digest(state);
|
const GameHash hash = XXH64_digest(state);
|
||||||
XXH64_freeState(state);
|
XXH64_freeState(state);
|
||||||
|
@ -636,7 +636,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories)
|
std::string System::GetExecutableNameForImage(IsoReader& iso, bool strip_subdirectories)
|
||||||
{
|
{
|
||||||
// Read SYSTEM.CNF
|
// Read SYSTEM.CNF
|
||||||
std::vector<u8> system_cnf_data;
|
std::vector<u8> 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)
|
std::string System::GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories)
|
||||||
{
|
{
|
||||||
ISOReader iso;
|
IsoReader iso;
|
||||||
if (!iso.Open(cdi, 1))
|
if (!iso.Open(cdi, 1))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
@ -738,14 +738,14 @@ std::string System::GetExecutableNameForImage(CDImage* cdi, bool strip_subdirect
|
||||||
bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name,
|
bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name,
|
||||||
std::vector<u8>* out_executable_data)
|
std::vector<u8>* out_executable_data)
|
||||||
{
|
{
|
||||||
ISOReader iso;
|
IsoReader iso;
|
||||||
if (!iso.Open(cdi, 1))
|
if (!iso.Open(cdi, 1))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return ReadExecutableFromImage(iso, out_executable_name, out_executable_data);
|
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<u8>* out_executable_data)
|
std::vector<u8>* out_executable_data)
|
||||||
{
|
{
|
||||||
const std::string executable_path = GetExecutableNameForImage(iso, false);
|
const std::string executable_path = GetExecutableNameForImage(iso, false);
|
||||||
|
@ -815,7 +815,7 @@ DiscRegion System::GetRegionForImage(CDImage* cdi)
|
||||||
if (system_area_region != DiscRegion::Other)
|
if (system_area_region != DiscRegion::Other)
|
||||||
return system_area_region;
|
return system_area_region;
|
||||||
|
|
||||||
ISOReader iso;
|
IsoReader iso;
|
||||||
if (!iso.Open(cdi, 1))
|
if (!iso.Open(cdi, 1))
|
||||||
return DiscRegion::NonPS1;
|
return DiscRegion::NonPS1;
|
||||||
|
|
||||||
|
|
|
@ -1,207 +1,235 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "iso_reader.h"
|
#include "iso_reader.h"
|
||||||
#include "cd_image.h"
|
#include "cd_image.h"
|
||||||
#include "common/log.h"
|
|
||||||
#include <cctype>
|
|
||||||
Log_SetChannel(ISOReader);
|
|
||||||
|
|
||||||
static bool FilenamesEqual(const char* a, const char* b, u32 length)
|
#include "common/error.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
Log_SetChannel(IsoReader);
|
||||||
|
|
||||||
|
IsoReader::IsoReader() = default;
|
||||||
|
|
||||||
|
IsoReader::~IsoReader() = default;
|
||||||
|
|
||||||
|
std::string_view IsoReader::RemoveVersionIdentifierFromPath(const std::string_view& path)
|
||||||
{
|
{
|
||||||
u32 pos = 0;
|
const std::string_view::size_type pos = path.find(';');
|
||||||
for (; pos < length && *a != '\0' && *b != '\0'; pos++)
|
return (pos != std::string_view::npos) ? path.substr(0, pos) : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsoReader::Open(CDImage* image, u32 track_number, Error* error)
|
||||||
|
{
|
||||||
|
m_image = image;
|
||||||
|
m_track_number = track_number;
|
||||||
|
|
||||||
|
if (!ReadPVD(error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error)
|
||||||
|
{
|
||||||
|
if (!m_image->Seek(m_track_number, lsn))
|
||||||
{
|
{
|
||||||
if (std::tolower(*(a++)) != std::tolower(*(b++)))
|
Error::SetString(error, fmt::format("Failed to seek to LSN #{}", lsn));
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ISOReader::ISOReader() = default;
|
bool IsoReader::ReadPVD(Error* error)
|
||||||
|
|
||||||
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
|
// volume descriptor start at sector 16
|
||||||
if (!m_image->Seek(m_track_number, 16))
|
static constexpr u32 START_SECTOR = 16;
|
||||||
return false;
|
|
||||||
|
|
||||||
// try only a maximum of 256 volume descriptors
|
// try only a maximum of 256 volume descriptors
|
||||||
for (u32 i = 0; i < 256; i++)
|
for (u32 i = 0; i < 256; i++)
|
||||||
{
|
{
|
||||||
u8 buffer[SECTOR_SIZE];
|
u8 buffer[SECTOR_SIZE];
|
||||||
if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buffer) != 1)
|
if (!ReadSector(buffer, START_SECTOR + i, error))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const ISOVolumeDescriptorHeader* header = reinterpret_cast<ISOVolumeDescriptorHeader*>(buffer);
|
const ISOVolumeDescriptorHeader* header = reinterpret_cast<ISOVolumeDescriptorHeader*>(buffer);
|
||||||
if (header->type_code != 1)
|
if (std::memcmp(header->standard_identifier, "CD001", 5) != 0)
|
||||||
|
continue;
|
||||||
|
else if (header->type_code != 1)
|
||||||
continue;
|
continue;
|
||||||
else if (header->type_code == 255)
|
else if (header->type_code == 255)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor));
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log_ErrorPrint("PVD not found");
|
Error::SetString(error, "Failed to find the Primary Volume Descriptor.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ISOReader::ISODirectoryEntry> ISOReader::LocateFile(const char* path)
|
std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(const std::string_view& path, Error* error)
|
||||||
{
|
{
|
||||||
u8 sector_buffer[SECTOR_SIZE];
|
|
||||||
|
|
||||||
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
|
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
|
||||||
if (*path == '\0' || std::strcmp(path, "/") == 0)
|
if (path.empty() || path == "/" || path == "\\")
|
||||||
{
|
{
|
||||||
// locating the root directory
|
// locating the root directory
|
||||||
return *root_de;
|
return *root_de;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start at the root directory
|
// 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::ISODirectoryEntry> ISOReader::LocateFile(const char* path, u8* sector_buffer,
|
std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset)
|
||||||
u32 directory_record_lba, u32 directory_record_size)
|
{
|
||||||
|
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(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<const char*>(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::ISODirectoryEntry> 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)
|
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;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip any leading slashes
|
// strip any leading slashes
|
||||||
const char* path_component_start = path;
|
size_t path_component_start = 0;
|
||||||
while (*path_component_start == '/' || *path_component_start == '\\')
|
while (path_component_start < path.length() &&
|
||||||
|
(path[path_component_start] == '/' || path[path_component_start] == '\\'))
|
||||||
|
{
|
||||||
path_component_start++;
|
path_component_start++;
|
||||||
|
}
|
||||||
|
|
||||||
u32 path_component_length = 0;
|
size_t path_component_length = 0;
|
||||||
const char* path_component_end = path_component_start;
|
while ((path_component_start + path_component_length) < path.length() &&
|
||||||
while (*path_component_end != '\0' && *path_component_end != '/' && *path_component_end != '\\')
|
path[path_component_start + path_component_length] != '/' &&
|
||||||
|
path[path_component_start + path_component_length] != '\\')
|
||||||
{
|
{
|
||||||
path_component_length++;
|
path_component_length++;
|
||||||
path_component_end++;
|
}
|
||||||
|
|
||||||
|
const std::string_view path_component = path.substr(path_component_start, path_component_length);
|
||||||
|
if (path_component.empty())
|
||||||
|
{
|
||||||
|
Error::SetString(error, fmt::format("Empty path component in {}", path));
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start reading directory entries
|
// start reading directory entries
|
||||||
const u32 num_sectors = (directory_record_size + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
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++)
|
for (u32 i = 0; i < num_sectors; i++)
|
||||||
{
|
{
|
||||||
if (m_image->Read(CDImage::ReadMode::DataOnly, 1, sector_buffer) != 1)
|
if (!ReadSector(sector_buffer, directory_record_lba + i, error))
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to read LBA %u", directory_record_lba + i);
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
|
||||||
|
|
||||||
u32 sector_offset = 0;
|
u32 sector_offset = 0;
|
||||||
while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
|
while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
|
||||||
{
|
{
|
||||||
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
||||||
const char* de_filename =
|
if (de->entry_length < sizeof(ISODirectoryEntry))
|
||||||
reinterpret_cast<const char*>(§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;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
|
const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
|
||||||
sector_offset += de->entry_length;
|
sector_offset += de->entry_length;
|
||||||
|
|
||||||
// skip current/parent directory
|
// Empty file would be pretty strange..
|
||||||
if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1'))
|
if (de_filename.empty() || de_filename == "." || de_filename == "..")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// check filename length
|
if (de_filename.length() != path_component.length() ||
|
||||||
if (de->filename_length < path_component_length)
|
StringUtil::Strncasecmp(de_filename.data(), path_component.data(), path_component.length()) != 0)
|
||||||
|
{
|
||||||
continue;
|
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
|
|
||||||
{
|
|
||||||
// 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?
|
// 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;
|
return *de;
|
||||||
|
|
||||||
// if it is a directory, recurse into it
|
// if it is a directory, recurse into it
|
||||||
if (de->flags & ISODirectoryEntryFlag_Directory)
|
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
|
// 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;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string temp(path_component_start, path_component_length);
|
Error::SetString(error, fmt::format("Path component '{}' not found", path_component));
|
||||||
Log_ErrorPrintf("Path component '%s' not found", temp.c_str());
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> ISOReader::GetFilesInDirectory(const char* path)
|
std::vector<std::string> IsoReader::GetFilesInDirectory(const std::string_view& path, Error* error)
|
||||||
{
|
{
|
||||||
std::string base_path = path;
|
std::string base_path(path);
|
||||||
u32 directory_record_lba;
|
u32 directory_record_lsn;
|
||||||
u32 directory_record_length;
|
u32 directory_record_length;
|
||||||
if (base_path.empty())
|
if (base_path.empty())
|
||||||
{
|
{
|
||||||
// root directory
|
// root directory
|
||||||
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(m_pvd.root_directory_entry);
|
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(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;
|
directory_record_length = root_de->length_le;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto directory_de = LocateFile(base_path.c_str());
|
auto directory_de = LocateFile(base_path, error);
|
||||||
if (!directory_de)
|
if (!directory_de.has_value())
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Directory entry not found for '%s'", path);
|
|
||||||
return {};
|
return {};
|
||||||
}
|
|
||||||
|
|
||||||
if ((directory_de->flags & ISODirectoryEntryFlag_Directory) == 0)
|
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 {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
directory_record_lba = directory_de->location_le;
|
directory_record_lsn = directory_de->location_le;
|
||||||
directory_record_length = directory_de->length_le;
|
directory_record_length = directory_de->length_le;
|
||||||
|
|
||||||
if (base_path[base_path.size() - 1] != '/')
|
if (base_path[base_path.size() - 1] != '/')
|
||||||
|
@ -210,92 +238,147 @@ std::vector<std::string> ISOReader::GetFilesInDirectory(const char* path)
|
||||||
|
|
||||||
// start reading directory entries
|
// start reading directory entries
|
||||||
const u32 num_sectors = (directory_record_length + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
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<std::string> files;
|
std::vector<std::string> files;
|
||||||
u8 sector_buffer[SECTOR_SIZE];
|
u8 sector_buffer[SECTOR_SIZE];
|
||||||
for (u32 i = 0; i < num_sectors; i++)
|
for (u32 i = 0; i < num_sectors; i++)
|
||||||
{
|
{
|
||||||
if (m_image->Read(CDImage::ReadMode::DataOnly, 1, sector_buffer) != 1)
|
if (!ReadSector(sector_buffer, directory_record_lsn + i, error))
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Failed to read LBA %u", directory_record_lba + i);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
u32 sector_offset = 0;
|
u32 sector_offset = 0;
|
||||||
while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
|
while ((sector_offset + sizeof(ISODirectoryEntry)) < SECTOR_SIZE)
|
||||||
{
|
{
|
||||||
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
||||||
const char* de_filename =
|
if (de->entry_length < sizeof(ISODirectoryEntry))
|
||||||
reinterpret_cast<const char*>(§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;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
|
const std::string_view de_filename = GetDirectoryEntryFileName(sector_buffer, sector_offset);
|
||||||
sector_offset += de->entry_length;
|
sector_offset += de->entry_length;
|
||||||
|
|
||||||
// skip current/parent directory
|
// Empty file would be pretty strange..
|
||||||
if (de->filename_length == 1 && (*de_filename == '\x0' || *de_filename == '\x1'))
|
if (de_filename.empty() || de_filename == "." || de_filename == "..")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// strip off terminator/file version
|
files.push_back(fmt::format("{}{}", base_path, de_filename));
|
||||||
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;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ISOReader::ReadFile(const char* path, std::vector<u8>* data)
|
std::vector<std::pair<std::string, IsoReader::ISODirectoryEntry>>
|
||||||
|
IsoReader::GetEntriesInDirectory(const std::string_view& path, Error* error /*= nullptr*/)
|
||||||
{
|
{
|
||||||
auto de = LocateFile(path);
|
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<const ISODirectoryEntry*>(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<std::pair<std::string, IsoReader::ISODirectoryEntry>> 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<const ISODirectoryEntry*>(§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;
|
||||||
|
|
||||||
|
files.emplace_back(fmt::format("{}{}", base_path, de_filename), *de);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsoReader::FileExists(const std::string_view& path, Error* error)
|
||||||
|
{
|
||||||
|
auto de = LocateFile(path, error);
|
||||||
if (!de)
|
if (!de)
|
||||||
{
|
|
||||||
Log_ErrorPrintf("File not found: '%s'", path);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
if (de->flags & ISODirectoryEntryFlag_Directory)
|
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;
|
||||||
|
|
||||||
|
return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsoReader::ReadFile(const std::string_view& path, std::vector<u8>* data, Error* error)
|
||||||
|
{
|
||||||
|
auto de = LocateFile(path, error);
|
||||||
|
if (!de)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ReadFile(de.value(), data, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error /*= nullptr*/)
|
||||||
|
{
|
||||||
|
if (de.flags & ISODirectoryEntryFlag_Directory)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("File is a directory: '%s'", path);
|
Error::SetString(error, "File is a directory");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_image->Seek(m_track_number, de->location_le))
|
if (de.length_le == 0)
|
||||||
return false;
|
|
||||||
|
|
||||||
if (de->length_le == 0)
|
|
||||||
{
|
{
|
||||||
data->clear();
|
data->clear();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 num_sectors = (de->length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
static_assert(sizeof(size_t) == sizeof(u64));
|
||||||
data->resize(num_sectors * u64(SECTOR_SIZE));
|
const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
|
||||||
if (m_image->Read(CDImage::ReadMode::DataOnly, num_sectors, data->data()) != num_sectors)
|
data->resize(num_sectors * static_cast<u64>(SECTOR_SIZE));
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ISOReader::FileExists(const char* path)
|
|
||||||
{
|
|
||||||
auto de = LocateFile(path);
|
|
||||||
return de.has_value();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -10,7 +12,9 @@
|
||||||
|
|
||||||
class CDImage;
|
class CDImage;
|
||||||
|
|
||||||
class ISOReader
|
class Error;
|
||||||
|
|
||||||
|
class IsoReader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum : u32
|
enum : u32
|
||||||
|
@ -127,33 +131,46 @@ public:
|
||||||
u16 sequence_le;
|
u16 sequence_le;
|
||||||
u16 sequence_be;
|
u16 sequence_be;
|
||||||
u8 filename_length;
|
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)
|
#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 const CDImage* GetImage() const { return m_image; }
|
||||||
ALWAYS_INLINE u32 GetTrackNumber() const { return m_track_number; }
|
ALWAYS_INLINE u32 GetTrackNumber() const { return m_track_number; }
|
||||||
ALWAYS_INLINE const ISOPrimaryVolumeDescriptor& GetPVD() const { return m_pvd; }
|
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<std::string> GetFilesInDirectory(const char* path);
|
std::vector<std::string> GetFilesInDirectory(const std::string_view& path, Error* error = nullptr);
|
||||||
|
std::vector<std::pair<std::string, ISODirectoryEntry>> GetEntriesInDirectory(const std::string_view& path,
|
||||||
|
Error* error = nullptr);
|
||||||
|
|
||||||
bool ReadFile(const char* path, std::vector<u8>* data);
|
std::optional<ISODirectoryEntry> LocateFile(const std::string_view& path, Error* error);
|
||||||
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<u8>* data, Error* error = nullptr);
|
||||||
|
bool ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool ReadPVD();
|
static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset);
|
||||||
|
|
||||||
std::optional<ISODirectoryEntry> LocateFile(const char* path);
|
bool ReadSector(u8* buf, u32 lsn, Error* error);
|
||||||
std::optional<ISODirectoryEntry> LocateFile(const char* path, u8* sector_buffer, u32 directory_record_lba,
|
bool ReadPVD(Error* error);
|
||||||
u32 directory_record_size);
|
|
||||||
|
std::optional<ISODirectoryEntry> LocateFile(const std::string_view& path, u8* sector_buffer, u32 directory_record_lba,
|
||||||
|
u32 directory_record_size, Error* error);
|
||||||
|
|
||||||
CDImage* m_image;
|
CDImage* m_image;
|
||||||
u32 m_track_number;
|
u32 m_track_number;
|
||||||
|
|
||||||
ISOPrimaryVolumeDescriptor m_pvd = {};
|
ISOPrimaryVolumeDescriptor m_pvd = {};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue