ISOReader: Add XA and raw extraction modes

This commit is contained in:
Stenzek 2024-12-17 14:19:41 +10:00
parent b68370dff7
commit f010d81652
No known key found for this signature in database
9 changed files with 215 additions and 134 deletions

View File

@ -941,7 +941,7 @@ std::string System::GetExecutableNameForImage(IsoReader& iso, bool strip_subdire
{ {
// Read SYSTEM.CNF // Read SYSTEM.CNF
std::vector<u8> system_cnf_data; std::vector<u8> system_cnf_data;
if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data)) if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data, IsoReader::ReadMode::Data))
return FALLBACK_EXE_NAME; return FALLBACK_EXE_NAME;
// Parse lines // Parse lines
@ -1053,7 +1053,7 @@ bool System::ReadExecutableFromImage(IsoReader& iso, std::string* out_executable
DEV_LOG("Executable path: '{}'", executable_path); DEV_LOG("Executable path: '{}'", executable_path);
if (!executable_path.empty() && out_executable_data) if (!executable_path.empty() && out_executable_data)
{ {
if (!iso.ReadFile(executable_path, out_executable_data)) if (!iso.ReadFile(executable_path, out_executable_data, IsoReader::ReadMode::Data))
{ {
ERROR_LOG("Failed to read executable '{}' from disc", executable_path); ERROR_LOG("Failed to read executable '{}' from disc", executable_path);
return false; return false;
@ -1104,9 +1104,11 @@ DiscRegion System::GetRegionForSerial(const std::string_view serial)
DiscRegion System::GetRegionFromSystemArea(CDImage* cdi) DiscRegion System::GetRegionFromSystemArea(CDImage* cdi)
{ {
// The license code is on sector 4 of the disc. // The license code is on sector 4 of the disc.
u8 sector[CDImage::DATA_SECTOR_SIZE]; std::array<u8, CDImage::RAW_SECTOR_SIZE> sector;
std::span<const u8> sector_data;
if (cdi->GetTrackMode(1) == CDImage::TrackMode::Audio || !cdi->Seek(1, 4) || if (cdi->GetTrackMode(1) == CDImage::TrackMode::Audio || !cdi->Seek(1, 4) ||
cdi->Read(CDImage::ReadMode::DataOnly, 1, sector) != 1) !cdi->ReadRawSector(sector.data(), nullptr) ||
(sector_data = IsoReader::ExtractSectorData(sector, IsoReader::ReadMode::Data, nullptr)).empty())
{ {
return DiscRegion::Other; return DiscRegion::Other;
} }
@ -1116,11 +1118,11 @@ DiscRegion System::GetRegionFromSystemArea(CDImage* cdi)
static constexpr char pal_string[] = " Licensed by Sony Computer Entertainment Euro pe"; static constexpr char pal_string[] = " Licensed by Sony Computer Entertainment Euro pe";
// subtract one for the terminating null // subtract one for the terminating null
if (std::equal(ntsc_u_string, ntsc_u_string + countof(ntsc_u_string) - 1, sector)) if (std::memcmp(sector_data.data(), ntsc_u_string, std::size(ntsc_u_string) - 1) == 0)
return DiscRegion::NTSC_U; return DiscRegion::NTSC_U;
else if (std::equal(ntsc_j_string, ntsc_j_string + countof(ntsc_j_string) - 1, sector)) else if (std::memcmp(sector_data.data(), ntsc_j_string, std::size(ntsc_j_string) - 1) == 0)
return DiscRegion::NTSC_J; return DiscRegion::NTSC_J;
else if (std::equal(pal_string, pal_string + countof(pal_string) - 1, sector)) else if (std::memcmp(sector_data.data(), pal_string, std::size(pal_string) - 1) == 0)
return DiscRegion::PAL; return DiscRegion::PAL;
else else
return DiscRegion::Other; return DiscRegion::Other;

View File

@ -38,7 +38,7 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* pa
.arg(i) .arg(i)
.arg(static_cast<float>(i) * TIME_PER_SECTOR_DOUBLE_SPEED, 0, 'f', 0) .arg(static_cast<float>(i) * TIME_PER_SECTOR_DOUBLE_SPEED, 0, 'f', 0)
.arg(static_cast<float>(i * CDImage::DATA_SECTOR_SIZE) / 1024.0f)); .arg(static_cast<float>(i * CDImage::RAW_SECTOR_SIZE) / 1024.0f));
} }
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName, SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName,

View File

@ -29,7 +29,10 @@ ISOBrowserWindow::ISOBrowserWindow(QWidget* parent) : QWidget(parent)
enableUi(false); enableUi(false);
connect(m_ui.openFile, &QAbstractButton::clicked, this, &ISOBrowserWindow::onOpenFileClicked); connect(m_ui.openFile, &QAbstractButton::clicked, this, &ISOBrowserWindow::onOpenFileClicked);
connect(m_ui.extract, &QAbstractButton::clicked, this, &ISOBrowserWindow::onExtractClicked); connect(m_ui.extract, &QAbstractButton::clicked, this, [this]() { onExtractClicked(IsoReader::ReadMode::Data); });
connect(m_ui.extractMode2, &QAbstractButton::clicked, this,
[this]() { onExtractClicked(IsoReader::ReadMode::Mode2); });
connect(m_ui.extractRaw, &QAbstractButton::clicked, this, [this]() { onExtractClicked(IsoReader::ReadMode::Raw); });
connect(m_ui.directoryView, &QTreeWidget::itemClicked, this, &ISOBrowserWindow::onDirectoryItemClicked); connect(m_ui.directoryView, &QTreeWidget::itemClicked, this, &ISOBrowserWindow::onDirectoryItemClicked);
connect(m_ui.fileView, &QTreeWidget::itemActivated, this, &ISOBrowserWindow::onFileItemActivated); connect(m_ui.fileView, &QTreeWidget::itemActivated, this, &ISOBrowserWindow::onFileItemActivated);
connect(m_ui.fileView, &QTreeWidget::itemSelectionChanged, this, &ISOBrowserWindow::onFileItemSelectionChanged); connect(m_ui.fileView, &QTreeWidget::itemSelectionChanged, this, &ISOBrowserWindow::onFileItemSelectionChanged);
@ -105,14 +108,14 @@ void ISOBrowserWindow::onOpenFileClicked()
} }
} }
void ISOBrowserWindow::onExtractClicked() void ISOBrowserWindow::onExtractClicked(IsoReader::ReadMode mode)
{ {
const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems(); const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems();
if (items.isEmpty()) if (items.isEmpty())
return; return;
const QString path = items.front()->data(0, Qt::UserRole).toString(); const QString path = items.front()->data(0, Qt::UserRole).toString();
extractFile(path); extractFile(path, mode);
} }
void ISOBrowserWindow::onDirectoryItemClicked(QTreeWidgetItem* item, int column) void ISOBrowserWindow::onDirectoryItemClicked(QTreeWidgetItem* item, int column)
@ -141,21 +144,16 @@ void ISOBrowserWindow::onFileItemActivated(QTreeWidgetItem* item, int column)
} }
// file, go to extract // file, go to extract
extractFile(item->data(0, Qt::UserRole).toString()); extractFile(item->data(0, Qt::UserRole).toString(), IsoReader::ReadMode::Data);
} }
void ISOBrowserWindow::onFileItemSelectionChanged() void ISOBrowserWindow::onFileItemSelectionChanged()
{ {
const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems(); const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems();
if (items.isEmpty())
{
m_ui.extract->setEnabled(false);
return;
}
// directory? // directory?
const bool is_directory = items.front()->data(0, Qt::UserRole + 1).toBool(); const bool enabled = (!items.isEmpty() && !items.front()->data(0, Qt::UserRole + 1).toBool());
m_ui.extract->setEnabled(!is_directory); enableExtractButtons(enabled);
} }
void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos) void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos)
@ -176,7 +174,11 @@ void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos)
else else
{ {
connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), tr("&Extract")), &QAction::triggered, connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), tr("&Extract")), &QAction::triggered,
this, [this, &path]() { extractFile(path); }); this, [this, &path]() { extractFile(path, IsoReader::ReadMode::Data); });
connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), tr("Extract (&XA)")),
&QAction::triggered, this, [this, &path]() { extractFile(path, IsoReader::ReadMode::Mode2); });
connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), tr("Extract (&Raw)")),
&QAction::triggered, this, [this, &path]() { extractFile(path, IsoReader::ReadMode::Raw); });
} }
menu.exec(m_ui.fileView->mapToGlobal(pos)); menu.exec(m_ui.fileView->mapToGlobal(pos));
@ -187,7 +189,7 @@ void ISOBrowserWindow::resizeFileListColumns()
QtUtils::ResizeColumnsForTreeView(m_ui.fileView, {-1, 200, 100}); QtUtils::ResizeColumnsForTreeView(m_ui.fileView, {-1, 200, 100});
} }
void ISOBrowserWindow::extractFile(const QString& path) void ISOBrowserWindow::extractFile(const QString& path, IsoReader::ReadMode mode)
{ {
const std::string spath = path.toStdString(); const std::string spath = path.toStdString();
const QString filename = QtUtils::StringViewToQString(Path::GetFileName(spath)); const QString filename = QtUtils::StringViewToQString(Path::GetFileName(spath));
@ -207,7 +209,7 @@ void ISOBrowserWindow::extractFile(const QString& path)
cb.SetCancellable(true); cb.SetCancellable(true);
cb.SetTitle("ISO Browser"); cb.SetTitle("ISO Browser");
cb.SetStatusText(tr("Extracting %1...").arg(filename).toStdString()); cb.SetStatusText(tr("Extracting %1...").arg(filename).toStdString());
if (m_iso.WriteFileToStream(de.value(), fp.get(), &error, &cb)) if (m_iso.WriteFileToStream(de.value(), fp.get(), mode, &error, &cb))
{ {
if (FileSystem::CommitAtomicRenamedFile(fp, &error)) if (FileSystem::CommitAtomicRenamedFile(fp, &error))
return; return;
@ -256,13 +258,20 @@ void ISOBrowserWindow::enableUi(bool enabled)
m_ui.fileView->setEnabled(enabled); m_ui.fileView->setEnabled(enabled);
if (!enabled) if (!enabled)
m_ui.extract->setEnabled(enabled); enableExtractButtons(enabled);
}
void ISOBrowserWindow::enableExtractButtons(bool enabled)
{
m_ui.extract->setEnabled(enabled);
m_ui.extractMode2->setEnabled(enabled);
m_ui.extractRaw->setEnabled(enabled);
} }
void ISOBrowserWindow::populateDirectories() void ISOBrowserWindow::populateDirectories()
{ {
m_ui.directoryView->clear(); m_ui.directoryView->clear();
m_ui.extract->setEnabled(false); enableExtractButtons(false);
QTreeWidgetItem* root = new QTreeWidgetItem; QTreeWidgetItem* root = new QTreeWidgetItem;
root->setIcon(0, QIcon::fromTheme("disc-line")); root->setIcon(0, QIcon::fromTheme("disc-line"));

View File

@ -25,7 +25,6 @@ protected:
private Q_SLOTS: private Q_SLOTS:
void onOpenFileClicked(); void onOpenFileClicked();
void onExtractClicked();
void onDirectoryItemClicked(QTreeWidgetItem* item, int column); void onDirectoryItemClicked(QTreeWidgetItem* item, int column);
void onFileItemActivated(QTreeWidgetItem* item, int column); void onFileItemActivated(QTreeWidgetItem* item, int column);
void onFileItemSelectionChanged(); void onFileItemSelectionChanged();
@ -34,10 +33,12 @@ private Q_SLOTS:
private: private:
void enableUi(bool enabled); void enableUi(bool enabled);
void enableExtractButtons(bool enabled);
void populateDirectories(); void populateDirectories();
void populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent); void populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent);
void populateFiles(const QString& path); void populateFiles(const QString& path);
void extractFile(const QString& path); void onExtractClicked(IsoReader::ReadMode mode);
void extractFile(const QString& path, IsoReader::ReadMode mode);
QTreeWidgetItem* findDirectoryItemForPath(const QString& path, QTreeWidgetItem* parent = nullptr) const; QTreeWidgetItem* findDirectoryItemForPath(const QString& path, QTreeWidgetItem* parent = nullptr) const;

View File

@ -114,6 +114,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="extractMode2">
<property name="text">
<string>Extract (XA)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="extractRaw">
<property name="text">
<string>Extract (Raw)</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="close"> <widget class="QPushButton" name="close">
<property name="text"> <property name="text">

View File

@ -249,60 +249,6 @@ bool CDImage::Seek(u32 track_number, LBA lba)
return Seek(track.start_lba + lba); return Seek(track.start_lba + lba);
} }
u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer)
{
u8* buffer_ptr = static_cast<u8*>(buffer);
u32 sectors_read = 0;
for (; sectors_read < sector_count; sectors_read++)
{
// get raw sector
u8 raw_sector[RAW_SECTOR_SIZE];
if (!ReadRawSector(raw_sector, nullptr))
break;
switch (read_mode)
{
case ReadMode::DataOnly:
{
const SectorHeader* header = reinterpret_cast<const SectorHeader*>(raw_sector + SECTOR_SYNC_SIZE);
if (header->sector_mode == 1)
{
std::memcpy(buffer_ptr, raw_sector + SECTOR_SYNC_SIZE + MODE1_HEADER_SIZE, DATA_SECTOR_SIZE);
}
else if (header->sector_mode == 2)
{
std::memcpy(buffer_ptr, raw_sector + SECTOR_SYNC_SIZE + MODE2_HEADER_SIZE, DATA_SECTOR_SIZE);
}
else
{
ERROR_LOG("Invalid sector mode {} at LBA {}", header->sector_mode,
m_current_index->start_lba_on_disc + m_position_in_track);
break;
}
buffer_ptr += DATA_SECTOR_SIZE;
}
break;
case ReadMode::RawNoSync:
std::memcpy(buffer_ptr, raw_sector + SECTOR_SYNC_SIZE, RAW_SECTOR_SIZE - SECTOR_SYNC_SIZE);
buffer_ptr += RAW_SECTOR_SIZE - SECTOR_SYNC_SIZE;
break;
case ReadMode::RawSector:
std::memcpy(buffer_ptr, raw_sector, RAW_SECTOR_SIZE);
buffer_ptr += RAW_SECTOR_SIZE;
break;
default:
UnreachableCode();
break;
}
}
return sectors_read;
}
bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq) bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq)
{ {
if (m_position_in_index == m_current_index->length) if (m_position_in_index == m_current_index->length)

View File

@ -32,6 +32,7 @@ public:
SECTOR_HEADER_SIZE = 4, SECTOR_HEADER_SIZE = 4,
MODE1_HEADER_SIZE = 4, MODE1_HEADER_SIZE = 4,
MODE2_HEADER_SIZE = 12, MODE2_HEADER_SIZE = 12,
MODE2_DATA_SECTOR_SIZE = 2336, // header + edc
FRAMES_PER_SECOND = 75, // "sectors", or "timecode frames" (not "channel frames") FRAMES_PER_SECOND = 75, // "sectors", or "timecode frames" (not "channel frames")
SECONDS_PER_MINUTE = 60, SECONDS_PER_MINUTE = 60,
FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE, FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE,
@ -47,13 +48,6 @@ public:
LEAD_OUT_TRACK_NUMBER = 0xAA LEAD_OUT_TRACK_NUMBER = 0xAA
}; };
enum class ReadMode : u8
{
DataOnly, // 2048 bytes per sector.
RawSector, // 2352 bytes per sector.
RawNoSync, // 2340 bytes per sector.
};
enum class TrackMode : u8 enum class TrackMode : u8
{ {
Audio, // 2352 bytes per sector Audio, // 2352 bytes per sector
@ -296,9 +290,6 @@ public:
// Seek to track and LBA. // Seek to track and LBA.
bool Seek(u32 track_number, LBA 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);
// Read a single raw sector, and subchannel from the current LBA. // Read a single raw sector, and subchannel from the current LBA.
bool ReadRawSector(void* buffer, SubChannelQ* subq); bool ReadRawSector(void* buffer, SubChannelQ* subq);

View File

@ -4,9 +4,10 @@
#include "iso_reader.h" #include "iso_reader.h"
#include "cd_image.h" #include "cd_image.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/error.h" #include "common/error.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h"
#include "common/progress_callback.h" #include "common/progress_callback.h"
#include "common/string_util.h" #include "common/string_util.h"
@ -41,20 +42,25 @@ bool IsoReader::Open(CDImage* image, u32 track_number, Error* error)
return true; return true;
} }
bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error) bool IsoReader::ReadSector(std::span<u8, SECTOR_SIZE> buf, u32 lsn, Error* error)
{ {
if (!m_image->Seek(m_track_number, lsn)) if (!m_image->Seek(m_track_number, lsn))
{ {
Error::SetString(error, fmt::format("Failed to seek to LSN #{}", lsn)); Error::SetStringFmt(error, "Failed to seek to LSN #{}", lsn);
return false; return false;
} }
if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buf) != 1) std::array<u8, CDImage::RAW_SECTOR_SIZE> raw_sector;
std::span<const u8> sector_data;
if (!m_image->ReadRawSector(raw_sector.data(), nullptr) ||
(sector_data = ExtractSectorData(raw_sector, ReadMode::Data, error)).empty())
{ {
Error::SetString(error, fmt::format("Failed to read LSN #{}", lsn)); Error::SetStringFmt(error, "Failed to read LSN #{}: ", lsn);
return false; return false;
} }
Assert(buf.size() == SECTOR_SIZE);
std::memcpy(buf.data(), sector_data.data(), SECTOR_SIZE);
return true; return true;
} }
@ -64,13 +70,13 @@ bool IsoReader::ReadPVD(Error* error)
static constexpr u32 START_SECTOR = 16; static constexpr u32 START_SECTOR = 16;
// try only a maximum of 256 volume descriptors // try only a maximum of 256 volume descriptors
std::array<u8, SECTOR_SIZE> buffer;
for (u32 i = 0; i < 256; i++) for (u32 i = 0; i < 256; i++)
{ {
u8 buffer[SECTOR_SIZE];
if (!ReadSector(buffer, START_SECTOR + i, error)) 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.data());
if (std::memcmp(header->standard_identifier, "CD001", 5) != 0) if (std::memcmp(header->standard_identifier, "CD001", 5) != 0)
continue; continue;
else if (header->type_code != 1) else if (header->type_code != 1)
@ -79,7 +85,7 @@ bool IsoReader::ReadPVD(Error* error)
break; break;
m_pvd_lba = START_SECTOR + i; m_pvd_lba = START_SECTOR + i;
std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor)); std::memcpy(&m_pvd, buffer.data(), sizeof(ISOPrimaryVolumeDescriptor));
return true; return true;
} }
@ -101,16 +107,16 @@ std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(std::string_vi
return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le, error); return LocateFile(path, sector_buffer, root_de->location_le, root_de->length_le, error);
} }
std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset) std::string_view IsoReader::GetDirectoryEntryFileName(std::span<const u8, SECTOR_SIZE> sector, u32 de_sector_offset)
{ {
const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(sector + de_sector_offset); const ISODirectoryEntry* de = reinterpret_cast<const ISODirectoryEntry*>(sector.data() + de_sector_offset);
if ((sizeof(ISODirectoryEntry) + de->filename_length) > de->entry_length || if ((sizeof(ISODirectoryEntry) + de->filename_length) > de->entry_length ||
(sizeof(ISODirectoryEntry) + de->filename_length + de_sector_offset) > SECTOR_SIZE) (sizeof(ISODirectoryEntry) + de->filename_length + de_sector_offset) > SECTOR_SIZE)
{ {
return std::string_view(); return std::string_view();
} }
const char* str = reinterpret_cast<const char*>(sector + de_sector_offset + sizeof(ISODirectoryEntry)); const char* str = reinterpret_cast<const char*>(sector.data() + de_sector_offset + sizeof(ISODirectoryEntry));
if (de->filename_length == 1) if (de->filename_length == 1)
{ {
if (str[0] == '\0') if (str[0] == '\0')
@ -130,7 +136,71 @@ std::string_view IsoReader::GetDirectoryEntryFileName(const u8* sector, u32 de_s
return std::string_view(str, length_without_version); return std::string_view(str, length_without_version);
} }
std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(std::string_view path, u8* sector_buffer, u32 IsoReader::GetReadModeSectorSize(ReadMode mode)
{
switch (mode)
{
case ReadMode::Data:
return CDImage::DATA_SECTOR_SIZE;
case ReadMode::Mode2:
return CDImage::MODE2_DATA_SECTOR_SIZE;
case ReadMode::Raw:
return CDImage::RAW_SECTOR_SIZE;
DefaultCaseIsUnreachable();
}
}
std::span<const u8> IsoReader::ExtractSectorData(std::span<const u8> raw_sector, ReadMode mode, Error* error)
{
switch (mode)
{
case ReadMode::Data:
{
const CDImage::SectorHeader* header =
reinterpret_cast<const CDImage::SectorHeader*>(raw_sector.data() + CDImage::SECTOR_SYNC_SIZE);
if (header->sector_mode == 1)
{
return raw_sector.subspan(CDImage::SECTOR_SYNC_SIZE + CDImage::MODE1_HEADER_SIZE, CDImage::DATA_SECTOR_SIZE);
}
else if (header->sector_mode == 2)
{
return raw_sector.subspan(CDImage::SECTOR_SYNC_SIZE + CDImage::MODE2_HEADER_SIZE, CDImage::DATA_SECTOR_SIZE);
}
else
{
Error::SetStringFmt(error, "Invalid sector mode {}", header->sector_mode);
return {};
}
}
case ReadMode::Mode2:
{
const CDImage::SectorHeader* header =
reinterpret_cast<const CDImage::SectorHeader*>(raw_sector.data() + CDImage::SECTOR_SYNC_SIZE);
if (header->sector_mode != 2)
{
Error::SetStringView(error, "Non-mode 2 sector found");
return {};
}
return raw_sector.subspan(CDImage::SECTOR_SYNC_SIZE + CDImage::MODE1_HEADER_SIZE,
CDImage::MODE2_DATA_SECTOR_SIZE);
}
case ReadMode::Raw:
{
return raw_sector.subspan(0, CDImage::RAW_SECTOR_SIZE);
}
DefaultCaseIsUnreachable();
}
}
std::optional<IsoReader::ISODirectoryEntry> IsoReader::LocateFile(std::string_view path,
std::span<u8, SECTOR_SIZE> sector_buffer,
u32 directory_record_lba, u32 directory_record_size, u32 directory_record_lba, u32 directory_record_size,
Error* error) Error* error)
{ {
@ -352,16 +422,17 @@ bool IsoReader::DirectoryExists(std::string_view path, Error* error)
return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory; return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory;
} }
bool IsoReader::ReadFile(std::string_view path, std::vector<u8>* data, Error* error) bool IsoReader::ReadFile(std::string_view path, std::vector<u8>* data, ReadMode read_mode, Error* error)
{ {
auto de = LocateFile(path, error); auto de = LocateFile(path, error);
if (!de) if (!de)
return false; return false;
return ReadFile(de.value(), data, error); return ReadFile(de.value(), data, read_mode, error);
} }
bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error /*= nullptr*/) bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, ReadMode read_mode,
Error* error /*= nullptr*/)
{ {
if (de.flags & ISODirectoryEntryFlag_Directory) if (de.flags & ISODirectoryEntryFlag_Directory)
{ {
@ -375,31 +446,52 @@ bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Err
return true; return true;
} }
const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; if (!m_image->Seek(1, de.location_le))
data->resize(num_sectors * static_cast<size_t>(SECTOR_SIZE));
for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++)
{ {
if (!ReadSector(data->data() + (i * SECTOR_SIZE), lsn, error)) Error::SetStringFmt(error, "Failed to seek to LSN #{}", de.location_le);
return false; return false;
} }
// Might not be sector aligned, so reduce it back. // NOTE: ISO uses 2048 byte "sectors" in the directory listing regardless of the file mode.
data->resize(de.length_le); const u32 sector_size = GetReadModeSectorSize(read_mode);
const u32 num_sectors = de.GetSizeInSectors();
data->resize(num_sectors * sector_size);
std::array<u8, CDImage::RAW_SECTOR_SIZE> raw_sector;
size_t data_offset = 0;
for (u32 i = 0; i < num_sectors; i++)
{
std::span<const u8> sector_data;
if (!m_image->ReadRawSector(raw_sector.data(), nullptr) ||
(sector_data = ExtractSectorData(raw_sector, read_mode, error)).empty())
{
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i);
return false;
}
std::memcpy(data->data() + data_offset, sector_data.data(), sector_data.size());
data_offset += sector_data.size();
}
// only shrink for data read mode
if (read_mode == ReadMode::Data)
data->resize(de.length_le);
return true; return true;
} }
bool IsoReader::WriteFileToStream(std::string_view path, std::FILE* fp, Error* error /* = nullptr */, bool IsoReader::WriteFileToStream(std::string_view path, std::FILE* fp, ReadMode read_mode,
ProgressCallback* progress /* = nullptr */) Error* error /* = nullptr */, ProgressCallback* progress /* = nullptr */)
{ {
auto de = LocateFile(path, error); auto de = LocateFile(path, error);
if (!de) if (!de)
return false; return false;
return WriteFileToStream(de.value(), fp, error, progress); return WriteFileToStream(de.value(), fp, read_mode, error, progress);
} }
bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Error* error /* = nullptr */, bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, ReadMode read_mode,
ProgressCallback* progress /* = nullptr */) Error* error /* = nullptr */, ProgressCallback* progress /* = nullptr */)
{ {
if (de.flags & ISODirectoryEntryFlag_Directory) if (de.flags & ISODirectoryEntryFlag_Directory)
{ {
@ -413,9 +505,11 @@ bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Er
if (de.length_le == 0) if (de.length_le == 0)
return FileSystem::FTruncate64(fp, 0, error); return FileSystem::FTruncate64(fp, 0, error);
const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; if (!m_image->Seek(1, de.location_le))
u32 file_pos = 0; {
u8 sector_buffer[SECTOR_SIZE]; Error::SetStringFmt(error, "Failed to seek to LSN #{}", de.location_le);
return false;
}
if (progress) if (progress)
{ {
@ -423,13 +517,26 @@ bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Er
progress->SetProgressValue(0); progress->SetProgressValue(0);
} }
for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++) const u32 num_sectors = de.GetSizeInSectors();
{
if (!ReadSector(sector_buffer, lsn, error))
return false;
const u32 write_size = std::min<u32>(de.length_le - file_pos, SECTOR_SIZE); std::array<u8, CDImage::RAW_SECTOR_SIZE> raw_sector;
if (std::fwrite(sector_buffer, write_size, 1, fp) != 1) u32 file_pos = 0;
for (u32 i = 0; i < num_sectors; i++)
{
std::span<const u8> sector_data;
if (!m_image->ReadRawSector(raw_sector.data(), nullptr) ||
(sector_data = ExtractSectorData(raw_sector, read_mode, error)).empty())
{
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i);
return false;
}
// only shrink for data mode
const u32 write_size = (read_mode == ReadMode::Data) ?
std::min<u32>(de.length_le - file_pos, static_cast<u32>(sector_data.size())) :
static_cast<u32>(sector_data.size());
if (std::fwrite(sector_data.data(), write_size, 1, fp) != 1)
{ {
Error::SetErrno(error, "fwrite() failed: ", errno); Error::SetErrno(error, "fwrite() failed: ", errno);
return false; return false;

View File

@ -8,6 +8,7 @@
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <span>
#include <string> #include <string>
#include <vector> #include <vector>
@ -142,11 +143,21 @@ public:
#pragma pack(pop) #pragma pack(pop)
enum class ReadMode : u8
{
Data,
Mode2,
Raw,
};
IsoReader(); IsoReader();
~IsoReader(); ~IsoReader();
static std::string_view RemoveVersionIdentifierFromPath(std::string_view path); static std::string_view RemoveVersionIdentifierFromPath(std::string_view path);
static u32 GetReadModeSectorSize(ReadMode mode);
static std::span<const u8> ExtractSectorData(std::span<const u8> raw_sector, ReadMode mode, Error* error);
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 u32 GetPVDLBA() const { return m_pvd_lba; } ALWAYS_INLINE u32 GetPVDLBA() const { return m_pvd_lba; }
@ -162,22 +173,22 @@ public:
bool FileExists(std::string_view path, Error* error = nullptr); bool FileExists(std::string_view path, Error* error = nullptr);
bool DirectoryExists(std::string_view path, Error* error = nullptr); bool DirectoryExists(std::string_view path, Error* error = nullptr);
bool ReadFile(std::string_view path, std::vector<u8>* data, Error* error = nullptr); bool ReadFile(std::string_view path, std::vector<u8>* data, ReadMode read_mode, Error* error = nullptr);
bool ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Error* error = nullptr); bool ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, ReadMode read_mode, Error* error = nullptr);
bool WriteFileToStream(std::string_view path, std::FILE* fp, Error* error = nullptr, bool WriteFileToStream(std::string_view path, std::FILE* fp, ReadMode read_mode, Error* error = nullptr,
ProgressCallback* progress = nullptr); ProgressCallback* progress = nullptr);
bool WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Error* error = nullptr, bool WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, ReadMode read_mode, Error* error = nullptr,
ProgressCallback* progress = nullptr); ProgressCallback* progress = nullptr);
private: private:
static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset); static std::string_view GetDirectoryEntryFileName(std::span<const u8, SECTOR_SIZE> sector, u32 de_sector_offset);
bool ReadSector(u8* buf, u32 lsn, Error* error); bool ReadSector(std::span<u8, SECTOR_SIZE> buf, u32 lsn, Error* error);
bool ReadPVD(Error* error); bool ReadPVD(Error* error);
std::optional<ISODirectoryEntry> LocateFile(std::string_view path, u8* sector_buffer, u32 directory_record_lba, std::optional<ISODirectoryEntry> LocateFile(std::string_view path, std::span<u8, SECTOR_SIZE> sector_buffer,
u32 directory_record_size, Error* error); u32 directory_record_lba, u32 directory_record_size, Error* error);
CDImage* m_image; CDImage* m_image;
u32 m_track_number; u32 m_track_number;