CDImage: Add aggregate LBA-addressed batch reads

cdrom-async-readahead
Stenzek 2 weeks ago
parent 074e5ff466
commit 31547f5d35
No known key found for this signature in database

@ -952,11 +952,12 @@ DiscRegion System::GetRegionForSerial(const std::string_view serial)
DiscRegion System::GetRegionFromSystemArea(CDImage* cdi)
{
// The license code is on sector 4 of the disc.
std::array<u8, CDImage::RAW_SECTOR_SIZE> sector;
CDImage::Sector sector;
std::span<const u8> sector_data;
if (cdi->GetTrackMode(1) == CDImage::TrackMode::Audio || !cdi->Seek(1, 4) ||
!cdi->ReadRawSector(sector.data(), nullptr) ||
(sector_data = IsoReader::ExtractSectorData(sector, IsoReader::ReadMode::Data, nullptr)).empty())
if (cdi->GetTrackMode(1) == CDImage::TrackMode::Audio ||
cdi->ReadSectors(cdi->GetTrackStartPosition(1) + 4, std::span<CDImage::Sector>(&sector, 1),
CDImage::SectorReadMode::DataOnly) != 1 ||
(sector_data = IsoReader::ExtractSectorData(sector.data, IsoReader::ReadMode::Data, nullptr)).empty())
{
return DiscRegion::Other;
}

@ -169,10 +169,51 @@ TEST(CDImage, CueMode1_2048ReadsAsRaw)
std::unique_ptr<CDImage> image = CDImage::Open(cue.GetPath().c_str(), false, &error);
ASSERT_TRUE(image) << error.GetDescription();
std::array<u8, CDImage::RAW_SECTOR_SIZE> sector;
ASSERT_TRUE(image->ReadRawSector(sector.data(), nullptr));
ExpectSyncAndHeader(sector, 0x01, 2 * CDImage::FRAMES_PER_SECOND);
EXPECT_EQ(std::memcmp(&sector[16], payload.data(), payload.size()), 0);
CDImage::Sector sector;
ASSERT_EQ(image->ReadSectors(2 * CDImage::FRAMES_PER_SECOND, std::span<CDImage::Sector>(&sector, 1),
CDImage::SectorReadMode::DataAndSubQ),
1u);
ExpectSyncAndHeader(sector.data, 0x01, 2 * CDImage::FRAMES_PER_SECOND);
EXPECT_EQ(std::memcmp(&sector.data[16], payload.data(), payload.size()), 0);
}
TEST(CDImage, BatchReadUsesExplicitLBA)
{
TempFile bin("duckstation_cd_image_batch", "bin");
std::array<u8, CDImage::DATA_SECTOR_SIZE * 3> payload = {};
for (u32 sector = 0; sector < 3; sector++)
{
std::fill_n(payload.data() + (sector * CDImage::DATA_SECTOR_SIZE), CDImage::DATA_SECTOR_SIZE,
static_cast<u8>(sector + 1));
}
ASSERT_TRUE(bin.Write(payload));
TempFile cue("duckstation_cd_image_batch", "cue");
const std::string cue_data =
fmt::format("FILE \"{}\" BINARY\nTRACK 01 MODE1/2048\nINDEX 01 00:00:00\n", Path::GetFileName(bin.GetPath()));
ASSERT_TRUE(cue.WriteString(cue_data));
Error error;
std::unique_ptr<CDImage> image = CDImage::Open(cue.GetPath().c_str(), false, &error);
ASSERT_TRUE(image) << error.GetDescription();
std::array<CDImage::Sector, 2> sectors;
ASSERT_EQ(image->ReadSectors(2 * CDImage::FRAMES_PER_SECOND + 1, sectors, CDImage::SectorReadMode::DataAndSubQ),
sectors.size());
for (u32 i = 0; i < sectors.size(); i++)
{
ExpectSyncAndHeader(sectors[i].data, 0x01, 2 * CDImage::FRAMES_PER_SECOND + i + 1);
EXPECT_EQ(sectors[i].data[16], i + 2);
EXPECT_TRUE(sectors[i].subq.IsCRCValid());
}
std::array<CDImage::Sector, 4> partial;
EXPECT_EQ(image->ReadSectors(image->GetLBACount() + CDImage::LEAD_OUT_SECTOR_COUNT - 1, partial,
CDImage::SectorReadMode::DataAndSubQ),
1u);
EXPECT_EQ(image->ReadSectors(image->GetLBACount() + CDImage::LEAD_OUT_SECTOR_COUNT, partial,
CDImage::SectorReadMode::DataAndSubQ),
0u);
}
TEST(CDImage, Iso2048DetectedAsMode1)
@ -188,10 +229,12 @@ TEST(CDImage, Iso2048DetectedAsMode1)
ASSERT_TRUE(image) << error.GetDescription();
EXPECT_EQ(image->GetTrackMode(1), CDImage::TrackMode::Mode1);
std::array<u8, CDImage::RAW_SECTOR_SIZE> sector;
ASSERT_TRUE(image->ReadRawSector(sector.data(), nullptr));
ExpectSyncAndHeader(sector, 0x01, 2 * CDImage::FRAMES_PER_SECOND);
EXPECT_EQ(std::memcmp(&sector[16], payload.data(), payload.size()), 0);
CDImage::Sector sector;
ASSERT_EQ(image->ReadSectors(2 * CDImage::FRAMES_PER_SECOND, std::span<CDImage::Sector>(&sector, 1),
CDImage::SectorReadMode::DataAndSubQ),
1u);
ExpectSyncAndHeader(sector.data, 0x01, 2 * CDImage::FRAMES_PER_SECOND);
EXPECT_EQ(std::memcmp(&sector.data[16], payload.data(), payload.size()), 0);
}
TEST(CDImage, IsoRawDetectedAsRaw)
@ -210,7 +253,9 @@ TEST(CDImage, IsoRawDetectedAsRaw)
ASSERT_TRUE(image) << error.GetDescription();
EXPECT_EQ(image->GetTrackMode(1), CDImage::TrackMode::Mode2Raw);
std::array<u8, CDImage::RAW_SECTOR_SIZE> sector;
ASSERT_TRUE(image->ReadRawSector(sector.data(), nullptr));
EXPECT_EQ(std::memcmp(sector.data(), raw.data(), raw.size()), 0);
CDImage::Sector sector;
ASSERT_EQ(image->ReadSectors(2 * CDImage::FRAMES_PER_SECOND, std::span<CDImage::Sector>(&sector, 1),
CDImage::SectorReadMode::DataAndSubQ),
1u);
EXPECT_EQ(std::memcmp(sector.data.data(), raw.data(), raw.size()), 0);
}

