From e739c114a47edc43d140ec683f62877f1d7365b2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 5 Nov 2023 20:36:28 +1000 Subject: [PATCH] CDImageCHD: Support reading subchannel from CHD --- src/util/cd_image.cpp | 29 +++++++++++++++++ src/util/cd_image.h | 17 ++++++++-- src/util/cd_image_bin.cpp | 6 ++-- src/util/cd_image_chd.cpp | 60 +++++++++++++++++++++++++++--------- src/util/cd_image_cue.cpp | 7 +++-- src/util/cd_image_device.cpp | 55 ++++++++------------------------- src/util/cd_image_ecm.cpp | 9 ++++-- src/util/cd_image_mds.cpp | 4 ++- src/util/cd_image_pbp.cpp | 5 ++- 9 files changed, 123 insertions(+), 69 deletions(-) diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp index 6d0feeec8..12cc1f50e 100644 --- a/src/util/cd_image.cpp +++ b/src/util/cd_image.cpp @@ -21,6 +21,35 @@ u32 CDImage::GetBytesPerSector(TrackMode mode) return sizes[static_cast(mode)]; } +// Adapted from +// https://github.com/saramibreak/DiscImageCreator/blob/5a8fe21730872d67991211f1319c87f0780f2d0f/DiscImageCreator/convert.cpp +void CDImage::DeinterleaveSubcode(const u8* subcode_in, u8* subcode_out) +{ + std::memset(subcode_out, 0, ALL_SUBCODE_SIZE); + + int row = 0; + for (int bitNum = 0; bitNum < 8; bitNum++) + { + for (int nColumn = 0; nColumn < ALL_SUBCODE_SIZE; row++) + { + u32 mask = 0x80; + for (int nShift = 0; nShift < 8; nShift++, nColumn++) + { + const int n = nShift - bitNum; + if (n > 0) + { + subcode_out[row] |= static_cast((subcode_in[nColumn] >> n) & mask); + } + else + { + subcode_out[row] |= static_cast((subcode_in[nColumn] << std::abs(n)) & mask); + } + mask >>= 1; + } + } + } +} + std::unique_ptr CDImage::Open(const char* filename, bool allow_patches, Error* error) { const char* extension; diff --git a/src/util/cd_image.h b/src/util/cd_image.h index 1ad169b7f..824bdba6b 100644 --- a/src/util/cd_image.h +++ b/src/util/cd_image.h @@ -32,7 +32,8 @@ public: SECONDS_PER_MINUTE = 60, FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE, SUBCHANNEL_BYTES_PER_FRAME = 12, - LEAD_OUT_SECTOR_COUNT = 6750 + LEAD_OUT_SECTOR_COUNT = 6750, + ALL_SUBCODE_SIZE = 96, }; enum : u8 @@ -40,14 +41,14 @@ public: LEAD_OUT_TRACK_NUMBER = 0xAA }; - enum class ReadMode : u32 + enum class ReadMode : u8 { DataOnly, // 2048 bytes per sector. RawSector, // 2352 bytes per sector. RawNoSync, // 2340 bytes per sector. }; - enum class TrackMode : u32 + enum class TrackMode : u8 { Audio, // 2352 bytes per sector Mode1, // 2048 bytes per sector @@ -59,6 +60,13 @@ public: Mode2Raw // 2352 bytes per sector }; + enum class SubchannelMode : u8 + { + None, // no subcode data stored + RawInterleaved, // raw interleaved 96 bytes per sector + Raw, // raw uninterleaved 96 bytes per sector + }; + enum class PrecacheResult : u8 { Unsupported, @@ -201,6 +209,7 @@ public: u32 first_index; u32 length; TrackMode mode; + SubchannelMode submode; SubChannelQ::Control control; }; @@ -215,12 +224,14 @@ public: LBA start_lba_in_track; u32 length; TrackMode mode; + SubchannelMode submode; SubChannelQ::Control control; bool is_pregap; }; // Helper functions. static u32 GetBytesPerSector(TrackMode mode); + static void DeinterleaveSubcode(const u8* subcode_in, u8* subcode_out); /// Returns a list of physical CD-ROM devices, .first being the device path, .second being the device name. static std::vector> GetDeviceList(); diff --git a/src/util/cd_image_bin.cpp b/src/util/cd_image_bin.cpp index 885f010d3..1b181a635 100644 --- a/src/util/cd_image_bin.cpp +++ b/src/util/cd_image_bin.cpp @@ -73,6 +73,7 @@ bool CDImageBin::Open(const char* filename, Error* error) pregap_index.track_number = 1; pregap_index.index_number = 0; pregap_index.mode = mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; m_indices.push_back(pregap_index); @@ -88,12 +89,13 @@ bool CDImageBin::Open(const char* filename, Error* error) data_index.start_lba_in_track = 0; data_index.length = m_lba_count; data_index.mode = mode; + data_index.submode = CDImage::SubchannelMode::None; data_index.control.bits = control.bits; m_indices.push_back(data_index); // Assume a single track. - m_tracks.push_back( - Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count, mode, control}); + m_tracks.push_back(Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count, mode, + SubchannelMode::None, control}); AddLeadOutIndex(); diff --git a/src/util/cd_image_chd.cpp b/src/util/cd_image_chd.cpp index 8879f635d..e08194ed6 100644 --- a/src/util/cd_image_chd.cpp +++ b/src/util/cd_image_chd.cpp @@ -16,6 +16,7 @@ #include "common/string_util.h" #include "fmt/format.h" +#include "libchdr/cdrom.h" #include "libchdr/chd.h" #include @@ -76,7 +77,7 @@ private: static constexpr u32 MAX_PARENTS = 32; // Surely someone wouldn't be insane enough to go beyond this... chd_file* OpenCHD(std::string_view filename, FileSystem::ManagedCFilePtr fp, Error* error, u32 recursion_level); - bool ReadHunk(u32 hunk_index); + bool UpdateHunkBuffer(const Index& index, LBA lba_in_index, u32& hunk_offset); static void CopyAndSwap(void* dst_ptr, const u8* src_ptr); @@ -307,6 +308,13 @@ bool CDImageCHD::Open(const char* filename, Error* error) } } + u32 csubtype, csubsize; + if (!cdrom_parse_subtype_string(subtype_str, &csubtype, &csubsize)) + { + csubtype = CD_SUB_NONE; + csubsize = 0; + } + if (track_num != (num_tracks + 1)) { Log_ErrorFmt("Incorrect track number at index {}, expected {} got {}", num_tracks, (num_tracks + 1), track_num); @@ -342,6 +350,7 @@ bool CDImageCHD::Open(const char* filename, Error* error) pregap_index.track_number = track_num; pregap_index.index_number = 0; pregap_index.mode = mode.value(); + pregap_index.submode = static_cast(csubtype); pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; @@ -367,7 +376,8 @@ bool CDImageCHD::Open(const char* filename, Error* error) // add the track itself m_tracks.push_back(Track{static_cast(track_num), disc_lba, static_cast(m_indices.size()), - static_cast(frames + pregap_frames), mode.value(), control}); + static_cast(frames + pregap_frames), mode.value(), + static_cast(csubtype), control}); // how many indices in this track? Index index = {}; @@ -379,6 +389,7 @@ bool CDImageCHD::Open(const char* filename, Error* error) index.file_sector_size = CHD_CD_SECTOR_DATA_SIZE; index.file_offset = file_lba; index.mode = mode.value(); + index.submode = static_cast(csubtype); index.control.bits = control.bits; index.is_pregap = false; index.length = static_cast(frames); @@ -412,14 +423,31 @@ bool CDImageCHD::ReadSubChannelQ(SubChannelQ* subq, const Index& index, LBA lba_ if (m_sbi.GetReplacementSubChannelQ(index.start_lba_on_disc + lba_in_index, subq)) return true; - // TODO: Read subchannel data from CHD + if (index.submode == CDImage::SubchannelMode::None) + return CDImage::ReadSubChannelQ(subq, index, lba_in_index); - 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::HasNonStandardSubchannel() const { - return (m_sbi.GetReplacementSectorCount() > 0); + // Just look at the first track for in-CHD subq. + return (m_sbi.GetReplacementSectorCount() > 0 || m_tracks.front().submode != CDImage::SubchannelMode::None); } CDImage::PrecacheResult CDImageCHD::Precache(ProgressCallback* progress) @@ -458,11 +486,11 @@ ALWAYS_INLINE_RELEASE void CDImageCHD::CopyAndSwap(void* dst_ptr, const u8* src_ #if defined(CPU_ARCH_SSE) // Requires SSSE3. - //const __m128i mask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); + // const __m128i mask = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1); for (u32 i = 0; i < num_values; i++) { __m128i value = _mm_load_si128(reinterpret_cast(src_ptr)); - //value = _mm_shuffle_epi8(value, mask); + // value = _mm_shuffle_epi8(value, mask); value = _mm_or_si128(_mm_slli_epi16(value, 8), _mm_srli_epi16(value, 8)); _mm_storeu_si128(reinterpret_cast<__m128i*>(dst_ptr_byte), value); src_ptr += sizeof(value); @@ -505,12 +533,8 @@ ALWAYS_INLINE_RELEASE void CDImageCHD::CopyAndSwap(void* dst_ptr, const u8* src_ bool CDImageCHD::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) { - const u32 disc_frame = static_cast(index.file_offset) + lba_in_index; - const u32 hunk_index = static_cast(disc_frame / m_sectors_per_hunk); - const u32 hunk_offset = static_cast((disc_frame % m_sectors_per_hunk) * CHD_CD_SECTOR_DATA_SIZE); - DebugAssert((m_hunk_size - hunk_offset) >= CHD_CD_SECTOR_DATA_SIZE); - - if (m_current_hunk_index != hunk_index && !ReadHunk(hunk_index)) + u32 hunk_offset; + if (!UpdateHunkBuffer(index, lba_in_index, hunk_offset)) return false; // Audio data is in big-endian, so we have to swap it for little endian hosts... @@ -522,8 +546,16 @@ bool CDImageCHD::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i return true; } -bool CDImageCHD::ReadHunk(u32 hunk_index) +ALWAYS_INLINE_RELEASE bool CDImageCHD::UpdateHunkBuffer(const Index& index, LBA lba_in_index, u32& hunk_offset) { + const u32 disc_frame = static_cast(index.file_offset) + lba_in_index; + const u32 hunk_index = static_cast(disc_frame / m_sectors_per_hunk); + hunk_offset = static_cast((disc_frame % m_sectors_per_hunk) * CHD_CD_SECTOR_DATA_SIZE); + DebugAssert((m_hunk_size - hunk_offset) >= CHD_CD_SECTOR_DATA_SIZE); + + if (m_current_hunk_index == hunk_index) + return true; + const chd_error err = chd_read(m_chd, hunk_index, m_hunk_buffer.data()); if (err != CHDERR_NONE) { diff --git a/src/util/cd_image_cue.cpp b/src/util/cd_image_cue.cpp index a93d8eabc..0a70d9e93 100644 --- a/src/util/cd_image_cue.cpp +++ b/src/util/cd_image_cue.cpp @@ -178,6 +178,7 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error) pregap_index.track_number = track_num; pregap_index.index_number = 0; pregap_index.mode = mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; pregap_index.file_index = track_file_index; @@ -217,6 +218,7 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error) pregap_index.track_number = track_num; pregap_index.index_number = 0; pregap_index.mode = mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; m_indices.push_back(pregap_index); @@ -226,8 +228,8 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error) } // add the track itself - m_tracks.push_back( - Track{track_num, disc_lba, static_cast(m_indices.size()), track_length + pregap_frames, mode, control}); + m_tracks.push_back(Track{track_num, disc_lba, static_cast(m_indices.size()), track_length + pregap_frames, + mode, SubchannelMode::None, control}); // how many indices in this track? Index last_index; @@ -239,6 +241,7 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error) last_index.file_sector_size = track_sector_size; last_index.file_offset = static_cast(track_start) * track_sector_size; last_index.mode = mode; + last_index.submode = CDImage::SubchannelMode::None; last_index.control.bits = control.bits; last_index.is_pregap = false; diff --git a/src/util/cd_image_device.cpp b/src/util/cd_image_device.cpp index df83e4c3d..3948c80f4 100644 --- a/src/util/cd_image_device.cpp +++ b/src/util/cd_image_device.cpp @@ -14,8 +14,15 @@ Log_SetChannel(CDImageDevice); #if defined(_WIN32) +// The include order here is critical. +// clang-format off +#include "common/windows_headers.h" +#include +#include +#include +// clang-format on + static constexpr u32 MAX_TRACK_NUMBER = 99; -static constexpr int ALL_SUBCODE_SIZE = 96; static u32 BEToU32(const u8* val) { @@ -29,47 +36,6 @@ static void U16ToBE(u8* beval, u16 leval) beval[1] = static_cast(leval); } -// Adapted from -// https://github.com/saramibreak/DiscImageCreator/blob/5a8fe21730872d67991211f1319c87f0780f2d0f/DiscImageCreator/convert.cpp -static void DeinterleaveSubcode(const u8* subcode_in, u8* subcode_out) -{ - std::memset(subcode_out, 0, ALL_SUBCODE_SIZE); - - int row = 0; - for (int bitNum = 0; bitNum < 8; bitNum++) - { - for (int nColumn = 0; nColumn < ALL_SUBCODE_SIZE; row++) - { - u32 mask = 0x80; - for (int nShift = 0; nShift < 8; nShift++, nColumn++) - { - const int n = nShift - bitNum; - if (n > 0) - { - subcode_out[row] |= static_cast((subcode_in[nColumn] >> n) & mask); - } - else - { - subcode_out[row] |= static_cast((subcode_in[nColumn] << std::abs(n)) & mask); - } - mask >>= 1; - } - } - } -} - -#endif - -#if defined(_WIN32) - -// The include order here is critical. -// clang-format off -#include "common/windows_headers.h" -#include -#include -#include -// clang-format on - class CDImageDeviceWin32 : public CDImage { public: @@ -217,6 +183,7 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error) pregap_index.track_number = track_num; pregap_index.index_number = 0; pregap_index.mode = track_mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; m_indices.push_back(pregap_index); @@ -227,7 +194,8 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error) if (track_num <= MAX_TRACK_NUMBER) { // add the track itself - m_tracks.push_back(Track{track_num, disc_lba, static_cast(m_indices.size()), 0, track_mode, control}); + m_tracks.push_back( + Track{track_num, disc_lba, static_cast(m_indices.size()), 0, track_mode, SubchannelMode::None, control}); Index index1; index1.start_lba_on_disc = disc_lba; @@ -239,6 +207,7 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error) index1.file_sector_size = 2048; index1.file_offset = static_cast(track_lba) * index1.file_sector_size; index1.mode = track_mode; + index1.submode = CDImage::SubchannelMode::None; index1.control.bits = control.bits; index1.is_pregap = false; m_indices.push_back(index1); diff --git a/src/util/cd_image_ecm.cpp b/src/util/cd_image_ecm.cpp index 0cddd5343..f828d8586 100644 --- a/src/util/cd_image_ecm.cpp +++ b/src/util/cd_image_ecm.cpp @@ -341,7 +341,8 @@ bool CDImageEcm::Open(const char* filename, Error* error) if (std::fseek(m_fp, file_offset, SEEK_SET) != 0) { Log_ErrorPrintf("Failed to seek to offset %u after %zu chunks", file_offset, m_data_map.size()); - Error::SetString(error, fmt::format("Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size())); + Error::SetString(error, + fmt::format("Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size())); return false; } } @@ -373,6 +374,7 @@ bool CDImageEcm::Open(const char* filename, Error* error) pregap_index.track_number = 1; pregap_index.index_number = 0; pregap_index.mode = mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; m_indices.push_back(pregap_index); @@ -388,12 +390,13 @@ bool CDImageEcm::Open(const char* filename, Error* error) data_index.start_lba_in_track = 0; data_index.length = m_lba_count; data_index.mode = mode; + data_index.submode = CDImage::SubchannelMode::None; data_index.control.bits = control.bits; m_indices.push_back(data_index); // Assume a single track. - m_tracks.push_back( - Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count, mode, control}); + m_tracks.push_back(Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count, mode, + SubchannelMode::None, control}); AddLeadOutIndex(); diff --git a/src/util/cd_image_mds.cpp b/src/util/cd_image_mds.cpp index 3cff1b9b1..1622f36b8 100644 --- a/src/util/cd_image_mds.cpp +++ b/src/util/cd_image_mds.cpp @@ -187,6 +187,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error) pregap_index.track_number = track_number; pregap_index.index_number = 0; pregap_index.mode = mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = control.bits; pregap_index.is_pregap = true; @@ -204,7 +205,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error) // add the track itself m_tracks.push_back(Track{static_cast(track_number), track_start_lba, static_cast(m_indices.size()), - static_cast(track_length), mode, control}); + static_cast(track_length), mode, SubchannelMode::None, control}); // how many indices in this track? Index last_index; @@ -216,6 +217,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error) last_index.file_sector_size = track_sector_size; last_index.file_offset = track_file_offset; last_index.mode = mode; + last_index.submode = CDImage::SubchannelMode::None; last_index.control.bits = control.bits; last_index.is_pregap = false; last_index.length = track_length; diff --git a/src/util/cd_image_pbp.cpp b/src/util/cd_image_pbp.cpp index 11d287a99..3e5b9e474 100644 --- a/src/util/cd_image_pbp.cpp +++ b/src/util/cd_image_pbp.cpp @@ -605,6 +605,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error) pregap_index.start_lba_in_track = static_cast(-static_cast(pregap_frames)); pregap_index.length = pregap_frames; pregap_index.mode = track_mode; + pregap_index.submode = CDImage::SubchannelMode::None; pregap_index.control.bits = track_control.bits; pregap_index.is_pregap = true; @@ -619,6 +620,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error) userdata_index.index_number = 1; userdata_index.start_lba_in_track = 0; userdata_index.mode = track_mode; + userdata_index.submode = CDImage::SubchannelMode::None; userdata_index.control.bits = track_control.bits; userdata_index.is_pregap = false; @@ -651,7 +653,8 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error) m_indices.push_back(userdata_index); m_tracks.push_back(Track{curr_track, userdata_start, 2 * curr_track - 1, - pregap_index.length + userdata_index.length, track_mode, track_control}); + pregap_index.length + userdata_index.length, track_mode, SubchannelMode::None, + track_control}); } AddLeadOutIndex();