Skip to content

Commit

Permalink
ChdReader: Use core_file to implement precaching
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLastRar committed Dec 14, 2024
1 parent 6315e60 commit 30cacc6
Showing 1 changed file with 116 additions and 27 deletions.
143 changes: 116 additions & 27 deletions pcsx2/CDVD/ChdFileReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ static std::vector<std::pair<std::string, chd_header>> s_chd_hash_cache; // <fil
static std::recursive_mutex s_chd_hash_cache_mutex;

// Provides an implementation of core_file which allows us to control if the underlying FILE handle is freed.
// Additionally, this class allows greater control and feedback while precaching CHD files.
// The lifetime of ChdCoreFileWrapper will be equal to that of the relevant chd_file,
// ChdCoreFileWrapper will also get destroyed if chd_open_core_file fails.
class ChdCoreFileWrapper
Expand All @@ -31,10 +32,15 @@ class ChdCoreFileWrapper
core_file m_core;
std::FILE* m_file;
bool m_free_file = false;
ChdCoreFileWrapper* m_parent = nullptr;
std::unique_ptr<u8[]> m_file_cache;
s64 m_file_cache_size;
s64 m_file_read_ptr;

public:
ChdCoreFileWrapper(std::FILE* file)
ChdCoreFileWrapper(std::FILE* file, ChdCoreFileWrapper* parent)
: m_file{file}
, m_parent{parent}
{
m_core.argp = this;
m_core.fsize = FSize;
Expand All @@ -45,7 +51,7 @@ class ChdCoreFileWrapper

~ChdCoreFileWrapper()
{
if (m_free_file)
if (m_free_file && m_file)
std::fclose(m_file);
}

Expand All @@ -64,15 +70,94 @@ class ChdCoreFileWrapper
m_free_file = isOwner;
}

s64 GetPrecacheSize()
{
const s64 size = static_cast<size_t>(FileSystem::FSize64(m_file));
if (m_parent != nullptr)
return m_parent->GetPrecacheSize() + size;
else
return size;
}

bool Precache(ProgressCallback* progress, Error* error)
{
progress->SetProgressRange(100);

const s64 size = GetPrecacheSize();
return PrecacheInternal(progress, error, 0, size);
}

private:
bool PrecacheInternal(ProgressCallback* progress, Error* error, s64 startSize, s64 finalSize)
{
m_file_cache_size = FileSystem::FSize64(m_file);
if (m_file_cache_size <= 0)
{
Error::SetStringView(error, "Failed to determine file size.");
return false;
}

// Copy the current file position.
m_file_read_ptr = FileSystem::FTell64(m_file);
if (m_file_read_ptr <= 0)
{
Error::SetStringView(error, "Failed to determine file position.");
return false;
}

m_file_cache = std::make_unique_for_overwrite<u8[]>(m_file_cache_size);
if (FileSystem::FSeek64(m_file, 0, SEEK_SET) != 0 ||
FileSystem::ReadFileWithPartialProgress(
m_file, m_file_cache.get(), m_file_cache_size, progress,
(startSize * 100) / finalSize,
((startSize + m_file_cache_size) * 100) / finalSize,
error) != static_cast<size_t>(m_file_cache_size))
{
m_file_cache.reset();
Error::SetStringView(error, "Failed to read part of the file.");
return false;
}

startSize += m_file_cache_size;

if (m_parent)
{
if (!m_parent->PrecacheInternal(progress, error, startSize, finalSize))
{
m_file_cache.reset();
return false;
}
}

if (m_free_file)
std::fclose(m_file);
m_file = nullptr;

return true;
}

static u64 FSize(core_file* file)
{
return static_cast<u64>(FileSystem::FSize64(FromCoreFile(file)->m_file));
ChdCoreFileWrapper* fileWrapper = FromCoreFile(file);
if (fileWrapper->m_file_cache)
return fileWrapper->m_file_cache_size;
else
return static_cast<u64>(FileSystem::FSize64(fileWrapper->m_file));
}