@ -236,37 +236,37 @@ void CDImage::ConvertSectorToRaw(void* buffer, u32 lba, TrackMode mode)
}
}
CDImage::LBA CDImage::GetTrackStartPosition(u8 track) const
CDImage::LBA CDImage::GetTrackStartPosition(u32 track) const
{
Assert(track > 0 && track <= m_tracks.size());
return m_tracks[track - 1].start_lba;
}
CDImage::Position CDImage::GetTrackStartMSFPosition(u8 track) const
CDImage::Position CDImage::GetTrackStartMSFPosition(u32 track) const
{
Assert(track > 0 && track <= m_tracks.size());
return Position::FromLBA(m_tracks[track - 1].start_lba);
}
CDImage::LBA CDImage::GetTrackLength(u8 track) const
CDImage::LBA CDImage::GetTrackLength(u32 track) const
{
Assert(track > 0 && track <= m_tracks.size());
return m_tracks[track - 1].length;
}
CDImage::Position CDImage::GetTrackMSFLength(u8 track) const
CDImage::Position CDImage::GetTrackMSFLength(u32 track) const
{
Assert(track > 0 && track <= m_tracks.size());
return Position::FromLBA(m_tracks[track - 1].length);
}
CDImage::TrackMode CDImage::GetTrackMode(u8 track) const
CDImage::TrackMode CDImage::GetTrackMode(u32 track) const
{
Assert(track > 0 && track <= m_tracks.size());
return m_tracks[track - 1].mode;
}
CDImage::LBA CDImage::GetTrackIndexPosition(u8 track, u8 index) const
CDImage::LBA CDImage::GetTrackIndexPosition(u32 track, u32 index) const
{
for (const Index& current_index : m_indices)
{
@ -277,7 +277,7 @@ CDImage::LBA CDImage::GetTrackIndexPosition(u8 track, u8 index) const
return m_lba_count;
}
CDImage::LBA CDImage::GetTrackIndexLength(u8 track, u8 index) const
CDImage::LBA CDImage::GetTrackIndexLength(u32 track, u32 index) const
{
for (const Index& current_index : m_indices)
{
@ -360,52 +360,96 @@ bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq)
return false;
}
Sector sector;
if (ReadSectors(
m_position_on_disc, std::span<Sector>(&sector, 1),
(buffer ? (subq ? SectorReadMode::DataAndSubQ : SectorReadMode::DataOnly) : SectorReadMode::SubQOnly)) != 1)
{
ERROR_LOG("Read of LBA {} failed", m_position_on_disc);
Seek(m_position_on_disc);
return false;
}
if (buffer)
std::memcpy(buffer, sector.data.data(), sector.data.size());
if (subq)
*subq = sector.subq;
m_position_on_disc++;
m_position_in_index++;
m_position_in_track++;
return true;
}
u32 CDImage::ReadSectors(LBA lba, std::span<Sector> sectors, SectorReadMode mode)
{
const bool read_data = (mode != SectorReadMode::SubQOnly);
const bool read_subq = (mode != SectorReadMode::DataOnly);
u32 sectors_read = 0;
while (sectors_read < sectors.size())
{
if (m_current_index->file_sector_size > 0)
const LBA current_lba = lba + sectors_read;
if (current_lba < lba)
break;
const Index* index = GetIndexForDiscPosition(current_lba);
if (!index)
break;
const LBA lba_in_index = current_lba - index->start_lba_on_disc;
const u32 count = static_cast<u32>(
std::min<size_t>(sectors.size() - sectors_read, static_cast<size_t>(index->length - lba_in_index)));
std::span<Sector> chunk = sectors.subspan(sectors_read, count);
if (read_subq)
{
if (!ReadSectorFromIndex(buffer, *m_current_index, m_position_in_index))
{
ERROR_LOG("Read of LBA {} failed", m_position_on_disc);
Seek(m_position_on_disc);
return false;
}
for (u32 i = 0; i < count; i++)
GenerateSubChannelQ(&chunk[i].subq, *index, lba_in_index + i);
}
// Fix up the sector header and sync data if necessary.
ConvertSectorToRaw(buffer, m_current_index->start_lba_on_disc + m_position_in_index, m_current_index->mode);
u32 chunk_read;
const bool read_replacement_subq = (read_subq && HasSubchannelData() && index->submode != SubchannelMode::None);
if (!read_data && !read_replacement_subq)
{
// Generated SubQ is already complete. Do not touch a backing file just to return synthesized position data.
chunk_read = count;
}
else
else if (index->file_sector_size > 0)
{
if (m_current_index->track_number == LEAD_OUT_TRACK_NUMBER)
chunk_read = std::min(ReadSectorsFromIndex(chunk, *index, lba_in_index, mode), count);
if (read_data)
{
// Lead-out area.
std::fill(static_cast<u8*>(buffer), static_cast<u8*>(buffer) + RAW_SECTOR_SIZE, u8(0xAA));
for (u32 i = 0; i < chunk_read; i++)
ConvertSectorToRaw(chunk[i].data.data(), current_lba + i, index->mode);
}
else
}
else
{
if (read_data)
{
// This in an implicit pregap. Return silence.
std::fill(static_cast<u8*>(buffer), static_cast<u8*>(buffer) + RAW_SECTOR_SIZE, u8(0));
// Synthesize sectors which do not have a backing file.
if (index->track_number == LEAD_OUT_TRACK_NUMBER)
{
// Lead-out area.
for (Sector& sector : chunk)
sector.data.fill(0xAA);
}
else
{
// This is an implicit pregap. Return silence.
for (Sector& sector : chunk)
sector.data.fill(0);
}
}
chunk_read = count;
}
}
if (subq && !ReadSubChannelQ(subq, *m_current_index, m_position_in_index))
{
ERROR_LOG("Subchannel read of LBA {} failed", m_position_on_disc);
Seek(m_position_on_disc);
return false;
sectors_read += chunk_read;
if (chunk_read != count)
break;
}
m_position_on_disc++;
m_position_in_index++;
m_position_in_track++;
return true;
}
bool CDImage::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
GenerateSubChannelQ(subq, index, lba_in_index);
return true;
return sectors_read;
}
bool CDImage::HasSubchannelData() const

@ -8,6 +8,7 @@
#include <array>
#include <memory>
#include <span>
#include <string>
#include <tuple>
#include <vector>
@ -73,6 +74,13 @@ public:
Success,
};
enum class SectorReadMode : u8
{
DataAndSubQ,
DataOnly,
SubQOnly,
};
struct SectorHeader
{
u8 minute;
@ -150,6 +158,15 @@ public:
};
static_assert(sizeof(SubChannelQ) == SUBCHANNEL_BYTES_PER_FRAME, "SubChannelQ is correct size");
using SectorData = std::array<u8, RAW_SECTOR_SIZE>;
struct Sector
{
SectorData data;
SubChannelQ subq;
};
static_assert(sizeof(Sector) == (RAW_SECTOR_SIZE + SUBCHANNEL_BYTES_PER_FRAME));
struct Track
{
u32 track_number;
@ -223,13 +240,13 @@ public:
u32 GetIndexNumber() const { return m_current_index->index_number; }
u32 GetTrackNumber() const { return m_current_index->track_number; }
u32 GetTrackCount() const { return static_cast<u32>(m_tracks.size()); }
LBA GetTrackStartPosition(u8 track) const;
Position GetTrackStartMSFPosition(u8 track) const;
LBA GetTrackLength(u8 track) const;
Position GetTrackMSFLength(u8 track) const;
TrackMode GetTrackMode(u8 track) const;
LBA GetTrackIndexPosition(u8 track, u8 index) const;
LBA GetTrackIndexLength(u8 track, u8 index) const;
LBA GetTrackStartPosition(u32 track) const;
Position GetTrackStartMSFPosition(u32 track) const;
LBA GetTrackLength(u32 track) const;
Position GetTrackMSFLength(u32 track) const;
TrackMode GetTrackMode(u32 track) const;
LBA GetTrackIndexPosition(u32 track, u32 index) const;
LBA GetTrackIndexLength(u32 track, u32 index) const;
u32 GetFirstTrackNumber() const { return m_tracks.front().track_number; }
u32 GetLastTrackNumber() const { return m_tracks.back().track_number; }
u32 GetIndexCount() const { return static_cast<u32>(m_indices.size()); }
@ -253,23 +270,26 @@ public:
// Read a single raw sector, and subchannel from the current LBA.
bool ReadRawSector(void* buffer, SubChannelQ* subq);
/// Reads the requested components of consecutive raw sectors beginning at the specified LBA, leaving unrequested
/// components untouched. Returns the number of sectors successfully read.
u32 ReadSectors(LBA lba, std::span<Sector> sectors, SectorReadMode mode);
/// Generates sub-channel Q given the specified position.
bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba) const;
/// Generates sub-channel Q from the given index and index-offset.
void GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset) const;
// Reads sub-channel Q for the specified index+LBA.
virtual bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index);
// Returns true if the image has replacement subchannel data.
virtual bool HasSubchannelData() const;
/// Returns true if reads are serviced by a physical CD-ROM device.
virtual bool IsPhysicalDevice() const;
// Reads a single sector from an index.
virtual bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) = 0;
/// Reads sectors from a single index. When requested, SubQ is pre-filled with generated data and may be replaced by
/// the backend.
virtual u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) = 0;
// Returns true if this image type has sub-images (e.g. m3u).
virtual bool HasSubImages() const;

@ -30,11 +30,11 @@ public:
s64 GetSizeOnDisk() const override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
static constexpr SubchannelMode SUBCHANNEL_MODE = SubchannelMode::Raw;
@ -390,49 +390,53 @@ bool CDImageCCD::OpenAndParse(const char* path, Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageCCD::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImageCCD::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
const s64 file_position = static_cast<s64>(index.file_offset + (static_cast<u64>(lba_in_index) * IMG_SECTOR_SIZE));
if (m_img_file_position != file_position)
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
if (FileSystem::FSeek64(m_img_file, file_position, SEEK_SET) != 0)
return false;
m_img_file_position = file_position;
}
if (std::fread(buffer, IMG_SECTOR_SIZE, 1, m_img_file) != 1)
{
FileSystem::FSeek64(m_img_file, m_img_file_position, SEEK_SET);
return false;
}
m_img_file_position += IMG_SECTOR_SIZE;
return true;
}
const LBA current_lba_in_index = lba_in_index + sectors_read;
if (mode != SectorReadMode::SubQOnly)
{
const s64 file_position =
static_cast<s64>(index.file_offset + (static_cast<u64>(current_lba_in_index) * IMG_SECTOR_SIZE));
if (m_img_file_position != file_position)
{
if (FileSystem::FSeek64(m_img_file, file_position, SEEK_SET) != 0)
break;
bool CDImageCCD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
// For virtual pregaps (not in file), fall back to generated subchannel Q.
if (!m_sub_file || (index.is_pregap && index.file_sector_size == 0))
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
m_img_file_position = file_position;
}
// Q subchannel is the second 12-byte block (P, Q, R, S, T, U, V, W).
static constexpr u64 q_offset = SUBCHANNEL_BYTES_PER_FRAME;
if (std::fread(sector.data.data(), IMG_SECTOR_SIZE, 1, m_img_file) != 1)
{
FileSystem::FSeek64(m_img_file, m_img_file_position, SEEK_SET);
break;
}
m_img_file_position += IMG_SECTOR_SIZE;
}
// Have to wrangle this because of the two second implicit pregap.
const s64 sub_offset = static_cast<s64>(((index.file_offset / IMG_SECTOR_SIZE) * ALL_SUBCODE_SIZE) +
(static_cast<u64>(lba_in_index) * ALL_SUBCODE_SIZE) + q_offset);
// For virtual pregaps (not in file), keep the generated subchannel Q.
if (mode != SectorReadMode::DataOnly && m_sub_file)
{
// Q subchannel is the second 12-byte block (P, Q, R, S, T, U, V, W).
// Have to wrangle this because of the two second implicit pregap.
const u64 sub_offset = ((index.file_offset / IMG_SECTOR_SIZE) * ALL_SUBCODE_SIZE) +
(static_cast<u64>(current_lba_in_index) * ALL_SUBCODE_SIZE) + SUBCHANNEL_BYTES_PER_FRAME;
// Since we're only reading partially, the position's never going to match for sequential. Always seek.
if (FileSystem::FSeek64(m_sub_file, static_cast<s64>(sub_offset), SEEK_SET) != 0 ||
std::fread(sector.subq.data.data(), SUBCHANNEL_BYTES_PER_FRAME, 1, m_sub_file) != 1)
{
WARNING_LOG("Failed to read subq for sector {}", index.start_lba_on_disc + current_lba_in_index);
// Keep the generated Q which was placed in the output by CDImage::ReadSectors().
}
}
// Since we're only reading partially, the position's never going to match for sequential. Always seek.
if (FileSystem::FSeek64(m_sub_file, static_cast<s64>(sub_offset), SEEK_SET) != 0 ||
std::fread(subq->data.data(), SUBCHANNEL_BYTES_PER_FRAME, 1, m_sub_file) != 1)
{
WARNING_LOG("Failed to read subq for sector {}", index.start_lba_on_disc + lba_in_index);
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
sectors_read++;
}
return true;
return sectors_read;
}
bool CDImageCCD::HasSubchannelData() const

@ -65,14 +65,14 @@ public:
bool Open(const char* path, Error* error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
PrecacheResult Precache(ProgressCallback* progress, Error* error) override;
bool IsPrecached() const override;
s64 GetSizeOnDisk() const override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
static constexpr u32 CHD_CD_SECTOR_DATA_SIZE = 2352 + 96;
@ -416,29 +416,6 @@ bool CDImageCHD::Open(const char* path, Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
if (index.submode == CDImage::SubchannelMode::None)
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
u32 hunk_offset;
if (!UpdateHunkBuffer(index, lba_in_index, hunk_offset))
return false;
u8 deinterleaved_subchannel_data[96];
const u8* raw_subchannel_data = &m_hunk_buffer[hunk_offset + RAW_SECTOR_SIZE];
const u8* real_subchannel_data = raw_subchannel_data;
if (index.submode == CDImage::SubchannelMode::RawInterleaved)
{
DeinterleaveSubcode(raw_subchannel_data, deinterleaved_subchannel_data);
real_subchannel_data = deinterleaved_subchannel_data;
}
// P, Q, R, S, T, U, V, W
std::memcpy(subq->data.data(), real_subchannel_data + (1 * SUBCHANNEL_BYTES_PER_FRAME), SUBCHANNEL_BYTES_PER_FRAME);
return true;
}
bool CDImageCHD::HasSubchannelData() const
{
// Just look at the first track for in-CHD subq.
@ -495,19 +472,45 @@ ALWAYS_INLINE_RELEASE void CDImageCHD::CopyAndSwap(void* dst_ptr, const u8* src_
}
}
bool CDImageCHD::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImageCHD::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
u32 hunk_offset;
if (!UpdateHunkBuffer(index, lba_in_index, hunk_offset))
return false;
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
u32 hunk_offset;
if (!UpdateHunkBuffer(index, lba_in_index + sectors_read, hunk_offset))
break;
// Audio data is in big-endian, so we have to swap it for little endian hosts...
if (index.mode == TrackMode::Audio)
CopyAndSwap(buffer, &m_hunk_buffer[hunk_offset]);
else
std::memcpy(buffer, &m_hunk_buffer[hunk_offset], RAW_SECTOR_SIZE);
if (mode != SectorReadMode::SubQOnly)
{
// Audio data is in big-endian, so we have to swap it for little endian hosts...
if (index.mode == TrackMode::Audio)
CopyAndSwap(sector.data.data(), &m_hunk_buffer[hunk_offset]);
else
std::memcpy(sector.data.data(), &m_hunk_buffer[hunk_offset], RAW_SECTOR_SIZE);
}
return true;
if (mode != SectorReadMode::DataOnly && index.submode != CDImage::SubchannelMode::None)
{
u8 deinterleaved_subchannel_data[ALL_SUBCODE_SIZE];
const u8* raw_subchannel_data = &m_hunk_buffer[hunk_offset + RAW_SECTOR_SIZE];
const u8* real_subchannel_data = raw_subchannel_data;
if (index.submode == CDImage::SubchannelMode::RawInterleaved)
{
DeinterleaveSubcode(raw_subchannel_data, deinterleaved_subchannel_data);
real_subchannel_data = deinterleaved_subchannel_data;
}
// P, Q, R, S, T, U, V, W
std::memcpy(sector.subq.data.data(), real_subchannel_data + SUBCHANNEL_BYTES_PER_FRAME,
SUBCHANNEL_BYTES_PER_FRAME);
}
sectors_read++;
}
return sectors_read;
}
ALWAYS_INLINE_RELEASE bool CDImageCHD::UpdateHunkBuffer(const Index& index, LBA lba_in_index, u32& hunk_offset)

@ -148,7 +148,8 @@ public:
s64 GetSizeOnDisk() const override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
std::optional<TrackMode> DetectSingleFileTrackMode(TrackFileInterface* fi, const char* path, Error* error);
@ -910,20 +911,27 @@ std::optional<CDImage::TrackMode> CDImageCueSheet::DetectSingleFileTrackMode(Tra
return TrackMode::Mode1;
}
bool CDImageCueSheet::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImageCueSheet::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
DebugAssert(index.file_index < m_files.size());
(void)mode;
TrackFileInterface* tf = m_files[index.file_index].get();
const u64 file_position = index.file_offset + (static_cast<u64>(lba_in_index) * index.file_sector_size);
Error error;
if (!tf->Read(buffer, file_position, index.file_sector_size, &error)) [[unlikely]]
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
ERROR_LOG("Failed to read LBA {}: {}", lba_in_index, error.GetDescription());
return false;
const u64 file_position =
index.file_offset + (static_cast<u64>(lba_in_index + sectors_read) * index.file_sector_size);
Error error;
if (!tf->Read(sector.data.data(), file_position, index.file_sector_size, &error)) [[unlikely]]
{
ERROR_LOG("Failed to read LBA {}: {}", lba_in_index + sectors_read, error.GetDescription());
break;
}
sectors_read++;
}
return true;
return sectors_read;
}
s64 CDImageCueSheet::GetSizeOnDisk() const

