From 31547f5d358539e128d7d0a21e8c5e4c4bbeb58b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 12 Sep 2026 15:22:11 +1000 Subject: [PATCH] CDImage: Add aggregate LBA-addressed batch reads --- src/core/system.cpp | 9 +- src/util-tests/cd_image_tests.cpp | 67 +++++- src/util/cd_image.cpp | 122 ++++++---- src/util/cd_image.h | 44 +++- src/util/cd_image_ccd.cpp | 78 ++++--- src/util/cd_image_chd.cpp | 73 +++--- src/util/cd_image_cue.cpp | 26 ++- src/util/cd_image_device.cpp | 373 +++++++++++++++++++++--------- src/util/cd_image_hasher.cpp | 28 +-- src/util/cd_image_m3u.cpp | 14 +- src/util/cd_image_mds.cpp | 41 ++-- src/util/cd_image_memory.cpp | 105 +++++---- src/util/cd_image_pbp.cpp | 54 +++-- src/util/cd_image_ppf.cpp | 57 +++-- src/util/iso_reader.cpp | 109 +++++---- 15 files changed, 765 insertions(+), 435 deletions(-) diff --git a/src/core/system.cpp b/src/core/system.cpp index 2bdabb084..b9f6c3a3e 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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 sector; + CDImage::Sector sector; std::span 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(§or, 1), + CDImage::SectorReadMode::DataOnly) != 1 || + (sector_data = IsoReader::ExtractSectorData(sector.data, IsoReader::ReadMode::Data, nullptr)).empty()) { return DiscRegion::Other; } diff --git a/src/util-tests/cd_image_tests.cpp b/src/util-tests/cd_image_tests.cpp index 1c40e899a..0433434d0 100644 --- a/src/util-tests/cd_image_tests.cpp +++ b/src/util-tests/cd_image_tests.cpp @@ -169,10 +169,51 @@ TEST(CDImage, CueMode1_2048ReadsAsRaw) std::unique_ptr image = CDImage::Open(cue.GetPath().c_str(), false, &error); ASSERT_TRUE(image) << error.GetDescription(); - std::array sector; - ASSERT_TRUE(image->ReadRawSector(sector.data(), nullptr)); - ExpectSyncAndHeader(sector, 0x01, 2 * CDImage::FRAMES_PER_SECOND); - EXPECT_EQ(std::memcmp(§or[16], payload.data(), payload.size()), 0); + CDImage::Sector sector; + ASSERT_EQ(image->ReadSectors(2 * CDImage::FRAMES_PER_SECOND, std::span(§or, 1), + CDImage::SectorReadMode::DataAndSubQ), + 1u); + ExpectSyncAndHeader(sector.data, 0x01, 2 * CDImage::FRAMES_PER_SECOND); + EXPECT_EQ(std::memcmp(§or.data[16], payload.data(), payload.size()), 0); +} + +TEST(CDImage, BatchReadUsesExplicitLBA) +{ + TempFile bin("duckstation_cd_image_batch", "bin"); + std::array payload = {}; + for (u32 sector = 0; sector < 3; sector++) + { + std::fill_n(payload.data() + (sector * CDImage::DATA_SECTOR_SIZE), CDImage::DATA_SECTOR_SIZE, + static_cast(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 image = CDImage::Open(cue.GetPath().c_str(), false, &error); + ASSERT_TRUE(image) << error.GetDescription(); + + std::array 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 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 sector; - ASSERT_TRUE(image->ReadRawSector(sector.data(), nullptr)); - ExpectSyncAndHeader(sector, 0x01, 2 * CDImage::FRAMES_PER_SECOND); - EXPECT_EQ(std::memcmp(§or[16], payload.data(), payload.size()), 0); + CDImage::Sector sector; + ASSERT_EQ(image->ReadSectors(2 * CDImage::FRAMES_PER_SECOND, std::span(§or, 1), + CDImage::SectorReadMode::DataAndSubQ), + 1u); + ExpectSyncAndHeader(sector.data, 0x01, 2 * CDImage::FRAMES_PER_SECOND); + EXPECT_EQ(std::memcmp(§or.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 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(§or, 1), + CDImage::SectorReadMode::DataAndSubQ), + 1u); + EXPECT_EQ(std::memcmp(sector.data.data(), raw.data(), raw.size()), 0); } diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp index ba2dfc7c0..a1373030f 100644 --- a/src/util/cd_image.cpp +++ b/src/util/cd_image.cpp @@ -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(§or, 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 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( + std::min(sectors.size() - sectors_read, static_cast(index->length - lba_in_index))); + std::span 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(buffer), static_cast(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(buffer), static_cast(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 diff --git a/src/util/cd_image.h b/src/util/cd_image.h index 9adc4dedd..2a1090f45 100644 --- a/src/util/cd_image.h +++ b/src/util/cd_image.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -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; + + 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(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(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 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 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; diff --git a/src/util/cd_image_ccd.cpp b/src/util/cd_image_ccd.cpp index 3a5f4200a..b9e1c68b2 100644 --- a/src/util/cd_image_ccd.cpp +++ b/src/util/cd_image_ccd.cpp @@ -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 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 sectors, const Index& index, LBA lba_in_index, + SectorReadMode mode) { - const s64 file_position = static_cast(index.file_offset + (static_cast(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(index.file_offset + (static_cast(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(((index.file_offset / IMG_SECTOR_SIZE) * ALL_SUBCODE_SIZE) + - (static_cast(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(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(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(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 diff --git a/src/util/cd_image_chd.cpp b/src/util/cd_image_chd.cpp index a2c13dc68..14a3cfdcc 100644 --- a/src/util/cd_image_chd.cpp +++ b/src/util/cd_image_chd.cpp @@ -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 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 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) diff --git a/src/util/cd_image_cue.cpp b/src/util/cd_image_cue.cpp index d4fff2395..0c2f24c71 100644 --- a/src/util/cd_image_cue.cpp +++ b/src/util/cd_image_cue.cpp @@ -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 sectors, const Index& index, LBA lba_in_index, + SectorReadMode mode) override; private: std::optional DetectSingleFileTrackMode(TrackFileInterface* fi, const char* path, Error* error); @@ -910,20 +911,27 @@ std::optional CDImageCueSheet::DetectSingleFileTrackMode(Tra return TrackMode::Mode1; } -bool CDImageCueSheet::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) +u32 CDImageCueSheet::ReadSectorsFromIndex(std::span 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(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(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 diff --git a/src/util/cd_image_device.cpp b/src/util/cd_image_device.cpp index e6f5f8230..a93a8f320 100644 --- a/src/util/cd_image_device.cpp +++ b/src/util/cd_image_device.cpp @@ -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 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 sectors, const Index& index, LBA lba_in_index, + SectorReadMode mode) override; private: std::optional DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span out_buffer); std::optional DoSCSIRead(LBA lba, SCSIReadMode read_mode); + std::optional DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode, std::span out_buffer); bool DoRawRead(LBA lba); + bool DoRawRead(LBA lba, u32 sector_count, std::span 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 m_buffer; + std::vector 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 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(index.file_offset) + lba_in_index; + bool batch_failed = false; + if (m_supports_batch_reads && sectors.size() > 1) + { + const u32 sector_count = static_cast(sectors.size()); + const u32 sector_size = (m_scsi_read_mode == SCSIReadMode::None) ? + static_cast(CD_RAW_SECTOR_WITH_SUBCODE_SIZE) : + SCSIReadCommandOutputSize(m_scsi_read_mode); + m_batch_buffer.resize(static_cast(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(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(§ors[i].subq, source, m_scsi_read_mode); + } - const LBA offset = static_cast(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(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(§or.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(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 CDImageDeviceWin32::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span out_buffer) { struct SPTDBuffer @@ -500,12 +551,18 @@ std::optional CDImageDeviceWin32::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], st } std::optional CDImageDeviceWin32::DoSCSIRead(LBA lba, SCSIReadMode read_mode) +{ + const u32 size = SCSIReadCommandOutputSize(read_mode); + return DoSCSIRead(lba, 1, read_mode, std::span(m_buffer.data(), size)); +} + +std::optional CDImageDeviceWin32::DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode, + std::span 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(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 out_buffer) +{ + const DWORD expected_size = static_cast((RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE) * sector_count); + DebugAssert(out_buffer.size() >= expected_size); RAW_READ_INFO rri; rri.DiskOffset.QuadPart = static_cast(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(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 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 DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span out_buffer); std::optional DoSCSIRead(LBA lba, SCSIReadMode read_mode); + std::optional DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode, std::span out_buffer); bool DoRawRead(LBA lba); bool DoSetSpeed(u32 speed_multiplier); @@ -728,8 +795,10 @@ private: LBA m_current_lba = ~static_cast(0); SCSIReadMode m_scsi_read_mode = SCSIReadMode::None; + bool m_supports_batch_reads = true; std::array m_buffer; + std::vector 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 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(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(sectors.size()); + const u32 sector_size = SCSIReadCommandOutputSize(m_scsi_read_mode); + m_batch_buffer.resize(static_cast(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(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(§ors[i].subq, source, m_scsi_read_mode); + } - const LBA disc_lba = static_cast(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(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(§or.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(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 CDImageDeviceLinux::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], std::span out_buffer) { sg_io_hdr_t hdr; @@ -978,16 +1065,29 @@ std::optional 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(hdr.resid), transferred); + transferred -= residual; + WARNING_LOG("SCSI command {:02X} transferred {} of {} bytes", cmd[0], transferred, hdr.dxfer_len); + } + return transferred; } std::optional CDImageDeviceLinux::DoSCSIRead(LBA lba, SCSIReadMode read_mode) +{ + const u32 size = SCSIReadCommandOutputSize(read_mode); + return DoSCSIRead(lba, 1, read_mode, std::span(m_buffer.data(), size)); +} + +std::optional CDImageDeviceLinux::DoSCSIRead(LBA lba, u32 sector_count, SCSIReadMode read_mode, + std::span 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(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 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(0); SCSIReadMode m_read_mode = SCSIReadMode::None; + bool m_supports_batch_reads = true; std::array m_buffer; + std::vector 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 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(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(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(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(start_lba - RAW_READ_OFFSET) * sector_size; + desc.buffer = m_batch_buffer.data(); + desc.bufferLength = static_cast(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(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(§ors[i].subq, source, m_read_mode); + } - const LBA disc_lba = static_cast(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(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(§or.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(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((FRAMES_PER_SECOND * RAW_SECTOR_SIZE * speed_multiplier) / 1024); diff --git a/src/util/cd_image_hasher.cpp b/src/util/cd_image_hasher.cpp index fe6800a18..84e06b09e 100644 --- a/src/util/cd_image_hasher.cpp +++ b/src/util/cd_image_hasher.cpp @@ -1,10 +1,11 @@ -// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin // 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(index_length / 100u, 1u); + const u32 update_interval = Common::AlignUpPow2(std::max(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 sector; - for (u32 lba = 0; lba < index_length; lba++) + std::array 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); diff --git a/src/util/cd_image_m3u.cpp b/src/util/cd_image_m3u.cpp index bfe8c3de1..7cdee181a 100644 --- a/src/util/cd_image_m3u.cpp +++ b/src/util/cd_image_m3u.cpp @@ -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 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 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::OpenM3uImage(const char* path, bool apply_patches, Error* error) diff --git a/src/util/cd_image_mds.cpp b/src/util/cd_image_mds.cpp index ed56d7dc6..4bd81ab52 100644 --- a/src/util/cd_image_mds.cpp +++ b/src/util/cd_image_mds.cpp @@ -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 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 sectors, const Index& index, LBA lba_in_index, + SectorReadMode mode) { - const u64 file_position = index.file_offset + (static_cast(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(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 diff --git a/src/util/cd_image_memory.cpp b/src/util/cd_image_memory.cpp index 04644e850..a6637ee0d 100644 --- a/src/util/cd_image_memory.cpp +++ b/src/util/cd_image_memory.cpp @@ -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 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(index.length) * static_cast(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 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(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 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(index.file_sector_size))); - const size_t sector_size = static_cast(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(index.file_sector_size)); + const size_t sector_size = static_cast(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(index.file_sector_size))); - const size_t sector_size = static_cast(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 diff --git a/src/util/cd_image_pbp.cpp b/src/util/cd_image_pbp.cpp index c89073d45..f9611ed44 100644 --- a/src/util/cd_image_pbp.cpp +++ b/src/util/cd_image_pbp.cpp @@ -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 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 sectors, const Index& index, LBA lba_in_index, + SectorReadMode mode) { - const u32 offset_in_file = static_cast(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(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) diff --git a/src/util/cd_image_ppf.cpp b/src/util/cd_image_ppf.cpp index e4feee5af..70ea3e12b 100644 --- a/src/util/cd_image_ppf.cpp +++ b/src/util/cd_image_ppf.cpp @@ -35,7 +35,6 @@ public: bool Open(const char* path, std::unique_ptr 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 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 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(&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 patch, std::span(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(&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 patch, std::spanReadSubChannelQ(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 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(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 diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp index 8edd484a2..3535c88e8 100644 --- a/src/util/iso_reader.cpp +++ b/src/util/iso_reader.cpp @@ -45,16 +45,11 @@ bool IsoReader::Open(CDImage* image, u32 track_number, Error* error) bool IsoReader::ReadSector(std::span 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 raw_sector; + CDImage::Sector raw_sector; std::span 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(&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* 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 raw_sector; + static constexpr u32 READ_BATCH_SIZE = 32; + std::array 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 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 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 raw_sector; + static constexpr u32 READ_BATCH_SIZE = 32; + std::array 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 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(de.length_le - file_pos, static_cast(sector_data.size())) : - static_cast(sector_data.size()); - if (std::fwrite(sector_data.data(), write_size, 1, fp) != 1) + for (u32 j = 0; j < count; j++) { - Error::SetErrno(error, "fwrite() failed: ", errno); - return false; - } + const std::span 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(de.length_le - file_pos, static_cast(sector_data.size())) : + static_cast(sector_data.size()); + if (std::fwrite(sector_data.data(), write_size, 1, fp) != 1) { - Error::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)