ChdFileReader: Prefer using TOC for file size over header

The header is incorrect, and pads each track to 4 frames.
This commit is contained in:
Stenzek 2023-03-25 14:41:16 +10:00 committed by refractionpcsx2
parent 01b6e1b88d
commit 57fa3ac653
2 changed files with 95 additions and 12 deletions

View File

@ -30,6 +30,12 @@
#pragma clang diagnostic pop #pragma clang diagnostic pop
#endif #endif
ChdFileReader::ChdFileReader()
{
m_blocksize = 2048;
ChdFile = nullptr;
}
ChdFileReader::~ChdFileReader() ChdFileReader::~ChdFileReader()
{ {
Close(); Close();
@ -81,7 +87,7 @@ bool ChdFileReader::Open2(std::string fileName)
std::string dirname; std::string dirname;
FileSystem::FindResultsArray results; FileSystem::FindResultsArray results;
while (CHDERR_REQUIRES_PARENT == (error = chd_open_wrapper(chds[chd_depth].c_str(), &fp, CHD_OPEN_READ, NULL, &child))) while (CHDERR_REQUIRES_PARENT == (error = chd_open_wrapper(chds[chd_depth].c_str(), &fp, CHD_OPEN_READ, nullptr, &child)))
{ {
if (chd_depth >= static_cast<int>(std::size(chds) - 1)) if (chd_depth >= static_cast<int>(std::size(chds) - 1))
{ {
@ -138,7 +144,7 @@ bool ChdFileReader::Open2(std::string fileName)
for (int d = chd_depth - 1; d >= 0; d--) for (int d = chd_depth - 1; d >= 0; d--)
{ {
parent = child; parent = child;
child = NULL; child = nullptr;
error = chd_open_wrapper(chds[d].c_str(), &fp, CHD_OPEN_READ, parent, &child); error = chd_open_wrapper(chds[d].c_str(), &fp, CHD_OPEN_READ, parent, &child);
if (error != CHDERR_NONE) if (error != CHDERR_NONE)
{ {
@ -153,12 +159,24 @@ bool ChdFileReader::Open2(std::string fileName)
ChdFile = child; ChdFile = child;
const chd_header* chd_header = chd_get_header(ChdFile); const chd_header* chd_header = chd_get_header(ChdFile);
file_size = static_cast<u64>(chd_header->unitbytes) * chd_header->unitcount;
hunk_size = chd_header->hunkbytes; hunk_size = chd_header->hunkbytes;
// CHD likes to use full 2448 byte blocks, but keeps the +24 offset of source ISOs // CHD likes to use full 2448 byte blocks, but keeps the +24 offset of source ISOs
// The rest of PCSX2 likes to use 2448 byte buffers, which can't fit that so trim blocks instead // The rest of PCSX2 likes to use 2448 byte buffers, which can't fit that so trim blocks instead
m_internalBlockSize = chd_header->unitbytes; m_internalBlockSize = chd_header->unitbytes;
// The file size in the header is incorrect, each track gets padded to a multiple of 4 frames.
// (see chdman.cpp from MAME). Instead, we pull the real frame count from the TOC.
u64 total_frames;
if (ParseTOC(&total_frames))
{
file_size = total_frames * static_cast<u64>(chd_header->unitbytes);
}
else
{
Console.Warning("Failed to parse CHD TOC, file size may be incorrect.");
file_size = static_cast<u64>(chd_header->unitbytes) * chd_header->unitcount;
}
return true; return true;
} }
@ -195,10 +213,10 @@ int ChdFileReader::ReadChunk(void* dst, s64 chunkID)
void ChdFileReader::Close2() void ChdFileReader::Close2()
{ {
if (ChdFile != NULL) if (ChdFile)
{ {
chd_close(ChdFile); chd_close(ChdFile);
ChdFile = NULL; ChdFile = nullptr;
} }
} }
@ -206,8 +224,70 @@ u32 ChdFileReader::GetBlockCount() const
{ {
return (file_size - m_dataoffset) / m_internalBlockSize; return (file_size - m_dataoffset) / m_internalBlockSize;
} }
ChdFileReader::ChdFileReader(void)
bool ChdFileReader::ParseTOC(u64* out_frame_count)
{ {
m_blocksize = 2048; u64 total_frames = 0;
ChdFile = NULL; int max_found_track = -1;
};
for (int search_index = 0;; search_index++)
{
char metadata_str[256];
char type_str[256];
char subtype_str[256];
char pgtype_str[256];
char pgsub_str[256];
u32 metadata_length;
int track_num = 0, frames = 0, pregap_frames = 0, postgap_frames = 0;
chd_error err = chd_get_metadata(ChdFile, CDROM_TRACK_METADATA2_TAG, search_index, metadata_str, sizeof(metadata_str),
&metadata_length, nullptr, nullptr);
if (err == CHDERR_NONE)
{
if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames,
&pregap_frames, pgtype_str, pgsub_str, &postgap_frames) != 8)
{
Console.Error(fmt::format("Invalid track v2 metadata: '{}'", metadata_str));
return false;
}
}
else
{
// try old version
err = chd_get_metadata(ChdFile, CDROM_TRACK_METADATA_TAG, search_index, metadata_str, sizeof(metadata_str),
&metadata_length, nullptr, nullptr);
if (err != CHDERR_NONE)
{
// not found, so no more tracks
break;
}
if (std::sscanf(metadata_str, CDROM_TRACK_METADATA_FORMAT, &track_num, type_str, subtype_str, &frames) != 4)
{
Console.Error(fmt::format("Invalid track metadata: '{}'", metadata_str));
return false;
}
}
DevCon.WriteLn(fmt::format("CHD Track {}: frames:{} pregap:{} postgap:{} type:{} sub:{} pgtype:{} pgsub:{}",
track_num, frames, pregap_frames, postgap_frames, type_str, subtype_str, pgtype_str, pgsub_str));
// PCSX2 doesn't currently support multiple tracks for CDs.
if (track_num != 1)
{
Console.Warning(fmt::format(" Ignoring track {} in CHD.", track_num, frames));
continue;
}
total_frames += static_cast<u64>(pregap_frames) + static_cast<u64>(frames) + static_cast<u64>(postgap_frames);
max_found_track = std::max(max_found_track, track_num);
}
// No tracks in TOC?
if (max_found_track < 0)
return false;
// Compute total data size.
*out_frame_count = total_frames;
return true;
}

View File

@ -24,19 +24,22 @@ class ChdFileReader : public ThreadedFileReader
DeclareNoncopyableObject(ChdFileReader); DeclareNoncopyableObject(ChdFileReader);
public: public:
virtual ~ChdFileReader() override;; ChdFileReader();
virtual ~ChdFileReader() override;
static bool CanHandle(const std::string& fileName, const std::string& displayName); static bool CanHandle(const std::string& fileName, const std::string& displayName);
bool Open2(std::string fileName) override; bool Open2(std::string fileName) override;
Chunk ChunkForOffset(u64 offset) override; Chunk ChunkForOffset(u64 offset) override;
int ReadChunk(void *dst, s64 blockID) override; int ReadChunk(void* dst, s64 blockID) override;
void Close2(void) override; void Close2(void) override;
uint GetBlockCount(void) const override; uint GetBlockCount(void) const override;
ChdFileReader(void);
private: private:
bool ParseTOC(u64* out_frame_count);
chd_file* ChdFile; chd_file* ChdFile;
u64 file_size; u64 file_size;
u32 hunk_size; u32 hunk_size;