@ -38,17 +38,20 @@ enum class SCSIReadMode : u8
SubQOnly,
};
[[maybe_unused]] static void FillSCSIReadCommand(u8 cmd[SCSI_CMD_LENGTH], u32 sector_number, SCSIReadMode mode)
[[maybe_unused]] static void FillSCSIReadCommand(u8 cmd[SCSI_CMD_LENGTH], u32 sector_number, u32 sector_count,
SCSIReadMode mode)
{
DebugAssert(sector_count > 0 && sector_count <= 0xFFFFFF);
cmd[0] = 0xBE; // READ CD
cmd[1] = 0x00; // sector type
cmd[2] = Truncate8(sector_number >> 24); // Starting LBA
cmd[3] = Truncate8(sector_number >> 16);
cmd[4] = Truncate8(sector_number >> 8);
cmd[5] = Truncate8(sector_number);
cmd[6] = 0x00; // Transfer Count
cmd[7] = 0x00;
cmd[8] = 0x01;
cmd[6] = Truncate8(sector_count >> 16); // Transfer Count
cmd[7] = Truncate8(sector_count >> 8);
cmd[8] = Truncate8(sector_count);
cmd[9] = (1 << 7) | // include sync
(0b11 << 5) | // include header codes
(1 << 4) | // include user data
@ -98,6 +101,25 @@ enum class SCSIReadMode : u8
}
}
[[maybe_unused]] static void CopySCSISubChannelQ(CDImage::SubChannelQ* subq, const u8* buffer, SCSIReadMode mode)
{
if (mode == SCSIReadMode::SubQOnly)
{
// Copy out subq.
std::memcpy(subq->data.data(), buffer + CDImage::RAW_SECTOR_SIZE, CDImage::SUBCHANNEL_BYTES_PER_FRAME);
}
else if (mode == SCSIReadMode::Full || mode == SCSIReadMode::None)
{
// Need to deinterleave the subcode.
u8 deinterleaved_subcode[CDImage::ALL_SUBCODE_SIZE];
CDImage::DeinterleaveSubcode(buffer + CDImage::RAW_SECTOR_SIZE, deinterleaved_subcode);
// P, Q, ...
std::memcpy(subq->data.data(), deinterleaved_subcode + CDImage::SUBCHANNEL_BYTES_PER_FRAME,
CDImage::SUBCHANNEL_BYTES_PER_FRAME);
}
}
[[maybe_unused]] static bool VerifySCSIReadData(std::span<const u8> buffer, SCSIReadMode mode,
CDImage::LBA expected_sector)
{
@ -213,17 +235,19 @@ public:
bool Open(const char* path, Error* error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
bool IsPhysicalDevice() const override { return true; }
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
std::optional<u32> DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer);
std::optional<u32> DoSCSIRead(LBA lba, SCSIReadMode read_mode);
std::optional<u32> DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode, std::span<u8> out_buffer);
bool DoRawRead(LBA lba);
bool DoRawRead(LBA lba, u32 sector_count, std::span<u8> out_buffer);
bool DoSetSpeed(u32 speed_multiplier);
bool ReadSectorToBuffer(LBA lba);
@ -235,8 +259,10 @@ private:
SCSIReadMode m_scsi_read_mode = SCSIReadMode::None;
bool m_has_valid_subcode = false;
bool m_supports_batch_reads = true;
std::array<u8, CD_RAW_SECTOR_WITH_SUBCODE_SIZE> m_buffer;
std::vector<u8> m_batch_buffer;
};
} // namespace
@ -416,31 +442,69 @@ bool CDImageDeviceWin32::Open(const char* path, Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageDeviceWin32::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
u32 CDImageDeviceWin32::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
if (index.file_sector_size == 0 || !m_has_valid_subcode)
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
const LBA start_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
bool batch_failed = false;
if (m_supports_batch_reads && sectors.size() > 1)
{
const u32 sector_count = static_cast<u32>(sectors.size());
const u32 sector_size = (m_scsi_read_mode == SCSIReadMode::None) ?
static_cast<u32>(CD_RAW_SECTOR_WITH_SUBCODE_SIZE) :
SCSIReadCommandOutputSize(m_scsi_read_mode);
m_batch_buffer.resize(static_cast<size_t>(sector_size) * sector_count);
const bool batch_result =
(m_scsi_read_mode != SCSIReadMode::None) ?
(DoSCSIRead(start_lba, sector_count, m_scsi_read_mode, m_batch_buffer).value_or(0) == m_batch_buffer.size()) :
DoRawRead(start_lba, sector_count, m_batch_buffer);
if (batch_result)
{
for (u32 i = 0; i < sector_count; i++)
{
const u8* const source = m_batch_buffer.data() + (static_cast<size_t>(i) * sector_size);
if (mode != SectorReadMode::SubQOnly)
std::memcpy(sectors[i].data.data(), source, RAW_SECTOR_SIZE);
if (mode != SectorReadMode::DataOnly && m_has_valid_subcode)
CopySCSISubChannelQ(&sectors[i].subq, source, m_scsi_read_mode);
}
const LBA offset = static_cast<LBA>(index.file_offset) + lba_in_index;
if (m_current_lba != offset && !ReadSectorToBuffer(offset))
return false;
// Keep the scalar cache and its tag synchronized in case the next request repeats the end of this batch.
std::memcpy(m_buffer.data(), m_batch_buffer.data() + (static_cast<size_t>(sector_count - 1) * sector_size),
sector_size);
m_current_lba = start_lba + sector_count - 1;
return sector_count;
}
if (m_scsi_read_mode == SCSIReadMode::SubQOnly)
{
// copy out subq
std::memcpy(subq->data.data(), m_buffer.data() + RAW_SECTOR_SIZE, SUBCHANNEL_BYTES_PER_FRAME);
return true;
WARNING_LOG("Batch read of {} sectors at LBA {} failed, retrying individually", sector_count, start_lba);
batch_failed = true;
}
else // if (m_scsi_read_mode == SCSIReadMode::Full || m_scsi_read_mode == SCSIReadMode::None)
// Retry individually on errors so that callers receive the exact readable prefix.
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
// need to deinterleave the subcode
u8 deinterleaved_subcode[ALL_SUBCODE_SIZE];
DeinterleaveSubcode(m_buffer.data() + RAW_SECTOR_SIZE, deinterleaved_subcode);
const LBA offset = start_lba + sectors_read;
if (m_current_lba != offset && !ReadSectorToBuffer(offset))
break;
// P, Q, ...
std::memcpy(subq->data.data(), deinterleaved_subcode + SUBCHANNEL_BYTES_PER_FRAME, SUBCHANNEL_BYTES_PER_FRAME);
return true;
if (mode != SectorReadMode::SubQOnly)
std::memcpy(sector.data.data(), m_buffer.data(), RAW_SECTOR_SIZE);
if (mode != SectorReadMode::DataOnly && m_has_valid_subcode)
CopySCSISubChannelQ(&sector.subq, m_buffer.data(), m_scsi_read_mode);
sectors_read++;
}
if (batch_failed && sectors_read == sectors.size())
{
// A completely successful scalar retry distinguishes an unsupported/mishandled multi-sector command from an
// unreadable sector. Avoid paying the device timeout on every subsequent refill.
WARNING_LOG("Disabling multi-sector reads after {} sectors at LBA {} succeeded individually", sectors_read,
start_lba);
m_supports_batch_reads = false;
}
return sectors_read;
}
bool CDImageDeviceWin32::HasSubchannelData() const
@ -448,19 +512,6 @@ bool CDImageDeviceWin32::HasSubchannelData() const
return m_has_valid_subcode;
}
bool CDImageDeviceWin32::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
{
if (index.file_sector_size == 0)
return false;
const LBA offset = static_cast<LBA>(index.file_offset) + lba_in_index;
if (m_current_lba != offset && !ReadSectorToBuffer(offset))
return false;
std::memcpy(buffer, m_buffer.data(), RAW_SECTOR_SIZE);
return true;
}
std::optional<u32> CDImageDeviceWin32::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer)
{
struct SPTDBuffer
@ -500,12 +551,18 @@ std::optional<u32> CDImageDeviceWin32::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], st
}
std::optional<u32> CDImageDeviceWin32::DoSCSIRead(LBA lba, SCSIReadMode read_mode)
{
const u32 size = SCSIReadCommandOutputSize(read_mode);
return DoSCSIRead(lba, 1, read_mode, std::span<u8>(m_buffer.data(), size));
}
std::optional<u32> CDImageDeviceWin32::DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode,
std::span<u8> out_buffer)
{
u8 cmd[SCSI_CMD_LENGTH];
FillSCSIReadCommand(cmd, lba, read_mode);
FillSCSIReadCommand(cmd, lba, sector_count, read_mode);
const u32 size = SCSIReadCommandOutputSize(read_mode);
return DoSCSICommand(cmd, std::span<u8>(m_buffer.data(), size));
return DoSCSICommand(cmd, out_buffer);
}
bool CDImageDeviceWin32::DoSetSpeed(u32 speed_multiplier)
@ -517,23 +574,32 @@ bool CDImageDeviceWin32::DoSetSpeed(u32 speed_multiplier)
bool CDImageDeviceWin32::DoRawRead(LBA lba)
{
const DWORD expected_size = RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE;
return DoRawRead(lba, 1, m_buffer);
}
bool CDImageDeviceWin32::DoRawRead(LBA lba, u32 sector_count, std::span<u8> out_buffer)
{
const DWORD expected_size = static_cast<DWORD>((RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE) * sector_count);
DebugAssert(out_buffer.size() >= expected_size);
RAW_READ_INFO rri;
rri.DiskOffset.QuadPart = static_cast<u64>(lba) * 2048;
rri.SectorCount = 1;
rri.SectorCount = sector_count;
rri.TrackMode = RawWithSubCode;
DWORD bytes_returned;
if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_RAW_READ, &rri, sizeof(rri), m_buffer.data(),
static_cast<DWORD>(m_buffer.size()), &bytes_returned, nullptr))
if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_RAW_READ, &rri, sizeof(rri), out_buffer.data(), expected_size,
&bytes_returned, nullptr))
{
ERROR_LOG("DeviceIoControl(IOCTL_CDROM_RAW_READ) for LBA {} failed: {:08X}", lba, GetLastError());
return false;
}
if (bytes_returned != expected_size)
{
WARNING_LOG("Only read {} of {} bytes", bytes_returned, expected_size);
return false;
}
return true;
}
@ -705,12 +771,12 @@ public:
bool Open(const char* filename, Error* error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
bool IsPhysicalDevice() const override { return true; }
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
// Raw reads use an offset of 00:02:00
@ -721,6 +787,7 @@ private:
std::optional<u32> DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer);
std::optional<u32> DoSCSIRead(LBA lba, SCSIReadMode read_mode);
std::optional<u32> DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode, std::span<u8> out_buffer);
bool DoRawRead(LBA lba);
bool DoSetSpeed(u32 speed_multiplier);
@ -728,8 +795,10 @@ private:
LBA m_current_lba = ~static_cast<LBA>(0);
SCSIReadMode m_scsi_read_mode = SCSIReadMode::None;
bool m_supports_batch_reads = true;
std::array<u8, RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE> m_buffer;
std::vector<u8> m_batch_buffer;
};
} // namespace
@ -908,31 +977,62 @@ bool CDImageDeviceLinux::Open(const char* filename, Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageDeviceLinux::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
u32 CDImageDeviceLinux::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
if (index.file_sector_size == 0 || m_scsi_read_mode < SCSIReadMode::Full)
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
const LBA start_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
bool batch_failed = false;
if (m_supports_batch_reads && m_scsi_read_mode != SCSIReadMode::None && sectors.size() > 1)
{
const u32 sector_count = static_cast<u32>(sectors.size());
const u32 sector_size = SCSIReadCommandOutputSize(m_scsi_read_mode);
m_batch_buffer.resize(static_cast<size_t>(sector_size) * sector_count);
if (DoSCSIRead(start_lba, sector_count, m_scsi_read_mode, m_batch_buffer).value_or(0) == m_batch_buffer.size())
{
for (u32 i = 0; i < sector_count; i++)
{
const u8* const source = m_batch_buffer.data() + (static_cast<size_t>(i) * sector_size);
if (mode != SectorReadMode::SubQOnly)
std::memcpy(sectors[i].data.data(), source, RAW_SECTOR_SIZE);
if (mode != SectorReadMode::DataOnly && m_scsi_read_mode >= SCSIReadMode::Full)
CopySCSISubChannelQ(&sectors[i].subq, source, m_scsi_read_mode);
}
const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
return false;
// Keep the scalar cache and its tag synchronized in case the next request repeats the end of this batch.
std::memcpy(m_buffer.data(), m_batch_buffer.data() + (static_cast<size_t>(sector_count - 1) * sector_size),
sector_size);
m_current_lba = start_lba + sector_count - 1;
return sector_count;
}
if (m_scsi_read_mode == SCSIReadMode::SubQOnly)
{
// copy out subq
std::memcpy(subq->data.data(), m_buffer.data() + RAW_SECTOR_SIZE, SUBCHANNEL_BYTES_PER_FRAME);
return true;
WARNING_LOG("Batch read of {} sectors at LBA {} failed, retrying individually", sector_count, start_lba);
batch_failed = true;
}
else // if (m_scsi_read_mode == SCSIReadMode::Full)
// CDROMREADRAW cannot read multiple sectors. Scalar reads also determine the exact prefix after a batch error.
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
// need to deinterleave the subcode
u8 deinterleaved_subcode[ALL_SUBCODE_SIZE];
DeinterleaveSubcode(m_buffer.data() + RAW_SECTOR_SIZE, deinterleaved_subcode);
const LBA disc_lba = start_lba + sectors_read;
if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
break;
// P, Q, ...
std::memcpy(subq->data.data(), deinterleaved_subcode + SUBCHANNEL_BYTES_PER_FRAME, SUBCHANNEL_BYTES_PER_FRAME);
return true;
if (mode != SectorReadMode::SubQOnly)
std::memcpy(sector.data.data(), m_buffer.data(), RAW_SECTOR_SIZE);
if (mode != SectorReadMode::DataOnly && m_scsi_read_mode >= SCSIReadMode::Full)
CopySCSISubChannelQ(&sector.subq, m_buffer.data(), m_scsi_read_mode);
sectors_read++;
}
if (batch_failed && sectors_read == sectors.size())
{
// A completely successful scalar retry distinguishes an unsupported/mishandled multi-sector command from an
// unreadable sector. Avoid paying the device timeout on every subsequent refill.
WARNING_LOG("Disabling multi-sector reads after {} sectors at LBA {} succeeded individually", sectors_read,
start_lba);
m_supports_batch_reads = false;
}
return sectors_read;
}
bool CDImageDeviceLinux::HasSubchannelData() const
@ -941,19 +1041,6 @@ bool CDImageDeviceLinux::HasSubchannelData() const
return m_scsi_read_mode >= SCSIReadMode::Full;
}
bool CDImageDeviceLinux::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
{
if (index.file_sector_size == 0)
return false;
const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
return false;
std::memcpy(buffer, m_buffer.data(), RAW_SECTOR_SIZE);
return true;
}
std::optional<u32> CDImageDeviceLinux::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span<u8> out_buffer)
{
sg_io_hdr_t hdr;
@ -978,16 +1065,29 @@ std::optional<u32> CDImageDeviceLinux::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], st
return std::nullopt;
}
return hdr.dxfer_len;
u32 transferred = hdr.dxfer_len;
if (hdr.resid > 0)
{
const u32 residual = std::min(static_cast<u32>(hdr.resid), transferred);
transferred -= residual;
WARNING_LOG("SCSI command {:02X} transferred {} of {} bytes", cmd[0], transferred, hdr.dxfer_len);
}
return transferred;
}
std::optional<u32> CDImageDeviceLinux::DoSCSIRead(LBA lba, SCSIReadMode read_mode)
{
const u32 size = SCSIReadCommandOutputSize(read_mode);
return DoSCSIRead(lba, 1, read_mode, std::span<u8>(m_buffer.data(), size));
}
std::optional<u32> CDImageDeviceLinux::DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode,
std::span<u8> out_buffer)
{
u8 cmd[SCSI_CMD_LENGTH];
FillSCSIReadCommand(cmd, lba, read_mode);
FillSCSIReadCommand(cmd, lba, sector_count, read_mode);
const u32 size = SCSIReadCommandOutputSize(read_mode);
return DoSCSICommand(cmd, std::span<u8>(m_buffer.data(), size));
return DoSCSICommand(cmd, out_buffer);
}
bool CDImageDeviceLinux::DoSetSpeed(u32 speed_multiplier)
@ -1167,12 +1267,12 @@ public:
bool Open(const char* filename, Error* error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
bool IsPhysicalDevice() const override { return true; }
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
// Raw reads should subtract 00:02:00.
@ -1187,8 +1287,10 @@ private:
LBA m_current_lba = ~static_cast<LBA>(0);
SCSIReadMode m_read_mode = SCSIReadMode::None;
bool m_supports_batch_reads = true;
std::array<u8, RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE> m_buffer;
std::vector<u8> m_batch_buffer;
};
} // namespace
@ -1417,31 +1519,87 @@ bool CDImageDeviceMacOS::Open(const char* filename, Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageDeviceMacOS::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
u32 CDImageDeviceMacOS::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
if (index.file_sector_size == 0 || m_read_mode < SCSIReadMode::Full)
return CDImage::ReadSubChannelQ(subq, index, lba_in_index);
const LBA start_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
bool batch_failed = false;
if (m_supports_batch_reads && start_lba >= RAW_READ_OFFSET && sectors.size() > 1)
{
const u32 sector_count = static_cast<u32>(sectors.size());
const u32 sector_size =
RAW_SECTOR_SIZE + ((m_read_mode == SCSIReadMode::Full) ?
ALL_SUBCODE_SIZE :
((m_read_mode == SCSIReadMode::SubQOnly) ? SUBCHANNEL_BYTES_PER_FRAME : 0));
m_batch_buffer.resize(static_cast<size_t>(sector_size) * sector_count);
dk_cd_read_t desc = {};
desc.sectorArea =
kCDSectorAreaSync | kCDSectorAreaHeader | kCDSectorAreaSubHeader | kCDSectorAreaUser | kCDSectorAreaAuxiliary |
((m_read_mode == SCSIReadMode::Full) ? kCDSectorAreaSubChannel :
((m_read_mode == SCSIReadMode::SubQOnly) ? kCDSectorAreaSubChannelQ : 0));
desc.sectorType = kCDSectorTypeUnknown;
desc.offset = static_cast<u64>(start_lba - RAW_READ_OFFSET) * sector_size;
desc.buffer = m_batch_buffer.data();
desc.bufferLength = static_cast<u32>(m_batch_buffer.size());
const int ioctl_result = ioctl(m_fd, DKIOCCDREAD, &desc);
if (ioctl_result == 0 && desc.bufferLength == m_batch_buffer.size())
{
for (u32 i = 0; i < sector_count; i++)
{
const u8* const source = m_batch_buffer.data() + (static_cast<size_t>(i) * sector_size);
if (mode != SectorReadMode::SubQOnly)
std::memcpy(sectors[i].data.data(), source, RAW_SECTOR_SIZE);
if (mode != SectorReadMode::DataOnly && m_read_mode >= SCSIReadMode::Full)
CopySCSISubChannelQ(&sectors[i].subq, source, m_read_mode);
}
const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
return false;
// Keep the scalar cache and its tag synchronized in case the next request repeats the end of this batch.
std::memcpy(m_buffer.data(), m_batch_buffer.data() + (static_cast<size_t>(sector_count - 1) * sector_size),
sector_size);
m_current_lba = start_lba + sector_count - 1;
return sector_count;
}
if (m_read_mode == SCSIReadMode::SubQOnly)
{
// copy out subq
std::memcpy(subq->data.data(), m_buffer.data() + RAW_SECTOR_SIZE, SUBCHANNEL_BYTES_PER_FRAME);
return true;
const Position msf = Position::FromLBA(start_lba);
if (ioctl_result == 0)
{
WARNING_LOG("DKIOCCDREAD batch for LBA {} (MSF {}:{}:{}, count {}) returned {} of {} bytes, retrying "
"individually",
start_lba, msf.minute, msf.second, msf.frame, sector_count, desc.bufferLength, m_batch_buffer.size());
}
else
{
WARNING_LOG("DKIOCCDREAD batch for LBA {} (MSF {}:{}:{}, count {}) failed: {}, retrying individually", start_lba,
msf.minute, msf.second, msf.frame, sector_count, errno);
}
batch_failed = true;
}
else // if (m_scsi_read_mode == SCSIReadMode::Full)
// Retry individually on errors so that callers receive the exact readable prefix.
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
// need to deinterleave the subcode
u8 deinterleaved_subcode[ALL_SUBCODE_SIZE];
DeinterleaveSubcode(m_buffer.data() + RAW_SECTOR_SIZE, deinterleaved_subcode);
const LBA disc_lba = start_lba + sectors_read;
if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
break;
// P, Q, ...
std::memcpy(subq->data.data(), deinterleaved_subcode + SUBCHANNEL_BYTES_PER_FRAME, SUBCHANNEL_BYTES_PER_FRAME);
return true;
if (mode != SectorReadMode::SubQOnly)
std::memcpy(sector.data.data(), m_buffer.data(), RAW_SECTOR_SIZE);
if (mode != SectorReadMode::DataOnly && m_read_mode >= SCSIReadMode::Full)
CopySCSISubChannelQ(&sector.subq, m_buffer.data(), m_read_mode);
sectors_read++;
}
if (batch_failed && sectors_read == sectors.size())
{
// A completely successful scalar retry distinguishes an unsupported/mishandled multi-sector command from an
// unreadable sector. Avoid paying the device timeout on every subsequent refill.
WARNING_LOG("Disabling multi-sector reads after {} sectors at LBA {} succeeded individually", sectors_read,
start_lba);
m_supports_batch_reads = false;
}
return sectors_read;
}
bool CDImageDeviceMacOS::HasSubchannelData() const
@ -1450,19 +1608,6 @@ bool CDImageDeviceMacOS::HasSubchannelData() const
return m_read_mode >= SCSIReadMode::Full;
}
bool CDImageDeviceMacOS::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
{
if (index.file_sector_size == 0)
return false;
const LBA disc_lba = static_cast<LBA>(index.file_offset) + lba_in_index;
if (m_current_lba != disc_lba && !ReadSectorToBuffer(disc_lba))
return false;
std::memcpy(buffer, m_buffer.data(), RAW_SECTOR_SIZE);
return true;
}
bool CDImageDeviceMacOS::DoSetSpeed(u32 speed_multiplier)
{
const u16 speed = static_cast<u16>((FRAMES_PER_SECOND * RAW_SECTOR_SIZE * speed_multiplier) / 1024);

@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "cd_image_hasher.h"
#include "cd_image.h"
#include "translation.h"
#include "common/align.h"
#include "common/error.h"
#include "common/md5_digest.h"
#include "common/progress_callback.h"
@ -23,20 +24,16 @@ static bool ReadTrack(CDImage* image, u8 track, MD5Digest* digest, ProgressCallb
bool CDImageHasher::ReadIndex(CDImage* image, u8 track, u8 index, MD5Digest* digest,
ProgressCallback* progress_callback, Error* error)
{
static constexpr u32 READ_BATCH_SIZE = 32;
const CDImage::LBA index_start = image->GetTrackIndexPosition(track, index);
const u32 index_length = image->GetTrackIndexLength(track, index);
const u32 update_interval = std::max<u32>(index_length / 100u, 1u);
const u32 update_interval = Common::AlignUpPow2(std::max<u32>(index_length / 100u, 1u), READ_BATCH_SIZE);
progress_callback->SetProgressRange(index_length);
if (!image->Seek(index_start))
{
Error::SetStringFmt(error, "Failed to seek to sector {} for track {} index {}", index_start, track, index);
return false;
}
std::array<u8, CDImage::RAW_SECTOR_SIZE> sector;
for (u32 lba = 0; lba < index_length; lba++)
std::array<CDImage::Sector, READ_BATCH_SIZE> sectors;
for (u32 lba = 0; lba < index_length;)
{
if ((lba % update_interval) == 0)
progress_callback->SetProgressValue(lba);
@ -44,13 +41,18 @@ bool CDImageHasher::ReadIndex(CDImage* image, u8 track, u8 index, MD5Digest* dig
if (progress_callback->IsCancelled())
return false;
if (!image->ReadRawSector(sector.data(), nullptr))
const u32 count = std::min(index_length - lba, READ_BATCH_SIZE);
const u32 count_read =
image->ReadSectors(index_start + lba, std::span(sectors).first(count), CDImage::SectorReadMode::DataOnly);
if (count_read != count)
{
Error::SetStringFmt(error, "Failed to read sector {} from image", image->GetPositionOnDisc());
Error::SetStringFmt(error, "Failed to read sector {} from image", index_start + lba + count_read);
return false;
}
digest->Update(sector);
for (u32 i = 0; i < count; i++)
digest->Update(sectors[i].data);
lba += count;
}
progress_callback->SetProgressValue(index_length);

@ -27,7 +27,6 @@ public:
bool Open(const char* path, bool apply_patches, Error* Error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
bool IsPhysicalDevice() const override;
@ -38,7 +37,8 @@ public:
bool SwitchSubImage(u32 index, Error* error) override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
struct Entry
@ -162,14 +162,10 @@ std::string CDImageM3u::GetSubImageTitle(u32 index) const
return ret;
}
bool CDImageM3u::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImageM3u::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
return m_current_image->ReadSectorFromIndex(buffer, index, lba_in_index);
}
bool CDImageM3u::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
return m_current_image->ReadSubChannelQ(subq, index, lba_in_index);
return m_current_image->ReadSectorsFromIndex(sectors, index, lba_in_index, mode);
}
std::unique_ptr<CDImage> CDImage::OpenM3uImage(const char* path, bool apply_patches, Error* error)

@ -49,7 +49,8 @@ public:
s64 GetSizeOnDisk() const override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
std::FILE* m_mdf_file = nullptr;
@ -250,27 +251,35 @@ bool CDImageMDS::OpenAndParse(const char* path, Error* error)
return Seek(1, Position{0, 0, 0});
}
bool CDImageMDS::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImageMDS::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
const u64 file_position = index.file_offset + (static_cast<u64>(lba_in_index) * index.file_sector_size);
if (m_mdf_file_position != file_position)
(void)mode;
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
if (FileSystem::FSeek64(m_mdf_file, file_position, SEEK_SET) != 0)
return false;
const u64 file_position =
index.file_offset + (static_cast<u64>(lba_in_index + sectors_read) * index.file_sector_size);
if (m_mdf_file_position != file_position)
{
if (FileSystem::FSeek64(m_mdf_file, file_position, SEEK_SET) != 0)
break;
m_mdf_file_position = file_position;
}
m_mdf_file_position = file_position;
}
// we don't want the subchannel data
const u32 read_size = RAW_SECTOR_SIZE;
if (std::fread(buffer, read_size, 1, m_mdf_file) != 1)
{
FileSystem::FSeek64(m_mdf_file, m_mdf_file_position, SEEK_SET);
return false;
// Preserve the existing behavior of ignoring embedded MDS subchannel data.
if (std::fread(sector.data.data(), RAW_SECTOR_SIZE, 1, m_mdf_file) != 1)
{
FileSystem::FSeek64(m_mdf_file, m_mdf_file_position, SEEK_SET);
break;
}
m_mdf_file_position += RAW_SECTOR_SIZE;
sectors_read++;
}
m_mdf_file_position += read_size;
return true;
return sectors_read;
}
s64 CDImageMDS::GetSizeOnDisk() const

@ -28,8 +28,8 @@ public:
bool HasSubchannelData() const override;
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
bool IsPrecached() const override;
@ -60,8 +60,7 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress, Error*
const Index& index = image->GetIndex(i);
if (index.file_sector_size > 0)
{
const u32 memory_sector_size =
GetBytesPerSector(index.mode) + (m_has_subchannel_data ? SUBCHANNEL_BYTES_PER_FRAME : 0);
const u32 memory_sector_size = RAW_SECTOR_SIZE + (m_has_subchannel_data ? SUBCHANNEL_BYTES_PER_FRAME : 0);
total_size += static_cast<u64>(index.length) * static_cast<u64>(memory_sector_size);
}
}
@ -89,6 +88,8 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress, Error*
u32 sectors_read = 0;
size_t memory_offset = 0;
static constexpr u32 READ_BATCH_SIZE = 32;
std::array<Sector, READ_BATCH_SIZE> read_buffer;
m_indices.reserve(image->GetIndexCount());
for (u32 i = 0; i < image->GetIndexCount(); i++)
{
@ -102,31 +103,42 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress, Error*
progress->FormatStatusText(TRANSLATE_FS("CDImage", "Loading Track {0} ({1})..."), index.track_number,
GetTrackModeDisplayName(index.mode));
if (!image->Seek(index.start_lba_on_disc))
{
ERROR_LOG("Failed to seek to LBA {} in index {}", index.start_lba_on_disc, i);
return false;
}
index.file_index = 0;
index.file_offset = memory_offset;
index.file_sector_size = GetBytesPerSector(index.mode) + (m_has_subchannel_data ? SUBCHANNEL_BYTES_PER_FRAME : 0);
index.file_sector_size = RAW_SECTOR_SIZE + (m_has_subchannel_data ? SUBCHANNEL_BYTES_PER_FRAME : 0);
for (u32 lba = 0; lba < index.length; lba++)
// Memory images store normalized raw sectors, regardless of how the source image stores them.
if (index.mode != TrackMode::Audio)
index.mode = (index.mode == TrackMode::Mode1 || index.mode == TrackMode::Mode1Raw) ? TrackMode::Mode1Raw :
TrackMode::Mode2Raw;
for (u32 lba = 0; lba < index.length;)
{
u8* const sector_ptr = m_memory + memory_offset;
SubChannelQ* const subq_ptr =
m_has_subchannel_data ?
reinterpret_cast<SubChannelQ*>(sector_ptr + index.file_sector_size - SUBCHANNEL_BYTES_PER_FRAME) :
nullptr;
if (!image->ReadRawSector(sector_ptr, subq_ptr))
const u32 count = std::min(index.length - lba, READ_BATCH_SIZE);
const u32 count_read =
image->ReadSectors(index.start_lba_on_disc + lba, std::span(read_buffer).first(count),
m_has_subchannel_data ? SectorReadMode::DataAndSubQ : SectorReadMode::DataOnly);
if (count_read != count)
{
ERROR_LOG("Failed to read LBA {} in index {} (disc LBA {})", lba, i, index.start_lba_on_disc + lba);
ERROR_LOG("Failed to read LBA {} in index {} (disc LBA {})", lba + count_read, i,
index.start_lba_on_disc + lba + count_read);
return false;
}
memory_offset += index.file_sector_size;
progress->SetProgressValue(sectors_read++);
for (u32 j = 0; j < count; j++)
{
u8* const sector_ptr = m_memory + memory_offset;
std::memcpy(sector_ptr, read_buffer[j].data.data(), RAW_SECTOR_SIZE);
if (m_has_subchannel_data)
{
std::memcpy(sector_ptr + RAW_SECTOR_SIZE, read_buffer[j].subq.data.data(), SUBCHANNEL_BYTES_PER_FRAME);
}
memory_offset += index.file_sector_size;
}
lba += count;
sectors_read += count;
progress->SetProgressValue(sectors_read);
}
}
@ -145,38 +157,39 @@ bool CDImageMemory::HasSubchannelData() const
return m_has_subchannel_data;
}
bool CDImageMemory::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImageMemory::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
DebugAssert(index.file_index == 0);
const u64 memory_offset = (index.file_offset + (lba_in_index * static_cast<u64>(index.file_sector_size)));
const size_t sector_size = static_cast<size_t>(index.file_sector_size);
if ((memory_offset + sector_size) > m_memory_size)
return false;
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
const u64 memory_offset =
index.file_offset + ((lba_in_index + sectors_read) * static_cast<u64>(index.file_sector_size));
const size_t sector_size = static_cast<size_t>(index.file_sector_size);
if ((memory_offset + sector_size) > m_memory_size)
break;
// don't copy subq into the receiving buffer
std::memcpy(buffer, &m_memory[memory_offset],
index.file_sector_size - (m_has_subchannel_data ? SUBCHANNEL_BYTES_PER_FRAME : 0));
return true;
}
if (mode != SectorReadMode::SubQOnly)
{
// Don't copy subq into the receiving data buffer.
const u32 data_size = index.file_sector_size - (m_has_subchannel_data ? SUBCHANNEL_BYTES_PER_FRAME : 0);
std::memcpy(sector.data.data(), &m_memory[memory_offset], data_size);
}
bool CDImageMemory::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
// generate subq for non-file indices
if (!m_has_subchannel_data || index.file_sector_size == 0)
{
GenerateSubChannelQ(subq, index, lba_in_index);
return true;
}
// SubQ was generated by the caller for images which do not have replacement subchannel data.
if (mode != SectorReadMode::DataOnly && m_has_subchannel_data)
{
std::memcpy(sector.subq.data.data(),
&m_memory[memory_offset + index.file_sector_size - SUBCHANNEL_BYTES_PER_FRAME],
SUBCHANNEL_BYTES_PER_FRAME);
}
const u64 memory_offset = (index.file_offset + (lba_in_index * static_cast<u64>(index.file_sector_size)));
const size_t sector_size = static_cast<size_t>(index.file_sector_size);
if ((memory_offset + sector_size) > m_memory_size)
return false;
sectors_read++;
}
std::memcpy(subq->data.data(), &m_memory[memory_offset + index.file_sector_size - SUBCHANNEL_BYTES_PER_FRAME],
SUBCHANNEL_BYTES_PER_FRAME);
return true;
return sectors_read;
}
bool CDImageMemory::IsPrecached() const

