Skip to content

Commit

Permalink
CDImageCHD: Support loading parent/delta CHDs
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Aug 12, 2023
1 parent f41384c commit 165b277
Showing 1 changed file with 104 additions and 14 deletions.
118 changes: 104 additions & 14 deletions src/util/cd_image_chd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,27 @@

#include "cd_image.h"
#include "cd_subchannel_replacement.h"

#include "common/align.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "common/platform.h"
#include "common/string_util.h"

#include "fmt/format.h"
#include "libchdr/chd.h"

#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <limits>
#include <map>
#include <optional>

Log_SetChannel(CDImageCHD);

static std::optional<CDImage::TrackMode> ParseTrackModeString(const char* str)
Expand All @@ -46,6 +52,7 @@ static std::optional<CDImage::TrackMode> ParseTrackModeString(const char* str)
return std::nullopt;
}

namespace {
class CDImageCHD : public CDImage
{
public:
Expand All @@ -66,12 +73,13 @@ class CDImageCHD : public CDImage
enum : u32
{
CHD_CD_SECTOR_DATA_SIZE = 2352 + 96,
CHD_CD_TRACK_ALIGNMENT = 4
CHD_CD_TRACK_ALIGNMENT = 4,
MAX_PARENTS = 32 // Surely someone wouldn't be insane enough to go beyond this...
};

chd_file* OpenCHD(const char* filename, FileSystem::ManagedCFilePtr fp, Common::Error* error, u32 recursion_level);
bool ReadHunk(u32 hunk_index);

std::FILE* m_fp = nullptr;
chd_file* m_chd = nullptr;
u32 m_hunk_size = 0;
u32 m_sectors_per_hunk = 0;
Expand All @@ -82,40 +90,122 @@ class CDImageCHD : public CDImage

CDSubChannelReplacement m_sbi;
};
} // namespace

CDImageCHD::CDImageCHD() = default;

CDImageCHD::~CDImageCHD()
{
if (m_chd)
chd_close(m_chd);
if (m_fp)
std::fclose(m_fp);
}

bool CDImageCHD::Open(const char* filename, Common::Error* error)
chd_file* CDImageCHD::OpenCHD(const char* filename, FileSystem::ManagedCFilePtr fp, Common::Error* error,
u32 recursion_level)
{
Assert(!m_fp);
m_fp = FileSystem::OpenCFile(filename, "rb");
if (!m_fp)
chd_file* chd;
chd_error err = chd_open_file(fp.get(), CHD_OPEN_READ | CHD_OPEN_TRANSFER_FILE, nullptr, &chd);
if (err == CHDERR_NONE)
{
Log_ErrorPrintf("Failed to open CHD '%s': errno %d", filename, errno);
// fp is now managed by libchdr
fp.release();
return chd;
}
else if (err != CHDERR_REQUIRES_PARENT)
{
Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err));
if (error)
error->SetErrno(errno);
error->SetMessage(chd_error_string(err));
return nullptr;
}

return false;
if (recursion_level >= MAX_PARENTS)
{
Log_ErrorPrintf("Failed to open CHD '%s': Too many parent files", filename);
if (error)
error->SetMessage("Too many parent files");
return nullptr;
}

// Need to get the sha1 to look for.
chd_header header;
err = chd_read_header_file(fp.get(), &header);
if (err != CHDERR_NONE)
{
Log_ErrorPrintf("Failed to read CHD header '%s': %s", filename, chd_error_string(err));
if (error)
error->SetMessage(chd_error_string(err));
return nullptr;
}

// Find a chd with a matching sha1 in the same directory.
// Have to do *.* and filter on the extension manually because Linux is case sensitive.
// We _could_ memoize the CHD headers here, but is anyone actually going to nest CHDs that deep?
chd_file* parent_chd = nullptr;
const std::string parent_dir(Path::GetDirectory(filename));
FileSystem::FindResultsArray parent_files;
FileSystem::FindFiles(parent_dir.c_str(), "*.*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &parent_files);
for (FILESYSTEM_FIND_DATA& fd : parent_files)
{
if (StringUtil::EndsWithNoCase(Path::GetExtension(fd.FileName), ".chd"))
continue;

auto parent_fp =
FileSystem::OpenManagedSharedCFile(fd.FileName.c_str(), "rb", FileSystem::FileShareMode::DenyWrite);
if (!parent_fp)
continue;

chd_header parent_header;
err = chd_read_header_file(parent_fp.get(), &parent_header);
if (err != CHDERR_NONE || !chd_is_matching_parent(&header, &parent_header))
continue;

// Match! Open this one.
if ((parent_chd = OpenCHD(fd.FileName.c_str(), std::move(parent_fp), error, recursion_level + 1)) != nullptr)
{
Log_DevPrintf(fmt::format("Found parent CHD '{}' for '{}'.", Path::GetFileName(fd.FileName), Path::GetFileName(filename)).c_str());
break;
}
}
if (!parent_chd)
{
Log_ErrorPrintf("Failed to open CHD '%s': Failed to find parent CHD, it must be in the same directory.", filename);
if (error)
error->SetMessage("Failed to find parent CHD, it must be in the same directory.");
return nullptr;
}

chd_error err = chd_open_file(m_fp, CHD_OPEN_READ, nullptr, &m_chd);
// Now try re-opening with the parent.
err = chd_open_file(fp.get(), CHD_OPEN_READ | CHD_OPEN_TRANSFER_FILE, parent_chd, &chd);
if (err != CHDERR_NONE)
{
Log_ErrorPrintf("Failed to open CHD '%s': %s", filename, chd_error_string(err));
if (error)
error->SetMessage(chd_error_string(err));
return nullptr;
}

// fp now owned by libchdr
fp.release();
return chd;
}

bool CDImageCHD::Open(const char* filename, Common::Error* error)
{
auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite);
if (!fp)
{
Log_ErrorPrintf("Failed to open CHD '%s': errno %d", filename, errno);
if (error)
error->SetErrno(errno);

return false;
}

m_chd = OpenCHD(filename, std::move(fp), error, 0);
if (!m_chd)
return false;

const chd_header* header = chd_get_header(m_chd);
m_hunk_size = header->hunkbytes;
if ((m_hunk_size % CHD_CD_SECTOR_DATA_SIZE) != 0)
Expand Down Expand Up @@ -146,8 +236,8 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
u32 metadata_length;

int track_num = 0, frames = 0, pregap_frames = 0, postgap_frames = 0;
err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, metadata_str, sizeof(metadata_str),
&metadata_length, nullptr, nullptr);
chd_error err = chd_get_metadata(m_chd, CDROM_TRACK_METADATA2_TAG, num_tracks, 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,
Expand Down

0 comments on commit 165b277

Please # to comment.