CDImage: Support SBI replacement subchannel Q for cue/bin images

This commit is contained in:
Connor McLaughlin 2019-12-06 16:23:08 +10:00
parent 53621bd3eb
commit bc44d4b1b0
11 changed files with 226 additions and 22 deletions

View file

@ -6,6 +6,8 @@ add_library(common
cd_image.h cd_image.h
cd_image_bin.cpp cd_image_bin.cpp
cd_image_cue.cpp cd_image_cue.cpp
cd_subchannel_replacement.cpp
cd_subchannel_replacement.h
cd_xa.cpp cd_xa.cpp
cd_xa.h cd_xa.h
gl/program.cpp gl/program.cpp

View file

@ -243,7 +243,8 @@ bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba)
if (!index) if (!index)
return false; 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); GenerateSubChannelQ(subq, index, index_offset);
return true; return true;
} }
@ -251,8 +252,8 @@ bool CDImage::GenerateSubChannelQ(SubChannelQ* subq, LBA lba)
void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset) void CDImage::GenerateSubChannelQ(SubChannelQ* subq, const Index* index, u32 index_offset)
{ {
subq->control.bits = index->control.bits; subq->control.bits = index->control.bits;
subq->track_number_bcd = DecimalToBCD(index->track_number); subq->track_number_bcd = BinaryToBCD(index->track_number);
subq->index_number_bcd = DecimalToBCD(index->index_number); subq->index_number_bcd = BinaryToBCD(index->index_number);
const Position relative_position = const Position relative_position =
Position::FromLBA(std::abs(static_cast<s32>(index->start_lba_in_track + index_offset))); 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); 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(); 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 = { static constexpr std::array<u16, 256> crc16_table = {
{0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, {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); return ~(value >> 8) | (~(value) << 8);
} }
bool CDImage::SubChannelQ::IsCRCValid() const
{
return crc == ComputeCRC(data);
}

View file

@ -64,7 +64,7 @@ public:
static constexpr Position FromBCD(u8 minute, u8 second, u8 frame) 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) static constexpr Position FromLBA(LBA lba)
@ -87,7 +87,7 @@ public:
constexpr std::tuple<u8, u8, u8> ToBCD() const 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()); } Position operator+(const Position& rhs) { return FromLBA(ToLBA() + rhs.ToLBA()); }
@ -143,7 +143,9 @@ public:
u8 data[SUBCHANNEL_BYTES_PER_FRAME]; u8 data[SUBCHANNEL_BYTES_PER_FRAME];
u16 ComputeCRC() const; static u16 ComputeCRC(const u8* data);
bool IsCRCValid() const;
SubChannelQ& operator=(const SubChannelQ& q) SubChannelQ& operator=(const SubChannelQ& q)
{ {

View file

@ -1,5 +1,6 @@
#include "YBaseLib/Log.h" #include "YBaseLib/Log.h"
#include "cd_image.h" #include "cd_image.h"
#include "cd_subchannel_replacement.h"
Log_SetChannel(CDImageBin); Log_SetChannel(CDImageBin);
class CDImageBin : public CDImage class CDImageBin : public CDImage
@ -10,10 +11,25 @@ public:
bool Open(const char* filename); bool Open(const char* filename);
bool ReadSubChannelQ(SubChannelQ* subq) override;
private: private:
std::FILE* m_fp = nullptr; 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() = default;
CDImageBin::~CDImageBin() CDImageBin::~CDImageBin()
@ -77,9 +93,19 @@ bool CDImageBin::Open(const char* filename)
m_tracks.push_back( m_tracks.push_back(
Track{static_cast<u32>(1), data_index.start_lba_on_disc, static_cast<u32>(0), m_lba_count, mode, control}); 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}); 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<CDImage> CDImage::OpenBinImage(const char* filename)
{ {
std::unique_ptr<CDImageBin> image = std::make_unique<CDImageBin>(); std::unique_ptr<CDImageBin> image = std::make_unique<CDImageBin>();

View file

@ -1,5 +1,6 @@
#include "YBaseLib/Log.h" #include "YBaseLib/Log.h"
#include "cd_image.h" #include "cd_image.h"
#include "cd_subchannel_replacement.h"
#include <libcue/libcue.h> #include <libcue/libcue.h>
#include <map> #include <map>
Log_SetChannel(CDImageCueSheet); Log_SetChannel(CDImageCueSheet);
@ -12,9 +13,12 @@ public:
bool OpenAndParse(const char* filename); bool OpenAndParse(const char* filename);
bool ReadSubChannelQ(SubChannelQ* subq) override;
private: private:
Cd* m_cd = nullptr; Cd* m_cd = nullptr;
std::map<std::string, std::FILE*> m_files; std::map<std::string, std::FILE*> m_files;
CDSubChannelReplacement m_sbi;
}; };
CDImageCueSheet::CDImageCueSheet() = default; CDImageCueSheet::CDImageCueSheet() = default;
@ -44,6 +48,17 @@ static std::string GetPathDirectory(const char* path)
return str; 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) bool CDImageCueSheet::OpenAndParse(const char* filename)
{ {
std::FILE* cue_fp = std::fopen(filename, "rb"); std::FILE* cue_fp = std::fopen(filename, "rb");
@ -202,9 +217,20 @@ bool CDImageCueSheet::OpenAndParse(const char* filename)
} }
m_lba_count = disc_lba; m_lba_count = disc_lba;
m_sbi.LoadSBI(ReplaceExtension(filename, "sbi").c_str());
return Seek(1, Position{0, 0, 0}); 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<CDImage> CDImage::OpenCueSheetImage(const char* filename)
{ {
std::unique_ptr<CDImageCueSheet> image = std::make_unique<CDImageCueSheet>(); std::unique_ptr<CDImageCueSheet> image = std::make_unique<CDImageCueSheet>();

View 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;
}

View 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;
};