@ -143,7 +143,8 @@ public:
std::string GetSubImageTitle(u32 index) const override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
struct BlockInfo
@ -814,33 +815,42 @@ bool CDImagePBP::DecompressBlock(const BlockInfo& block_info)
return true;
}
bool CDImagePBP::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImagePBP::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
const u32 offset_in_file = static_cast<u32>(index.file_offset) + (lba_in_index * index.file_sector_size);
const u32 offset_in_block = offset_in_file % DECOMPRESSED_BLOCK_SIZE;
const u32 requested_block = offset_in_file / DECOMPRESSED_BLOCK_SIZE;
if (requested_block >= m_blockinfo_table.size()) [[unlikely]]
(void)mode;
u32 sectors_read = 0;
for (Sector& sector : sectors)
{
ERROR_LOG("Invalid block {} requested", requested_block);
return false;
}
const u32 offset_in_file =
static_cast<u32>(index.file_offset) + ((lba_in_index + sectors_read) * index.file_sector_size);
const u32 offset_in_block = offset_in_file % DECOMPRESSED_BLOCK_SIZE;
const u32 requested_block = offset_in_file / DECOMPRESSED_BLOCK_SIZE;
const BlockInfo& bi = m_blockinfo_table[requested_block];
if (bi.size == 0) [[unlikely]]
{
ERROR_LOG("Requested block {} has size 0", requested_block);
return false;
}
if (requested_block >= m_blockinfo_table.size()) [[unlikely]]
{
ERROR_LOG("Invalid block {} requested", requested_block);
break;
}
if (m_current_block != requested_block && !DecompressBlock(bi)) [[unlikely]]
{
ERROR_LOG("Failed to decompress block {}", requested_block);
return false;
const BlockInfo& bi = m_blockinfo_table[requested_block];
if (bi.size == 0) [[unlikely]]
{
ERROR_LOG("Requested block {} has size 0", requested_block);
break;
}
if (m_current_block != requested_block && !DecompressBlock(bi)) [[unlikely]]
{
ERROR_LOG("Failed to decompress block {}", requested_block);
break;
}
std::memcpy(sector.data.data(), &m_decompressed_block[offset_in_block], RAW_SECTOR_SIZE);
sectors_read++;
}
std::memcpy(buffer, &m_decompressed_block[offset_in_block], RAW_SECTOR_SIZE);
return true;
return sectors_read;
}
#if defined(_DEBUG) || defined(_DEVEL)

