Common: Add a basic ISO reader class
This commit is contained in:
parent
ea35c5f3bc
commit
3b11d936df
|
@ -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
|
||||
|
|
|
@ -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<u8*>(buffer);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
<ClInclude Include="gl\stream_buffer.h" />
|
||||
<ClInclude Include="gl\texture.h" />
|
||||
<ClInclude Include="heap_array.h" />
|
||||
<ClInclude Include="iso_reader.h" />
|
||||
<ClInclude Include="jit_code_buffer.h" />
|
||||
<ClInclude Include="rectangle.h" />
|
||||
<ClInclude Include="state_wrapper.h" />
|
||||
|
@ -65,6 +66,7 @@
|
|||
<ClCompile Include="gl\program.cpp" />
|
||||
<ClCompile Include="gl\stream_buffer.cpp" />
|
||||
<ClCompile Include="gl\texture.cpp" />
|
||||
<ClCompile Include="iso_reader.cpp" />
|
||||
<ClCompile Include="jit_code_buffer.cpp" />
|
||||
<ClCompile Include="state_wrapper.cpp" />
|
||||
<ClCompile Include="cd_xa.cpp" />
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
<Filter>d3d11</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rectangle.h" />
|
||||
<ClInclude Include="iso_reader.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="jit_code_buffer.cpp" />
|
||||
|
@ -62,6 +63,7 @@
|
|||
<ClCompile Include="d3d11\shader_compiler.cpp">
|
||||
<Filter>d3d11</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="iso_reader.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="bitfield.natvis" />
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
#include "iso_reader.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "cd_image.h"
|
||||
#include <cctype>
|
||||
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<ISOVolumeDescriptorHeader*>(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::ISODirectoryEntry> ISOReader::LocateFile(const char* path)
|
||||
{
|
||||
u8 sector_buffer[SECTOR_SIZE];
|
||||
|
||||
const ISODirectoryEntry* root_de = reinterpret_cast<const ISODirectoryEntry*>(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::ISODirectoryEntry> 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<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
||||
const char* de_filename =
|
||||
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;
|
||||
}
|
||||
|
||||
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<std::string> 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<const ISODirectoryEntry*>(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<std::string> 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<const ISODirectoryEntry*>(§or_buffer[sector_offset]);
|
||||
const char* de_filename =
|
||||
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;
|
||||
}
|
||||
|
||||
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<u8>* 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;
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
#include "types.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class CDImage;
|
||||
|
||||
class ISOReader
|
||||
{
|
||||
public:
|
||||
enum : u32
|
||||
{
|
||||
SECTOR_SIZE = 2048
|
||||
};
|
||||
|
||||
ISOReader();
|
||||
~ISOReader();
|
||||
|
||||
bool Open(CDImage* image, u32 track_number);
|
||||
|
||||
std::vector<std::string> GetFilesInDirectory(const char* path);
|
||||
|
||||
bool ReadFile(const char* path, std::vector<u8>* 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<ISODirectoryEntry> LocateFile(const char* path);
|
||||
std::optional<ISODirectoryEntry> 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 = {};
|
||||
};
|
Loading…
Reference in New Issue