View file

@ -50,6 +50,7 @@
<ClInclude Include="iso_reader.h" /> <ClInclude Include="iso_reader.h" />
<ClInclude Include="jit_code_buffer.h" /> <ClInclude Include="jit_code_buffer.h" />
<ClInclude Include="rectangle.h" /> <ClInclude Include="rectangle.h" />
<ClInclude Include="cd_subchannel_replacement.h" />
<ClInclude Include="state_wrapper.h" /> <ClInclude Include="state_wrapper.h" />
<ClInclude Include="types.h" /> <ClInclude Include="types.h" />
<ClInclude Include="cd_xa.h" /> <ClInclude Include="cd_xa.h" />
@ -68,6 +69,7 @@
<ClCompile Include="gl\texture.cpp" /> <ClCompile Include="gl\texture.cpp" />
<ClCompile Include="iso_reader.cpp" /> <ClCompile Include="iso_reader.cpp" />
<ClCompile Include="jit_code_buffer.cpp" /> <ClCompile Include="jit_code_buffer.cpp" />
<ClCompile Include="cd_subchannel_replacement.cpp" />
<ClCompile Include="state_wrapper.cpp" /> <ClCompile Include="state_wrapper.cpp" />
<ClCompile Include="cd_xa.cpp" /> <ClCompile Include="cd_xa.cpp" />
</ItemGroup> </ItemGroup>

View file

@ -6,7 +6,6 @@
<ClInclude Include="jit_code_buffer.h" /> <ClInclude Include="jit_code_buffer.h" />
<ClInclude Include="state_wrapper.h" /> <ClInclude Include="state_wrapper.h" />
<ClInclude Include="fifo_queue.h" /> <ClInclude Include="fifo_queue.h" />
<ClInclude Include="cd_image.h" />
<ClInclude Include="audio_stream.h" /> <ClInclude Include="audio_stream.h" />
<ClInclude Include="cd_xa.h" /> <ClInclude Include="cd_xa.h" />
<ClInclude Include="heap_array.h" /> <ClInclude Include="heap_array.h" />
@ -33,6 +32,8 @@
</ClInclude> </ClInclude>
<ClInclude Include="rectangle.h" /> <ClInclude Include="rectangle.h" />
<ClInclude Include="iso_reader.h" /> <ClInclude Include="iso_reader.h" />
<ClInclude Include="cd_image.h" />
<ClInclude Include="cd_subchannel_replacement.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="jit_code_buffer.cpp" /> <ClCompile Include="jit_code_buffer.cpp" />
@ -64,6 +65,7 @@
<Filter>d3d11</Filter> <Filter>d3d11</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="iso_reader.cpp" /> <ClCompile Include="iso_reader.cpp" />
<ClCompile Include="cd_subchannel_replacement.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Natvis Include="bitfield.natvis" /> <Natvis Include="bitfield.natvis" />