@ -35,7 +35,6 @@ public:
bool Open(const char* path, std::unique_ptr<CDImage> parent_image, Error* error);
bool ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index) override;
bool HasSubchannelData() const override;
bool IsPhysicalDevice() const override;
s64 GetSizeOnDisk() const override;
@ -45,7 +44,8 @@ public:
PrecacheResult Precache(ProgressCallback* progress, Error* error) override;
protected:
bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) override;
u32 ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode) override;
private:
bool ReadV1Patch(std::FILE* fp, Error* error);
@ -240,10 +240,11 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp, Error* error)
u32 blockcheck_src_sector = 16 + m_replacement_offset;
u32 blockcheck_src_offset = 32;
std::vector<u8> src_sector(RAW_SECTOR_SIZE);
if (m_parent_image->Seek(blockcheck_src_sector) && m_parent_image->ReadRawSector(src_sector.data(), nullptr))
Sector src_sector;
if (m_parent_image->ReadSectors(blockcheck_src_sector, std::span<Sector>(&src_sector, 1),
SectorReadMode::DataOnly) == 1)
{
if (std::memcmp(&src_sector[blockcheck_src_offset], temp.data(), BLOCKCHECK_SIZE) != 0)
if (std::memcmp(&src_sector.data[blockcheck_src_offset], temp.data(), BLOCKCHECK_SIZE) != 0)
WARNING_LOG("Blockcheck failed. The patch may not apply correctly.");
}
else
@ -426,12 +427,14 @@ bool CDImagePPF::AddPatch(u64 offset, std::span<const u8> patch, std::span<const
{
const u32 replacement_buffer_start = static_cast<u32>(m_replacement_data.size());
m_replacement_data.resize(m_replacement_data.size() + RAW_SECTOR_SIZE);
if (!m_parent_image->Seek(sector_index) ||
!m_parent_image->ReadRawSector(&m_replacement_data[replacement_buffer_start], nullptr))
Sector source_sector;
if (m_parent_image->ReadSectors(sector_index, std::span<Sector>(&source_sector, 1), SectorReadMode::DataOnly) !=
1)
{
Error::SetStringFmt(error, "Failed to read sector {} from parent image", sector_index);
return false;
}
std::memcpy(&m_replacement_data[replacement_buffer_start], source_sector.data.data(), RAW_SECTOR_SIZE);
iter = m_replacement_map.emplace(sector_index, replacement_buffer_start).first;
}
@ -457,11 +460,6 @@ bool CDImagePPF::AddPatch(u64 offset, std::span<const u8> patch, std::span<const
return true;
}
bool CDImagePPF::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_in_index)
{
return m_parent_image->ReadSubChannelQ(subq, index, lba_in_index);
}
bool CDImagePPF::HasSubchannelData() const
{
return m_parent_image->HasSubchannelData();
@ -483,17 +481,38 @@ CDImage::PrecacheResult CDImagePPF::Precache(ProgressCallback* progress, Error*
return m_parent_image->Precache(progress, error);
}
bool CDImagePPF::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index)
u32 CDImagePPF::ReadSectorsFromIndex(std::span<Sector> sectors, const Index& index, LBA lba_in_index,
SectorReadMode mode)
{
DebugAssert(index.file_index == 0);
const u32 sector_number = index.start_lba_on_disc + lba_in_index;
const auto it = m_replacement_map.find(sector_number);
if (it == m_replacement_map.end())
return m_parent_image->ReadSectorFromIndex(buffer, index, lba_in_index);
if (mode == SectorReadMode::SubQOnly)
return m_parent_image->ReadSectorsFromIndex(sectors, index, lba_in_index, mode);
std::memcpy(buffer, &m_replacement_data[it->second], RAW_SECTOR_SIZE);
return true;
u32 i;
for (i = 0; i < static_cast<u32>(sectors.size()); i++)
{
const u32 sector_number = index.start_lba_on_disc + lba_in_index + i;
const auto it = m_replacement_map.find(sector_number);
if (it != m_replacement_map.end())
{
// add in subq
if (mode != SectorReadMode::DataOnly &&
m_parent_image->ReadSectorsFromIndex(sectors.subspan(i, 1), index, lba_in_index + i,
CDImage::SectorReadMode::SubQOnly) == 0)
{
return i;
}
std::memcpy(sectors[i].data.data(), &m_replacement_data[it->second], RAW_SECTOR_SIZE);
}
else if (m_parent_image->ReadSectorsFromIndex(sectors.subspan(i, 1), index, lba_in_index + i, mode) == 0)
{
return i;
}
}
return i;
}
s64 CDImagePPF::GetSizeOnDisk() const