static size_t FRead(void* buffer, size_t elmSize, size_t elmCount, core_file* file)
{
return std::fread(buffer, elmSize, elmCount, FromCoreFile(file)->m_file);
ChdCoreFileWrapper* fileWrapper = FromCoreFile(file);
if (fileWrapper->m_file_cache)
{
// While currently libchdr only uses a elmCount of 1, we can't guarantee that will always be the case.
elmCount = std::min<size_t>(elmCount, std::max<s64>(fileWrapper->m_file_cache_size - fileWrapper->m_file_read_ptr, 0) / elmSize);
const size_t size = elmSize * elmCount;
std::memcpy(buffer, &fileWrapper->m_file_cache[fileWrapper->m_file_read_ptr], size);
return elmCount;
}
else
return std::fread(buffer, elmSize, elmCount, fileWrapper->m_file);
}

static int FClose(core_file* file)
Expand All @@ -84,7 +169,28 @@ class ChdCoreFileWrapper

static int FSeek(core_file* file, int64_t offset, int whence)
{
return FileSystem::FSeek64(FromCoreFile(file)->m_file, offset, whence);
ChdCoreFileWrapper* fileWrapper = FromCoreFile(file);
if (fileWrapper->m_file_cache)
{
switch (whence)
{
case SEEK_SET:
fileWrapper->m_file_read_ptr = offset;
break;
case SEEK_CUR:
fileWrapper->m_file_read_ptr += offset;
break;
case SEEK_END:
fileWrapper->m_file_read_ptr = fileWrapper->m_file_cache_size + offset;
break;
default:
return -1;
}

return 0;
}
else
return FileSystem::FSeek64(fileWrapper->m_file, offset, whence);
}
};

Expand All @@ -98,7 +204,7 @@ ChdFileReader::~ChdFileReader()
static chd_file* OpenCHD(const std::string& filename, FileSystem::ManagedCFilePtr fp, Error* error, u32 recursion_level)
{
chd_file* chd;
ChdCoreFileWrapper* core_wrapper = new ChdCoreFileWrapper(fp.get());
ChdCoreFileWrapper* core_wrapper = new ChdCoreFileWrapper(fp.get(), nullptr);
// libchdr will take ownership of core_wrapper, and will close/free it on failure.
chd_error err = chd_open_core_file(core_wrapper->GetCoreFile(), CHD_OPEN_READ, nullptr, &chd);
if (err == CHDERR_NONE)
Expand Down Expand Up @@ -212,7 +318,7 @@ static chd_file* OpenCHD(const std::string& filename, FileSystem::ManagedCFilePt
}

// Our last core file wrapper got freed, so make a new one.
core_wrapper = new ChdCoreFileWrapper(fp.get());
core_wrapper = new ChdCoreFileWrapper(fp.get(), ChdCoreFileWrapper::FromCoreFile(chd_core_file(parent_chd)));
// Now try re-opening with the parent.
err = chd_open_core_file(core_wrapper->GetCoreFile(), CHD_OPEN_READ, parent_chd, &chd);
if (err != CHDERR_NONE)
Expand Down Expand Up @@ -266,28 +372,11 @@ bool ChdFileReader::Open2(std::string filename, Error* error)

bool ChdFileReader::Precache2(ProgressCallback* progress, Error* error)
{
if (!CheckAvailableMemoryForPrecaching(chd_get_compressed_size(ChdFile), error))
return false;

progress->SetProgressRange(100);

const auto callback = [](size_t pos, size_t total, void* param) -> bool {
ProgressCallback* progress = static_cast<ProgressCallback*>(param);
const u32 percent = static_cast<u32>((pos * 100) / total);
progress->SetProgressValue(std::min<u32>(percent, 100));
return !progress->IsCancelled();
};

const chd_error cerror = chd_precache_progress(ChdFile, callback, progress);
if (cerror != CHDERR_NONE)
{
if (cerror != CHDERR_CANCELLED)
Error::SetStringView(error, "Failed to read part of the file.");

ChdCoreFileWrapper* fileWrapper = ChdCoreFileWrapper::FromCoreFile(chd_core_file(ChdFile));
if (!CheckAvailableMemoryForPrecaching(fileWrapper->GetPrecacheSize(), error))
return false;
}

return true;
return fileWrapper->Precache(progress, error);
}

ThreadedFileReader::Chunk ChdFileReader::ChunkForOffset(u64 offset)
Expand Down

0 comments on commit 30cacc6

Please sign in to comment.