From f010d8165269a0b2f55d3a8df10d035e06cac69e Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 17 Dec 2024 14:19:41 +1000 Subject: [PATCH] ISOReader: Add XA and raw extraction modes --- src/core/system.cpp | 16 +- src/duckstation-qt/consolesettingswidget.cpp | 2 +- src/duckstation-qt/isobrowserwindow.cpp | 41 +++-- src/duckstation-qt/isobrowserwindow.h | 5 +- src/duckstation-qt/isobrowserwindow.ui | 14 ++ src/util/cd_image.cpp | 54 ------ src/util/cd_image.h | 11 +- src/util/iso_reader.cpp | 179 +++++++++++++++---- src/util/iso_reader.h | 27 ++- 9 files changed, 215 insertions(+), 134 deletions(-) diff --git a/src/core/system.cpp b/src/core/system.cpp index f1fea7874..b57f65637 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -941,7 +941,7 @@ std::string System::GetExecutableNameForImage(IsoReader& iso, bool strip_subdire { // Read SYSTEM.CNF std::vector 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; // Parse lines @@ -1053,7 +1053,7 @@ bool System::ReadExecutableFromImage(IsoReader& iso, std::string* out_executable DEV_LOG("Executable path: '{}'", executable_path); 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); return false; @@ -1104,9 +1104,11 @@ DiscRegion System::GetRegionForSerial(const std::string_view serial) DiscRegion System::GetRegionFromSystemArea(CDImage* cdi) { // The license code is on sector 4 of the disc. - u8 sector[CDImage::DATA_SECTOR_SIZE]; + std::array sector; + std::span sector_data; 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; } @@ -1116,11 +1118,11 @@ DiscRegion System::GetRegionFromSystemArea(CDImage* cdi) static constexpr char pal_string[] = " Licensed by Sony Computer Entertainment Euro pe"; // 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; - 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; - 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; else return DiscRegion::Other; diff --git a/src/duckstation-qt/consolesettingswidget.cpp b/src/duckstation-qt/consolesettingswidget.cpp index fb4af157c..432b00006 100644 --- a/src/duckstation-qt/consolesettingswidget.cpp +++ b/src/duckstation-qt/consolesettingswidget.cpp @@ -38,7 +38,7 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* pa .arg(i) .arg(static_cast(i) * TIME_PER_SECTOR_DOUBLE_SPEED, 0, 'f', 0) - .arg(static_cast(i * CDImage::DATA_SECTOR_SIZE) / 1024.0f)); + .arg(static_cast(i * CDImage::RAW_SECTOR_SIZE) / 1024.0f)); } SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName, diff --git a/src/duckstation-qt/isobrowserwindow.cpp b/src/duckstation-qt/isobrowserwindow.cpp index b06475727..91dfdf612 100644 --- a/src/duckstation-qt/isobrowserwindow.cpp +++ b/src/duckstation-qt/isobrowserwindow.cpp @@ -29,7 +29,10 @@ ISOBrowserWindow::ISOBrowserWindow(QWidget* parent) : QWidget(parent) enableUi(false); 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.fileView, &QTreeWidget::itemActivated, this, &ISOBrowserWindow::onFileItemActivated); 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 items = m_ui.fileView->selectedItems(); if (items.isEmpty()) return; const QString path = items.front()->data(0, Qt::UserRole).toString(); - extractFile(path); + extractFile(path, mode); } void ISOBrowserWindow::onDirectoryItemClicked(QTreeWidgetItem* item, int column) @@ -141,21 +144,16 @@ void ISOBrowserWindow::onFileItemActivated(QTreeWidgetItem* item, int column) } // file, go to extract - extractFile(item->data(0, Qt::UserRole).toString()); + extractFile(item->data(0, Qt::UserRole).toString(), IsoReader::ReadMode::Data); } void ISOBrowserWindow::onFileItemSelectionChanged() { const QList items = m_ui.fileView->selectedItems(); - if (items.isEmpty()) - { - m_ui.extract->setEnabled(false); - return; - } // directory? - const bool is_directory = items.front()->data(0, Qt::UserRole + 1).toBool(); - m_ui.extract->setEnabled(!is_directory); + const bool enabled = (!items.isEmpty() && !items.front()->data(0, Qt::UserRole + 1).toBool()); + enableExtractButtons(enabled); } void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos) @@ -176,7 +174,11 @@ void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos) else { 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)); @@ -187,7 +189,7 @@ void ISOBrowserWindow::resizeFileListColumns() 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 QString filename = QtUtils::StringViewToQString(Path::GetFileName(spath)); @@ -207,7 +209,7 @@ void ISOBrowserWindow::extractFile(const QString& path) cb.SetCancellable(true); cb.SetTitle("ISO Browser"); 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)) return; @@ -256,13 +258,20 @@ void ISOBrowserWindow::enableUi(bool enabled) m_ui.fileView->setEnabled(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() { m_ui.directoryView->clear(); - m_ui.extract->setEnabled(false); + enableExtractButtons(false); QTreeWidgetItem* root = new QTreeWidgetItem; root->setIcon(0, QIcon::fromTheme("disc-line")); diff --git a/src/duckstation-qt/isobrowserwindow.h b/src/duckstation-qt/isobrowserwindow.h index d681f8801..b1dabab08 100644 --- a/src/duckstation-qt/isobrowserwindow.h +++ b/src/duckstation-qt/isobrowserwindow.h @@ -25,7 +25,6 @@ protected: private Q_SLOTS: void onOpenFileClicked(); - void onExtractClicked(); void onDirectoryItemClicked(QTreeWidgetItem* item, int column); void onFileItemActivated(QTreeWidgetItem* item, int column); void onFileItemSelectionChanged(); @@ -34,10 +33,12 @@ private Q_SLOTS: private: void enableUi(bool enabled); + void enableExtractButtons(bool enabled); void populateDirectories(); void populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent); 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; diff --git a/src/duckstation-qt/isobrowserwindow.ui b/src/duckstation-qt/isobrowserwindow.ui index 5fe32dd6f..1f0da4144 100644 --- a/src/duckstation-qt/isobrowserwindow.ui +++ b/src/duckstation-qt/isobrowserwindow.ui @@ -114,6 +114,20 @@ + + + + Extract (XA) + + + + + + + Extract (Raw) + + + diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp index 896062cec..3532f6d24 100644 --- a/src/util/cd_image.cpp +++ b/src/util/cd_image.cpp @@ -249,60 +249,6 @@ bool CDImage::Seek(u32 track_number, LBA lba) return Seek(track.start_lba + lba); } -u32 CDImage::Read(ReadMode read_mode, u32 sector_count, void* buffer) -{ - u8* buffer_ptr = static_cast(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(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) { if (m_position_in_index == m_current_index->length) diff --git a/src/util/cd_image.h b/src/util/cd_image.h index 213f0d032..295c44492 100644 --- a/src/util/cd_image.h +++ b/src/util/cd_image.h @@ -32,6 +32,7 @@ public: SECTOR_HEADER_SIZE = 4, MODE1_HEADER_SIZE = 4, MODE2_HEADER_SIZE = 12, + MODE2_DATA_SECTOR_SIZE = 2336, // header + edc FRAMES_PER_SECOND = 75, // "sectors", or "timecode frames" (not "channel frames") SECONDS_PER_MINUTE = 60, FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE, @@ -47,13 +48,6 @@ public: 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 { Audio, // 2352 bytes per sector @@ -296,9 +290,6 @@ public: // 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); - // Read a single raw sector, and subchannel from the current LBA. bool ReadRawSector(void* buffer, SubChannelQ* subq); diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp index ef9d8870b..39a9d9f21 100644 --- a/src/util/iso_reader.cpp +++ b/src/util/iso_reader.cpp @@ -4,9 +4,10 @@ #include "iso_reader.h" #include "cd_image.h" +#include "common/align.h" +#include "common/assert.h" #include "common/error.h" #include "common/file_system.h" -#include "common/log.h" #include "common/progress_callback.h" #include "common/string_util.h" @@ -41,20 +42,25 @@ bool IsoReader::Open(CDImage* image, u32 track_number, Error* error) return true; } -bool IsoReader::ReadSector(u8* buf, u32 lsn, Error* error) +bool IsoReader::ReadSector(std::span buf, u32 lsn, Error* error) { 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; } - if (m_image->Read(CDImage::ReadMode::DataOnly, 1, buf) != 1) + std::array raw_sector; + std::span 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; } + Assert(buf.size() == SECTOR_SIZE); + std::memcpy(buf.data(), sector_data.data(), SECTOR_SIZE); return true; } @@ -64,13 +70,13 @@ bool IsoReader::ReadPVD(Error* error) static constexpr u32 START_SECTOR = 16; // try only a maximum of 256 volume descriptors + std::array buffer; for (u32 i = 0; i < 256; i++) { - u8 buffer[SECTOR_SIZE]; if (!ReadSector(buffer, START_SECTOR + i, error)) return false; - const ISOVolumeDescriptorHeader* header = reinterpret_cast(buffer); + const ISOVolumeDescriptorHeader* header = reinterpret_cast(buffer.data()); if (std::memcmp(header->standard_identifier, "CD001", 5) != 0) continue; else if (header->type_code != 1) @@ -79,7 +85,7 @@ bool IsoReader::ReadPVD(Error* error) break; m_pvd_lba = START_SECTOR + i; - std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor)); + std::memcpy(&m_pvd, buffer.data(), sizeof(ISOPrimaryVolumeDescriptor)); return true; } @@ -101,16 +107,16 @@ std::optional IsoReader::LocateFile(std::string_vi 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 sector, u32 de_sector_offset) { - const ISODirectoryEntry* de = reinterpret_cast(sector + de_sector_offset); + const ISODirectoryEntry* de = reinterpret_cast(sector.data() + 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)); + const char* str = reinterpret_cast(sector.data() + de_sector_offset + sizeof(ISODirectoryEntry)); if (de->filename_length == 1) { 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); } -std::optional 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 IsoReader::ExtractSectorData(std::span raw_sector, ReadMode mode, Error* error) +{ + switch (mode) + { + case ReadMode::Data: + { + const CDImage::SectorHeader* header = + reinterpret_cast(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(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::LocateFile(std::string_view path, + std::span sector_buffer, u32 directory_record_lba, u32 directory_record_size, Error* error) { @@ -352,16 +422,17 @@ bool IsoReader::DirectoryExists(std::string_view path, Error* error) return (de->flags & ISODirectoryEntryFlag_Directory) == ISODirectoryEntryFlag_Directory; } -bool IsoReader::ReadFile(std::string_view path, std::vector* data, Error* error) +bool IsoReader::ReadFile(std::string_view path, std::vector* data, ReadMode read_mode, Error* error) { auto de = LocateFile(path, error); if (!de) return false; - return ReadFile(de.value(), data, error); + return ReadFile(de.value(), data, read_mode, error); } -bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error /*= nullptr*/) +bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector* data, ReadMode read_mode, + Error* error /*= nullptr*/) { if (de.flags & ISODirectoryEntryFlag_Directory) { @@ -375,31 +446,52 @@ bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector* data, Err return true; } - 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 (!m_image->Seek(1, de.location_le)) { - if (!ReadSector(data->data() + (i * SECTOR_SIZE), lsn, error)) - return false; + Error::SetStringFmt(error, "Failed to seek to LSN #{}", de.location_le); + return false; } - // Might not be sector aligned, so reduce it back. - data->resize(de.length_le); + // NOTE: ISO uses 2048 byte "sectors" in the directory listing regardless of the file mode. + const u32 sector_size = GetReadModeSectorSize(read_mode); + const u32 num_sectors = de.GetSizeInSectors(); + data->resize(num_sectors * sector_size); + + std::array raw_sector; + size_t data_offset = 0; + for (u32 i = 0; i < num_sectors; i++) + { + std::span 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; } -bool IsoReader::WriteFileToStream(std::string_view path, std::FILE* fp, Error* error /* = nullptr */, - ProgressCallback* progress /* = nullptr */) +bool IsoReader::WriteFileToStream(std::string_view path, std::FILE* fp, ReadMode read_mode, + Error* error /* = nullptr */, ProgressCallback* progress /* = nullptr */) { auto de = LocateFile(path, error); if (!de) 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 */, - ProgressCallback* progress /* = nullptr */) +bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, ReadMode read_mode, + Error* error /* = nullptr */, ProgressCallback* progress /* = nullptr */) { if (de.flags & ISODirectoryEntryFlag_Directory) { @@ -413,9 +505,11 @@ bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Er if (de.length_le == 0) return FileSystem::FTruncate64(fp, 0, error); - const u32 num_sectors = (de.length_le + (SECTOR_SIZE - 1)) / SECTOR_SIZE; - u32 file_pos = 0; - u8 sector_buffer[SECTOR_SIZE]; + if (!m_image->Seek(1, de.location_le)) + { + Error::SetStringFmt(error, "Failed to seek to LSN #{}", de.location_le); + return false; + } if (progress) { @@ -423,13 +517,26 @@ bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Er progress->SetProgressValue(0); } - for (u32 i = 0, lsn = de.location_le; i < num_sectors; i++, lsn++) - { - if (!ReadSector(sector_buffer, lsn, error)) - return false; + const u32 num_sectors = de.GetSizeInSectors(); - const u32 write_size = std::min(de.length_le - file_pos, SECTOR_SIZE); - if (std::fwrite(sector_buffer, write_size, 1, fp) != 1) + std::array raw_sector; + u32 file_pos = 0; + + for (u32 i = 0; i < num_sectors; i++) + { + std::span 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(de.length_le - file_pos, static_cast(sector_data.size())) : + static_cast(sector_data.size()); + if (std::fwrite(sector_data.data(), write_size, 1, fp) != 1) { Error::SetErrno(error, "fwrite() failed: ", errno); return false; diff --git a/src/util/iso_reader.h b/src/util/iso_reader.h index 54fab6d15..b85c585ba 100644 --- a/src/util/iso_reader.h +++ b/src/util/iso_reader.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -142,11 +143,21 @@ public: #pragma pack(pop) + enum class ReadMode : u8 + { + Data, + Mode2, + Raw, + }; + IsoReader(); ~IsoReader(); static std::string_view RemoveVersionIdentifierFromPath(std::string_view path); + static u32 GetReadModeSectorSize(ReadMode mode); + static std::span ExtractSectorData(std::span raw_sector, ReadMode mode, Error* error); + ALWAYS_INLINE const CDImage* GetImage() const { return m_image; } ALWAYS_INLINE u32 GetTrackNumber() const { return m_track_number; } ALWAYS_INLINE u32 GetPVDLBA() const { return m_pvd_lba; } @@ -162,22 +173,22 @@ public: bool FileExists(std::string_view path, Error* error = nullptr); bool DirectoryExists(std::string_view path, Error* error = nullptr); - bool ReadFile(std::string_view path, std::vector* data, Error* error = nullptr); - bool ReadFile(const ISODirectoryEntry& de, std::vector* data, Error* error = nullptr); + bool ReadFile(std::string_view path, std::vector* data, ReadMode read_mode, Error* error = nullptr); + bool ReadFile(const ISODirectoryEntry& de, std::vector* 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); - 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); private: - static std::string_view GetDirectoryEntryFileName(const u8* sector, u32 de_sector_offset); + static std::string_view GetDirectoryEntryFileName(std::span sector, u32 de_sector_offset); - bool ReadSector(u8* buf, u32 lsn, Error* error); + bool ReadSector(std::span buf, u32 lsn, Error* error); bool ReadPVD(Error* error); - std::optional LocateFile(std::string_view path, u8* sector_buffer, u32 directory_record_lba, - u32 directory_record_size, Error* error); + std::optional LocateFile(std::string_view path, std::span sector_buffer, + u32 directory_record_lba, u32 directory_record_size, Error* error); CDImage* m_image; u32 m_track_number;