@ -45,16 +45,11 @@ bool IsoReader::Open(CDImage* image, u32 track_number, Error* error)
bool IsoReader::ReadSector(std::span<u8, SECTOR_SIZE> buf, u32 lsn, Error* error)
{
if (!m_image->Seek(m_track_number, lsn))
{
Error::SetStringFmt(error, "Failed to seek to LSN #{}", lsn);
return false;
}
std::array<u8, CDImage::RAW_SECTOR_SIZE> raw_sector;
CDImage::Sector 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())
const CDImage::LBA lba = m_image->GetTrackStartPosition(m_track_number) + lsn;
if (m_image->ReadSectors(lba, std::span<CDImage::Sector>(&raw_sector, 1), CDImage::SectorReadMode::DataOnly) != 1 ||
(sector_data = ExtractSectorData(raw_sector.data, ReadMode::Data, error)).empty())
{
Error::SetStringFmt(error, "Failed to read LSN #{}: ", lsn);
return false;
@ -442,31 +437,39 @@ bool IsoReader::ReadFile(const ISODirectoryEntry& de, std::vector<u8>* data, Rea
return true;
}
if (!m_image->Seek(m_track_number, de.location_le))
{
Error::SetStringFmt(error, "Failed to seek to LSN #{}", de.location_le);
return false;
}
// 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<u8, CDImage::RAW_SECTOR_SIZE> raw_sector;
static constexpr u32 READ_BATCH_SIZE = 32;
std::array<CDImage::Sector, READ_BATCH_SIZE> raw_sectors;
const CDImage::LBA start_lba = m_image->GetTrackStartPosition(m_track_number) + de.location_le;
size_t data_offset = 0;
for (u32 i = 0; i < num_sectors; i++)
for (u32 i = 0; i < num_sectors;)
{
std::span<const u8> sector_data;
if (!m_image->ReadRawSector(raw_sector.data(), nullptr) ||
(sector_data = ExtractSectorData(raw_sector, read_mode, error)).empty())
const u32 count = std::min(num_sectors - i, READ_BATCH_SIZE);
const u32 count_read =
m_image->ReadSectors(start_lba + i, std::span(raw_sectors).first(count), CDImage::SectorReadMode::DataOnly);
if (count_read != count)
{
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i);
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i + count_read);
return false;
}
std::memcpy(data->data() + data_offset, sector_data.data(), sector_data.size());
data_offset += sector_data.size();
for (u32 j = 0; j < count; j++)
{
const std::span<const u8> sector_data = ExtractSectorData(raw_sectors[j].data, read_mode, error);
if (sector_data.empty())
{
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i + j);
return false;
}
std::memcpy(data->data() + data_offset, sector_data.data(), sector_data.size());
data_offset += sector_data.size();
}
i += count;
}
// only shrink for data read mode
@ -501,12 +504,6 @@ bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Re
if (de.length_le == 0)
return FileSystem::FTruncate64(fp, 0, error);
if (!m_image->Seek(m_track_number, de.location_le))
{
Error::SetStringFmt(error, "Failed to seek to LSN #{}", de.location_le);
return false;
}
if (progress)
{
progress->SetProgressRange(de.length_le);
@ -515,39 +512,53 @@ bool IsoReader::WriteFileToStream(const ISODirectoryEntry& de, std::FILE* fp, Re
const u32 num_sectors = de.GetSizeInSectors();
std::array<u8, CDImage::RAW_SECTOR_SIZE> raw_sector;
static constexpr u32 READ_BATCH_SIZE = 32;
std::array<CDImage::Sector, READ_BATCH_SIZE> raw_sectors;
const CDImage::LBA start_lba = m_image->GetTrackStartPosition(m_track_number) + de.location_le;
u32 file_pos = 0;
for (u32 i = 0; i < num_sectors; i++)
for (u32 i = 0; i < num_sectors;)
{
std::span<const u8> sector_data;
if (!m_image->ReadRawSector(raw_sector.data(), nullptr) ||
(sector_data = ExtractSectorData(raw_sector, read_mode, error)).empty())
const u32 count = std::min(num_sectors - i, READ_BATCH_SIZE);
const u32 count_read =
m_image->ReadSectors(start_lba + i, std::span(raw_sectors).first(count), CDImage::SectorReadMode::DataOnly);
if (count_read != count)
{
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i);
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i + count_read);
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)
for (u32 j = 0; j < count; j++)
{
Error::SetErrno(error, "fwrite() failed: ", errno);
return false;
}
const std::span<const u8> sector_data = ExtractSectorData(raw_sectors[j].data, read_mode, error);
if (sector_data.empty())
{
Error::AddPrefixFmt(error, "Failed to read LSN #{}", de.location_le + i + j);
return false;
}
file_pos += write_size;
if (progress)
{
progress->SetProgressValue(file_pos);
if (progress->IsCancelled())
// 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::SetStringView(error, "Operation was cancelled.");
Error::SetErrno(error, "fwrite() failed: ", errno);
return false;
}
file_pos += write_size;
if (progress)
{
progress->SetProgressValue(file_pos);
if (progress->IsCancelled())
{
Error::SetStringView(error, "Operation was cancelled.");
return false;
}
}
}
i += count;
}
if (std::fflush(fp) != 0)

Loading…
Cancel
Save