diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e5d8fc8dd..4d0623c1c 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -6,6 +6,8 @@ add_library(common cd_image.h cd_image_bin.cpp cd_image_cue.cpp + cd_subchannel_replacement.cpp + cd_subchannel_replacement.h cd_xa.cpp cd_xa.h gl/program.cpp diff --git a/src/common/cd_image.cpp b/src/common/cd_image.cpp index 9b0765937..5eb5cf256 100644 --- a/src/common/cd_image.cpp +++ b/src/common/cd_image.cpp @@ -243,7 +243,8 @@ bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) if (!index) return false; - const u32 index_offset = index->start_lba_on_disc - lba;; + const u32 index_offset = index->start_lba_on_disc - lba; + ; GenerateSubChannelQ(subq, index, index_offset); return true; } @@ -251,8 +252,8 @@ bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba) void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset) { subq->control.bits = index->control.bits; - subq->track_number_bcd = DecimalToBCD(index->track_number); - subq->index_number_bcd = DecimalToBCD(index->index_number); + subq->track_number_bcd = BinaryToBCD(index->track_number); + subq->index_number_bcd = BinaryToBCD(index->index_number); const Position relative_position = Position::FromLBA(std::abs(static_cast(index->start_lba_in_track + index_offset))); @@ -261,10 +262,10 @@ void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 ind const Position absolute_position = Position::FromLBA(index->start_lba_on_disc + index_offset); std::tie(subq->absolute_minute_bcd, subq->absolute_second_bcd, subq->absolute_frame_bcd) = absolute_position.ToBCD(); - subq->crc = subq->ComputeCRC(); + subq->crc = SubChannelQ::ComputeCRC(subq->data); } -u16 CDImage::SubChannelQ::ComputeCRC() const +u16 CDImage::SubChannelQ::ComputeCRC(const u8* data) { static constexpr std::array crc16_table = { {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, @@ -293,3 +294,8 @@ u16 CDImage::SubChannelQ::ComputeCRC() const return ~(value >> 8) | (~(value) << 8); } + +bool CDImage::SubChannelQ::IsCRCValid() const +{ + return crc == ComputeCRC(data); +} \ No newline at end of file diff --git a/src/common/cd_image.h b/src/common/cd_image.h index 5c21a56a6..62fb1597a 100644 --- a/src/common/cd_image.h +++ b/src/common/cd_image.h @@ -64,7 +64,7 @@ public: static constexpr Position FromBCD(u8 minute, u8 second, u8 frame) { - return Position{BCDToDecimal(minute), BCDToDecimal(second), BCDToDecimal(frame)}; + return Position{PackedBCDToBinary(minute), PackedBCDToBinary(second), PackedBCDToBinary(frame)}; } static constexpr Position FromLBA(LBA lba) @@ -87,7 +87,7 @@ public: constexpr std::tuple ToBCD() const { - return std::make_tuple(DecimalToBCD(minute), DecimalToBCD(second), DecimalToBCD(frame)); + return std::make_tuple(BinaryToBCD(minute), BinaryToBCD(second), BinaryToBCD(frame)); } Position operator+(const Position& rhs) { return FromLBA(ToLBA() + rhs.ToLBA()); } @@ -143,7 +143,9 @@ public: u8 data[SUBCHANNEL_BYTES_PER_FRAME]; - u16 ComputeCRC() const; + static u16 ComputeCRC(const u8* data); + + bool IsCRCValid() const; SubChannelQ& operator=(const SubChannelQ& q) { diff --git a/src/common/cd_image_bin.cpp b/src/common/cd_image_bin.cpp index b5dbd3f37..7930ad354 100644 --- a/src/common/cd_image_bin.cpp +++ b/src/common/cd_image_bin.cpp @@ -1,5 +1,6 @@ #include "YBaseLib/Log.h" #include "cd_image.h" +#include "cd_subchannel_replacement.h" Log_SetChannel(CDImageBin); class CDImageBin : public CDImage @@ -10,10 +11,25 @@ public: bool Open(const char* filename); + bool ReadSubChannelQ(SubChannelQ* subq) override; + private: std::FILE* m_fp = nullptr; + + CDSubChannelReplacement m_sbi; }; +static std::string ReplaceExtension(std::string_view path, std::string_view new_extension) +{ + std::string_view::size_type pos = path.rfind('.'); + if (pos == std::string::npos) + return std::string(path); + + std::string ret(path, 0, pos + 1); + ret.append(new_extension); + return ret; +} + CDImageBin::CDImageBin() = default; CDImageBin::~CDImageBin() @@ -77,9 +93,19 @@ bool CDImageBin::Open(const char* filename) m_tracks.push_back( Track{static_cast(1), data_index.start_lba_on_disc, static_cast(0), m_lba_count, mode, control}); + m_sbi.LoadSBI(ReplaceExtension(filename, "sbi").c_str()); + return Seek(1, Position{0, 0, 0}); } +bool CDImageBin::ReadSubChannelQ(SubChannelQ* subq) +{ + if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq->data)) + return true; + + return CDImage::ReadSubChannelQ(subq); +} + std::unique_ptr CDImage::OpenBinImage(const char* filename) { std::unique_ptr image = std::make_unique(); diff --git a/src/common/cd_image_cue.cpp b/src/common/cd_image_cue.cpp index 91f9ec66c..464ee21f6 100644 --- a/src/common/cd_image_cue.cpp +++ b/src/common/cd_image_cue.cpp @@ -1,5 +1,6 @@ #include "YBaseLib/Log.h" #include "cd_image.h" +#include "cd_subchannel_replacement.h" #include #include Log_SetChannel(CDImageCueSheet); @@ -12,9 +13,12 @@ public: bool OpenAndParse(const char* filename); + bool ReadSubChannelQ(SubChannelQ* subq) override; + private: Cd* m_cd = nullptr; std::map m_files; + CDSubChannelReplacement m_sbi; }; CDImageCueSheet::CDImageCueSheet() = default; @@ -44,6 +48,17 @@ static std::string GetPathDirectory(const char* path) return str; } +static std::string ReplaceExtension(std::string_view path, std::string_view new_extension) +{ + std::string_view::size_type pos = path.rfind('.'); + if (pos == std::string::npos) + return std::string(path); + + std::string ret(path, 0, pos + 1); + ret.append(new_extension); + return ret; +} + bool CDImageCueSheet::OpenAndParse(const char* filename) { std::FILE* cue_fp = std::fopen(filename, "rb"); @@ -202,9 +217,20 @@ bool CDImageCueSheet::OpenAndParse(const char* filename) } m_lba_count = disc_lba; + + m_sbi.LoadSBI(ReplaceExtension(filename, "sbi").c_str()); + return Seek(1, Position{0, 0, 0}); } +bool CDImageCueSheet::ReadSubChannelQ(SubChannelQ* subq) +{ + if (m_sbi.GetReplacementSubChannelQ(m_position_on_disc, subq->data)) + return true; + + return CDImage::ReadSubChannelQ(subq); +} + std::unique_ptr CDImage::OpenCueSheetImage(const char* filename) { std::unique_ptr image = std::make_unique(); diff --git a/src/common/cd_subchannel_replacement.cpp b/src/common/cd_subchannel_replacement.cpp new file mode 100644 index 000000000..cced30f33 --- /dev/null +++ b/src/common/cd_subchannel_replacement.cpp @@ -0,0 +1,97 @@ +#include "cd_subchannel_replacement.h" +#include "YBaseLib/Log.h" +#include +Log_SetChannel(CDSubChannelReplacement); + +#pragma pack(push, 1) +struct SBIFileEntry +{ + u8 minute_bcd; + u8 second_bcd; + u8 frame_bcd; + u8 type; + u8 data[10]; +}; +#pragma pack(pop) + +CDSubChannelReplacement::CDSubChannelReplacement() = default; + +CDSubChannelReplacement::~CDSubChannelReplacement() = default; + +static constexpr u32 MSFToLBA(u8 minute_bcd, u8 second_bcd, u8 frame_bcd) +{ + const u8 minute = PackedBCDToBinary(minute_bcd); + const u8 second = PackedBCDToBinary(second_bcd); + const u8 frame = PackedBCDToBinary(frame_bcd); + + return (ZeroExtend32(minute) * 60 * 75) + (ZeroExtend32(second) * 75) + ZeroExtend32(frame); +} + +bool CDSubChannelReplacement::LoadSBI(const char* path) +{ + std::unique_ptr fp(std::fopen(path, "rb"), [](std::FILE* fp) { std::fclose(fp); }); + if (!fp) + return false; + + char header[4]; + if (std::fread(header, sizeof(header), 1, fp.get()) != 1) + { + Log_ErrorPrintf("Failed to read header for '%s'", path); + return true; + } + + static constexpr char expected_header[] = {'S', 'B', 'I', '\0'}; + if (std::memcmp(header, expected_header, sizeof(header)) != 0) + { + Log_ErrorPrintf("Invalid header in '%s'", path); + return true; + } + + SBIFileEntry entry; + while (std::fread(&entry, sizeof(entry), 1, fp.get()) == 1) + { + if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) || + !IsValidPackedBCD(entry.frame_bcd)) + { + Log_ErrorPrintf("Invalid position [%02x:%02x:%02x] in '%s'", entry.minute_bcd, entry.second_bcd, entry.frame_bcd, + path); + return false; + } + + if (entry.type != 1) + { + Log_ErrorPrintf("Invalid type 0x%02X in '%s'", path); + return false; + } + + const u32 lba = MSFToLBA(entry.minute_bcd, entry.second_bcd, entry.frame_bcd); + + ReplacementData subq_data; + std::copy_n(entry.data, countof(entry.data), subq_data.data()); + + // generate an invalid crc by flipping all bits from the valid crc (will never collide) + const u16 crc = CDImage::SubChannelQ::ComputeCRC(subq_data.data()) ^ 0xFFFF; + subq_data[10] = Truncate8(crc); + subq_data[11] = Truncate8(crc >> 8); + + m_replacement_subq.emplace(lba, subq_data); + } + + Log_InfoPrintf("Loaded %zu replacement sectors from '%s'", m_replacement_subq.size(), path); + return true; +} + +bool CDSubChannelReplacement::GetReplacementSubChannelQ(u8 minute_bcd, u8 second_bcd, u8 frame_bcd, u8* subq_data) const +{ + return GetReplacementSubChannelQ(MSFToLBA(minute_bcd, second_bcd, frame_bcd), subq_data); +} + +bool CDSubChannelReplacement::GetReplacementSubChannelQ(u32 lba, u8* subq_data) const +{ + ReplacementMap::const_iterator iter = m_replacement_subq.find(lba); + if (iter == m_replacement_subq.end()) + return false; + + std::copy(iter->second.begin(), iter->second.end(), subq_data); + return true; +} diff --git a/src/common/cd_subchannel_replacement.h b/src/common/cd_subchannel_replacement.h new file mode 100644 index 000000000..5986f186b --- /dev/null +++ b/src/common/cd_subchannel_replacement.h @@ -0,0 +1,34 @@ +#pragma once +#include "cd_image.h" +#include "types.h" +#include +#include +#include + +class CDSubChannelReplacement +{ +public: + enum : u32 + { + SUBCHANNEL_Q_SIZE = 12, + }; + + CDSubChannelReplacement(); + ~CDSubChannelReplacement(); + + u32 GetReplacementSectorCount() const { return static_cast(m_replacement_subq.size()); } + + bool LoadSBI(const char* path); + + /// Returns the replacement subchannel data for the specified position (in BCD). + bool GetReplacementSubChannelQ(u8 minute_bcd, u8 second_bcd, u8 frame_bcd, u8* subq_data) const; + + /// Returns the replacement subchannel data for the specified sector. + bool GetReplacementSubChannelQ(u32 lba, u8* subq_data) const; + +private: + using ReplacementData = std::array; + using ReplacementMap = std::unordered_map; + + std::unordered_map m_replacement_subq; +}; diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index ecede6003..e2e998a02 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -50,6 +50,7 @@ + @@ -68,6 +69,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 89a9bf9b3..f8321b034 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -6,7 +6,6 @@ - @@ -33,6 +32,8 @@ + + @@ -64,6 +65,7 @@ d3d11 + diff --git a/src/common/types.h b/src/common/types.h index 8f1c8ea00..f79e75b90 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -139,15 +139,22 @@ ALWAYS_INLINE constexpr u32 Truncate32(TValue value) } // BCD helpers -ALWAYS_INLINE constexpr u8 DecimalToBCD(u8 value) +ALWAYS_INLINE constexpr u8 BinaryToBCD(u8 value) { return ((value / 10) << 4) + (value % 10); } - -ALWAYS_INLINE constexpr u8 BCDToDecimal(u8 value) +ALWAYS_INLINE constexpr u8 PackedBCDToBinary(u8 value) { return ((value >> 4) * 10) + (value % 16); } +ALWAYS_INLINE constexpr u8 IsValidBCDDigit(u8 digit) +{ + return (digit <= 9); +} +ALWAYS_INLINE constexpr u8 IsValidPackedBCD(u8 value) +{ + return IsValidBCDDigit(value & 0x0F) && IsValidBCDDigit(value >> 4); +} // Boolean to integer ALWAYS_INLINE constexpr u8 BoolToUInt8(bool value) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index afe9c09d9..d0a8d99b8 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -696,9 +696,9 @@ void CDROM::ExecuteCommand() case Command::Setloc: { // TODO: Verify parameter count - m_setloc_position.minute = BCDToDecimal(m_param_fifo.Peek(0)); - m_setloc_position.second = BCDToDecimal(m_param_fifo.Peek(1)); - m_setloc_position.frame = BCDToDecimal(m_param_fifo.Peek(2)); + m_setloc_position.minute = PackedBCDToBinary(m_param_fifo.Peek(0)); + m_setloc_position.second = PackedBCDToBinary(m_param_fifo.Peek(1)); + m_setloc_position.frame = PackedBCDToBinary(m_param_fifo.Peek(2)); m_setloc_pending = true; Log_DebugPrintf("CDROM setloc command (%02X, %02X, %02X)", ZeroExtend32(m_param_fifo.Peek(0)), ZeroExtend32(m_param_fifo.Peek(1)), ZeroExtend32(m_param_fifo.Peek(2))); @@ -890,8 +890,8 @@ void CDROM::ExecuteCommand() if (m_media) { m_response_fifo.Push(m_secondary_status.bits); - m_response_fifo.Push(DecimalToBCD(Truncate8(m_media->GetTrackNumber()))); - m_response_fifo.Push(DecimalToBCD(Truncate8(m_media->GetTrackCount()))); + m_response_fifo.Push(BinaryToBCD(Truncate8(m_media->GetTrackNumber()))); + m_response_fifo.Push(BinaryToBCD(Truncate8(m_media->GetTrackCount()))); SetInterrupt(Interrupt::ACK); } else @@ -907,7 +907,7 @@ void CDROM::ExecuteCommand() { Log_DebugPrintf("CDROM GetTD command"); Assert(m_param_fifo.GetSize() >= 1); - const u8 track = BCDToDecimal(m_param_fifo.Peek()); + const u8 track = PackedBCDToBinary(m_param_fifo.Peek()); if (!m_media) { @@ -926,8 +926,8 @@ void CDROM::ExecuteCommand() pos = m_media->GetTrackStartMSFPosition(track); m_response_fifo.Push(m_secondary_status.bits); - m_response_fifo.Push(DecimalToBCD(Truncate8(pos.minute))); - m_response_fifo.Push(DecimalToBCD(Truncate8(pos.second))); + m_response_fifo.Push(BinaryToBCD(Truncate8(pos.minute))); + m_response_fifo.Push(BinaryToBCD(Truncate8(pos.second))); SetInterrupt(Interrupt::ACK); } @@ -1043,10 +1043,10 @@ void CDROM::BeginPlaying(u8 track_bcd) if (track_bcd > m_media->GetTrackCount()) { // restart current track - track_bcd = DecimalToBCD(Truncate8(m_media->GetTrackNumber())); + track_bcd = BinaryToBCD(Truncate8(m_media->GetTrackNumber())); } - m_setloc_position = m_media->GetTrackStartMSFPosition(BCDToDecimal(track_bcd)); + m_setloc_position = m_media->GetTrackStartMSFPosition(PackedBCDToBinary(track_bcd)); m_setloc_pending = true; }