View file

@ -139,15 +139,22 @@ ALWAYS_INLINE constexpr u32 Truncate32(TValue value)
} }
// BCD helpers // BCD helpers
ALWAYS_INLINE constexpr u8 DecimalToBCD(u8 value) ALWAYS_INLINE constexpr u8 BinaryToBCD(u8 value)
{ {
return ((value / 10) << 4) + (value % 10); return ((value / 10) << 4) + (value % 10);
} }
ALWAYS_INLINE constexpr u8 PackedBCDToBinary(u8 value)
ALWAYS_INLINE constexpr u8 BCDToDecimal(u8 value)
{ {
return ((value >> 4) * 10) + (value % 16); 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 // Boolean to integer
ALWAYS_INLINE constexpr u8 BoolToUInt8(bool value) ALWAYS_INLINE constexpr u8 BoolToUInt8(bool value)

View file

@ -696,9 +696,9 @@ void CDROM::ExecuteCommand()
case Command::Setloc: case Command::Setloc:
{ {
// TODO: Verify parameter count // TODO: Verify parameter count
m_setloc_position.minute = BCDToDecimal(m_param_fifo.Peek(0)); m_setloc_position.minute = PackedBCDToBinary(m_param_fifo.Peek(0));
m_setloc_position.second = BCDToDecimal(m_param_fifo.Peek(1)); m_setloc_position.second = PackedBCDToBinary(m_param_fifo.Peek(1));
m_setloc_position.frame = BCDToDecimal(m_param_fifo.Peek(2)); m_setloc_position.frame = PackedBCDToBinary(m_param_fifo.Peek(2));
m_setloc_pending = true; m_setloc_pending = true;
Log_DebugPrintf("CDROM setloc command (%02X, %02X, %02X)", ZeroExtend32(m_param_fifo.Peek(0)), 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))); ZeroExtend32(m_param_fifo.Peek(1)), ZeroExtend32(m_param_fifo.Peek(2)));
@ -890,8 +890,8 @@ void CDROM::ExecuteCommand()
if (m_media) if (m_media)
{ {
m_response_fifo.Push(m_secondary_status.bits); m_response_fifo.Push(m_secondary_status.bits);
m_response_fifo.Push(DecimalToBCD(Truncate8(m_media->GetTrackNumber()))); m_response_fifo.Push(BinaryToBCD(Truncate8(m_media->GetTrackNumber())));
m_response_fifo.Push(DecimalToBCD(Truncate8(m_media->GetTrackCount()))); m_response_fifo.Push(BinaryToBCD(Truncate8(m_media->GetTrackCount())));
SetInterrupt(Interrupt::ACK); SetInterrupt(Interrupt::ACK);
} }
else else
@ -907,7 +907,7 @@ void CDROM::ExecuteCommand()
{ {
Log_DebugPrintf("CDROM GetTD command"); Log_DebugPrintf("CDROM GetTD command");
Assert(m_param_fifo.GetSize() >= 1); 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) if (!m_media)
{ {
@ -926,8 +926,8 @@ void CDROM::ExecuteCommand()
pos = m_media->GetTrackStartMSFPosition(track); pos = m_media->GetTrackStartMSFPosition(track);
m_response_fifo.Push(m_secondary_status.bits); m_response_fifo.Push(m_secondary_status.bits);
m_response_fifo.Push(DecimalToBCD(Truncate8(pos.minute))); m_response_fifo.Push(BinaryToBCD(Truncate8(pos.minute)));
m_response_fifo.Push(DecimalToBCD(Truncate8(pos.second))); m_response_fifo.Push(BinaryToBCD(Truncate8(pos.second)));
SetInterrupt(Interrupt::ACK); SetInterrupt(Interrupt::ACK);
} }
@ -1043,10 +1043,10 @@ void CDROM::BeginPlaying(u8 track_bcd)
if (track_bcd > m_media->GetTrackCount()) if (track_bcd > m_media->GetTrackCount())
{ {
// restart current track // 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; m_setloc_pending = true;
} }