#pragma once #include "bitfield.h" #include "progress_callback.h" #include "types.h" #include #include #include #include #include namespace Common { class Error; } class CDImage { public: CDImage(); virtual ~CDImage(); using LBA = u32; enum : u32 { RAW_SECTOR_SIZE = 2352, DATA_SECTOR_SIZE = 2048, SECTOR_SYNC_SIZE = 12, SECTOR_HEADER_SIZE = 4, FRAMES_PER_SECOND = 75, // "sectors", or "timecode frames" (not "channel frames") SECONDS_PER_MINUTE = 60, FRAMES_PER_MINUTE = FRAMES_PER_SECOND * SECONDS_PER_MINUTE, SUBCHANNEL_BYTES_PER_FRAME = 12, LEAD_OUT_SECTOR_COUNT = 6750 }; enum : u8 { LEAD_OUT_TRACK_NUMBER = 0xAA }; enum class ReadMode : u32 { DataOnly, // 2048 bytes per sector. RawSector, // 2352 bytes per sector. RawNoSync, // 2340 bytes per sector. }; enum class TrackMode : u32 { Audio, // 2352 bytes per sector Mode1, // 2048 bytes per sector Mode1Raw, // 2352 bytes per sector Mode2, // 2336 bytes per sector Mode2Form1, // 2048 bytes per sector Mode2Form2, // 2324 bytes per sector Mode2FormMix, // 2332 bytes per sector Mode2Raw // 2352 bytes per sector }; struct SectorHeader { u8 minute; u8 second; u8 frame; u8 sector_mode; }; struct Position { u8 minute; u8 second; u8 frame; static constexpr Position FromBCD(u8 minute, u8 second, u8 frame) { return Position{PackedBCDToBinary(minute), PackedBCDToBinary(second), PackedBCDToBinary(frame)}; } static constexpr Position FromLBA(LBA lba) { const u8 frame = Truncate8(lba % FRAMES_PER_SECOND); lba /= FRAMES_PER_SECOND; const u8 second = Truncate8(lba % SECONDS_PER_MINUTE); lba /= SECONDS_PER_MINUTE; const u8 minute = Truncate8(lba); return Position{minute, second, frame}; } LBA ToLBA() const { return ZeroExtend32(minute) * FRAMES_PER_MINUTE + ZeroExtend32(second) * FRAMES_PER_SECOND + ZeroExtend32(frame); } constexpr std::tuple ToBCD() const { return std::make_tuple(BinaryToBCD(minute), BinaryToBCD(second), BinaryToBCD(frame)); } Position operator+(const Position& rhs) { return FromLBA(ToLBA() + rhs.ToLBA()); } Position& operator+=(const Position& pos) { *this = *this + pos; return *this; } #define RELATIONAL_OPERATOR(op) \ bool operator op(const Position& rhs) const \ { \ return std::tie(minute, second, frame) op std::tie(rhs.minute, rhs.second, rhs.frame); \ } RELATIONAL_OPERATOR(==); RELATIONAL_OPERATOR(!=); RELATIONAL_OPERATOR(<); RELATIONAL_OPERATOR(<=); RELATIONAL_OPERATOR(>); RELATIONAL_OPERATOR(>=); #undef RELATIONAL_OPERATOR }; union SubChannelQ { using Data = std::array; union Control { u8 bits; BitField adr; BitField audio_preemphasis; BitField digital_copy_permitted; BitField data; BitField four_channel_audio; }; struct { Control control; u8 track_number_bcd; u8 index_number_bcd; u8 relative_minute_bcd; u8 relative_second_bcd; u8 relative_frame_bcd; u8 reserved; u8 absolute_minute_bcd; u8 absolute_second_bcd; u8 absolute_frame_bcd; u16 crc; }; Data data; static u16 ComputeCRC(const Data& data); bool IsCRCValid() const; SubChannelQ& operator=(const SubChannelQ& q) { data = q.data; return *this; } }; static_assert(sizeof(SubChannelQ) == SUBCHANNEL_BYTES_PER_FRAME, "SubChannelQ is correct size"); struct Track { u32 track_number; LBA start_lba; u32 first_index; u32 length; TrackMode mode; SubChannelQ::Control control; }; struct Index { u64 file_offset; u32 file_index; u32 file_sector_size; LBA start_lba_on_disc; u32 track_number; u32 index_number; LBA start_lba_in_track; u32 length; TrackMode mode; SubChannelQ::Control control; bool is_pregap; }; // Helper functions. static u32 GetBytesPerSector(TrackMode mode); // Opening disc image. static std::unique_ptr Open(const char* filename, Common::Error* error); static std::unique_ptr OpenBinImage(const char* filename, Common::Error* error); static std::unique_ptr OpenCueSheetImage(const char* filename, Common::Error* error); static std::unique_ptr OpenCHDImage(const char* filename, Common::Error* error); static std::unique_ptr OpenEcmImage(const char* filename, Common::Error* error); static std::unique_ptr OpenMdsImage(const char* filename, Common::Error* error); static std::unique_ptr OpenPBPImage(const char* filename, Common::Error* error); static std::unique_ptr OpenM3uImage(const char* filename, Common::Error* error); static std::unique_ptr CreateMemoryImage(CDImage* image, ProgressCallback* progress = ProgressCallback::NullProgressCallback); // Accessors. const std::string& GetFileName() const { return m_filename; } LBA GetPositionOnDisc() const { return m_position_on_disc; } Position GetMSFPositionOnDisc() const { return Position::FromLBA(m_position_on_disc); } LBA GetPositionInTrack() const { return m_position_in_track; } Position GetMSFPositionInTrack() const { return Position::FromLBA(m_position_in_track); } LBA GetLBACount() const { return m_lba_count; } 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; 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()); } const Track& GetTrack(u32 track) const; const Index& GetIndex(u32 i) const; // Seek to data LBA. bool Seek(LBA lba); // Seek to disc position (MSF). bool Seek(const Position& pos); // Seek to track and position. bool Seek(u32 track_number, const Position& pos_in_track); // Seek to track and LBA. bool Seek(u32 track_number, LBA lba); // Read from the current LBA. Returns the number of sectors read. u32 Read(ReadMode read_mode, u32 sector_count, void* buffer); // Read a single raw sector, and subchannel from the current LBA. bool ReadRawSector(void* buffer, SubChannelQ* subq); // 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 HasNonStandardSubchannel() const; // Reads a single sector from an index. virtual bool ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_in_index) = 0; // Retrieve image metadata. virtual std::string GetMetadata(const std::string_view& type) const; // Returns true if this image type has sub-images (e.g. m3u). virtual bool HasSubImages() const; // Returns the number of sub-images in this image, if the format supports multiple. virtual u32 GetSubImageCount() const; // Returns the current sub-image index, if any. virtual u32 GetCurrentSubImage() const; // Changes the current sub-image. If this fails, the image state is unchanged. virtual bool SwitchSubImage(u32 index, Common::Error* error); // Retrieve sub-image metadata. virtual std::string GetSubImageMetadata(u32 index, const std::string_view& type) const; protected: void ClearTOC(); void CopyTOC(const CDImage* image); const Index* GetIndexForDiscPosition(LBA pos); const Index* GetIndexForTrackPosition(u32 track_number, LBA track_pos); /// Generates sub-channel Q given the specified position. bool GenerateSubChannelQ(SubChannelQ* subq, LBA lba); /// Generates sub-channel Q from the given index and index-offset. void GenerateSubChannelQ(SubChannelQ* subq, const Index& index, u32 index_offset); /// Synthesis of lead-out data. void AddLeadOutIndex(); std::string m_filename; u32 m_lba_count = 0; std::vector m_tracks; std::vector m_indices; private: // Position on disc. LBA m_position_on_disc = 0; // Position in track/index. const Index* m_current_index = nullptr; LBA m_position_in_index = 0; LBA m_position_in_track = 0; };