mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-30 01:25:51 +00:00
CDImage: Support SBI replacement subchannel Q for cue/bin images
This commit is contained in:
parent
53621bd3eb
commit
bc44d4b1b0
|
@ -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
|
||||
|
|
|
@ -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<s32>(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<u16, 256> 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);
|
||||
}
|
|
@ -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<u8, u8, u8> ToBCD() const
|
||||
{
|
||||
return std::make_tuple<u8, u8, u8>(DecimalToBCD(minute), DecimalToBCD(second), DecimalToBCD(frame));
|
||||
return std::make_tuple<u8, u8, u8>(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)
|
||||
{
|
||||
|
|
|
@ -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<u32>(1), data_index.start_lba_on_disc, static_cast<u32>(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> CDImage::OpenBinImage(const char* filename)
|
||||
{
|
||||
std::unique_ptr<CDImageBin> image = std::make_unique<CDImageBin>();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "YBaseLib/Log.h"
|
||||
#include "cd_image.h"
|
||||
#include "cd_subchannel_replacement.h"
|
||||
#include <libcue/libcue.h>
|
||||
#include <map>
|
||||
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<std::string, std::FILE*> 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> CDImage::OpenCueSheetImage(const char* filename)
|
||||
{
|
||||
std::unique_ptr<CDImageCueSheet> image = std::make_unique<CDImageCueSheet>();
|
||||
|
|
97
src/common/cd_subchannel_replacement.cpp
Normal file
97
src/common/cd_subchannel_replacement.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
#include "cd_subchannel_replacement.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include <memory>
|
||||
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<std::FILE, void (*)(std::FILE*)> 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;
|
||||
}
|
34
src/common/cd_subchannel_replacement.h
Normal file
34
src/common/cd_subchannel_replacement.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
#include "cd_image.h"
|
||||
#include "types.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <unordered_map>
|
||||
|
||||
class CDSubChannelReplacement
|
||||
{
|
||||
public:
|
||||
enum : u32
|
||||
{
|
||||
SUBCHANNEL_Q_SIZE = 12,
|
||||
};
|
||||
|
||||
CDSubChannelReplacement();
|
||||
~CDSubChannelReplacement();
|
||||
|
||||
u32 GetReplacementSectorCount() const { return static_cast<u32>(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<u8, SUBCHANNEL_Q_SIZE>;
|
||||
using ReplacementMap = std::unordered_map<u32, ReplacementData>;
|
||||
|
||||
std::unordered_map<u32, ReplacementData> m_replacement_subq;
|
||||
};
|
|
@ -50,6 +50,7 @@
|
|||
<ClInclude Include="iso_reader.h" />
|
||||
<ClInclude Include="jit_code_buffer.h" />
|
||||
<ClInclude Include="rectangle.h" />
|
||||
<ClInclude Include="cd_subchannel_replacement.h" />
|
||||
<ClInclude Include="state_wrapper.h" />
|
||||
<ClInclude Include="types.h" />
|
||||
<ClInclude Include="cd_xa.h" />
|
||||
|
@ -68,6 +69,7 @@
|
|||
<ClCompile Include="gl\texture.cpp" />
|
||||
<ClCompile Include="iso_reader.cpp" />
|
||||
<ClCompile Include="jit_code_buffer.cpp" />
|
||||
<ClCompile Include="cd_subchannel_replacement.cpp" />
|
||||
<ClCompile Include="state_wrapper.cpp" />
|
||||
<ClCompile Include="cd_xa.cpp" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
<ClInclude Include="jit_code_buffer.h" />
|
||||
<ClInclude Include="state_wrapper.h" />
|
||||
<ClInclude Include="fifo_queue.h" />
|
||||
<ClInclude Include="cd_image.h" />
|
||||
<ClInclude Include="audio_stream.h" />
|
||||
<ClInclude Include="cd_xa.h" />
|
||||
<ClInclude Include="heap_array.h" />
|
||||
|
@ -33,6 +32,8 @@
|
|||
</ClInclude>
|
||||
<ClInclude Include="rectangle.h" />
|
||||
<ClInclude Include="iso_reader.h" />
|
||||
<ClInclude Include="cd_image.h" />
|
||||
<ClInclude Include="cd_subchannel_replacement.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="jit_code_buffer.cpp" />
|
||||
|
@ -64,6 +65,7 @@
|
|||
<Filter>d3d11</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="iso_reader.cpp" />
|
||||
<ClCompile Include="cd_subchannel_replacement.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="bitfield.natvis" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue