ISOReader: Add XA and raw extraction modes
This commit is contained in:
parent
b68370dff7
commit
f010d81652
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue