mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
247 lines
7.0 KiB
C++
247 lines
7.0 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "Config.h"
|
|
#include "GzippedFileReader.h"
|
|
#include "Host.h"
|
|
#include "CDVD/zlib_indexed.h"
|
|
|
|
#include "common/Console.h"
|
|
#include "common/FileSystem.h"
|
|
#include "common/Error.h"
|
|
#include "common/Path.h"
|
|
#include "common/StringUtil.h"
|
|
#include "common/Timer.h"
|
|
|
|
#include "fmt/format.h"
|
|
|
|
#define GZIP_ID "PCSX2.index.gzip.v1|"
|
|
#define GZIP_ID_LEN (sizeof(GZIP_ID) - 1) /* sizeof includes the \0 terminator */
|
|
|
|
// File format is:
|
|
// - [GZIP_ID_LEN] GZIP_ID (no \0)
|
|
// - [sizeof(Access)] index (should be allocated, contains various sizes)
|
|
// - [rest] the indexed data points (should be allocated, index->list should then point to it)
|
|
static Access* ReadIndexFromFile(const char* filename)
|
|
{
|
|
auto fp = FileSystem::OpenManagedCFile(filename, "rb");
|
|
if (!fp)
|
|
return nullptr;
|
|
|
|
s64 size;
|
|
if ((size = FileSystem::FSize64(fp.get())) <= 0)
|
|
{
|
|
ERROR_LOG("Invalid gzip index size: {}", size);
|
|
return nullptr;
|
|
}
|
|
|
|
char fileId[GZIP_ID_LEN + 1] = {0};
|
|
if (std::fread(fileId, GZIP_ID_LEN, 1, fp.get()) != 1 || std::memcmp(fileId, GZIP_ID, 4) != 0)
|
|
{
|
|
ERROR_LOG("Incompatible gzip index: '{}'", filename);
|
|
return nullptr;
|
|
}
|
|
|
|
Access* const index = static_cast<Access*>(std::malloc(sizeof(Access)));
|
|
const s64 datasize = size - GZIP_ID_LEN - sizeof(Access);
|
|
if (std::fread(index, sizeof(Access), 1, fp.get()) != 1 ||
|
|
datasize != static_cast<s64>(index->have) * static_cast<s64>(sizeof(Point)))
|
|
{
|
|
ERROR_LOG("Unexpected size of gzip index: '{}'.", filename);
|
|
std::free(index);
|
|
return 0;
|
|
}
|
|
|
|
char* buffer = static_cast<char*>(std::malloc(datasize));
|
|
if (std::fread(buffer, datasize, 1, fp.get()) != 1)
|
|
{
|
|
ERROR_LOG("Failed read of gzip index: '{}'.");
|
|
std::free(buffer);
|
|
std::free(index);
|
|
return 0;
|
|
}
|
|
|
|
index->list = reinterpret_cast<Point*>(buffer); // adjust list pointer
|
|
return index;
|
|
}
|
|
|
|
static void WriteIndexToFile(Access* index, const char* filename)
|
|
{
|
|
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
|
if (!fp)
|
|
return;
|
|
|
|
bool success = (std::fwrite(GZIP_ID, GZIP_ID_LEN, 1, fp.get()) == 1);
|
|
|
|
Point* tmp = index->list;
|
|
index->list = 0; // current pointer is useless on disk, normalize it as 0.
|
|
std::fwrite((char*)index, sizeof(Access), 1, fp.get());
|
|
index->list = tmp;
|
|
|
|
success = success && (std::fwrite((char*)index->list, sizeof(Point) * index->have, 1, fp.get()) == 1);
|
|
|
|
// Verify
|
|
if (!success)
|
|
ERROR_LOG("Warning: Can't write index file to disk: '{}'", filename);
|
|
else
|
|
INFO_LOG("Gzip quick access index file saved to disk: '{}'", filename);
|
|
}
|
|
|
|
static const char* INDEX_TEMPLATE_KEY = "$(f)";
|
|
|
|
// template:
|
|
// must contain one and only one instance of '$(f)' (without the quotes)
|
|
// if if !canEndWithKey -> must not end with $(f)
|
|
// if starts with $(f) then it expands to the full path + file name.
|
|
// if doesn't start with $(f) then it's expanded to file name only (with extension)
|
|
// if doesn't start with $(f) and ends up relative,
|
|
// then it's relative to base (not to cwd)
|
|
// No checks are performed if the result file name can be created.
|
|
// If this proves useful, we can move it into Path:: . Right now there's no need.
|
|
static std::string ApplyTemplate(const std::string& name, const std::string& base,
|
|
const std::string& fileTemplate, const std::string& filename,
|
|
bool canEndWithKey, Error* error)
|
|
{
|
|
// both sides
|
|
std::string trimmedTemplate(StringUtil::StripWhitespace(fileTemplate));
|
|
|
|
std::string::size_type first = trimmedTemplate.find(INDEX_TEMPLATE_KEY);
|
|
if (first == std::string::npos // not found
|
|
|| first != trimmedTemplate.rfind(INDEX_TEMPLATE_KEY) // more than one instance
|
|
|| (!canEndWithKey && first == trimmedTemplate.length() - std::strlen(INDEX_TEMPLATE_KEY)))
|
|
{
|
|
Error::SetStringFmt(error, "Invalid {} template '{}'.\n"
|
|
"Template must contain exactly one '%s' and must not end with it. Aborting.",
|
|
name, trimmedTemplate, INDEX_TEMPLATE_KEY);
|
|
return {};
|
|
}
|
|
|
|
std::string fname(filename);
|
|
if (first > 0)
|
|
fname = Path::GetFileName(fname); // without path
|
|
|
|
StringUtil::ReplaceAll(&trimmedTemplate, INDEX_TEMPLATE_KEY, fname);
|
|
if (!Path::IsAbsolute(trimmedTemplate))
|
|
trimmedTemplate = Path::Combine(base, trimmedTemplate); // ignores appRoot if tem is absolute
|
|
|
|
return trimmedTemplate;
|
|
}
|
|
|
|
static std::string iso2indexname(const std::string& isoname, Error* error)
|
|
{
|
|
const std::string& appRoot = EmuFolders::DataRoot;
|
|
return ApplyTemplate("gzip index", appRoot, Host::GetBaseStringSettingValue("EmuCore", "GzipIsoIndexTemplate", "$(f).pindex.tmp"), isoname, false, error);
|
|
}
|
|
|
|
|
|
GzippedFileReader::GzippedFileReader() = default;
|
|
|
|
GzippedFileReader::~GzippedFileReader() = default;
|
|
|
|
bool GzippedFileReader::LoadOrCreateIndex(Error* error)
|
|
{
|
|
// Try to read index from disk
|
|
const std::string indexfile(iso2indexname(m_filename, error));
|
|
if (indexfile.empty())
|
|
{
|
|
// iso2indexname(...) will set errors if it can't apply the template
|
|
return false;
|
|
}
|
|
|
|
if ((m_index = ReadIndexFromFile(indexfile.c_str())) != nullptr)
|
|
{
|
|
INFO_LOG("Gzip quick access index read from disk: '{}'", indexfile);
|
|
return true;
|
|
}
|
|
|
|
// No valid index file. Generate an index
|
|
Console.Warning("This may take a while (but only once). Scanning compressed file to generate a quick access index...");
|
|
|
|
const s64 prevoffset = FileSystem::FTell64(m_src);
|
|
Access* index = nullptr;
|
|
int len = build_index(m_src, GZFILE_SPAN_DEFAULT, &index);
|
|
printf("\n"); // build_index prints progress without \n's
|
|
FileSystem::FSeek64(m_src, prevoffset, SEEK_SET);
|
|
|
|
if (len >= 0)
|
|
{
|
|
m_index = index;
|
|
WriteIndexToFile(m_index, indexfile.c_str());
|
|
}
|
|
else
|
|
{
|
|
Error::SetStringFmt(error, "ERROR ({}): Index could not be generated for file '{}'", len, m_filename);
|
|
free_index(index);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool GzippedFileReader::Open2(std::string filename, Error* error)
|
|
{
|
|
Close();
|
|
|
|
m_filename = std::move(filename);
|
|
if (!(m_src = FileSystem::OpenCFile(m_filename.c_str(), "rb", error)) || !LoadOrCreateIndex(error))
|
|
{
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void GzippedFileReader::Close2()
|
|
{
|
|
if (m_z_state.isValid)
|
|
{
|
|
inflateEnd(&m_z_state.strm);
|
|
m_z_state = {};
|
|
}
|
|
|
|
if (m_src)
|
|
{
|
|
std::fclose(m_src);
|
|
m_src = nullptr;
|
|
}
|
|
|
|
if (m_index)
|
|
{
|
|
free_index(m_index);
|
|
m_index = nullptr;
|
|
}
|
|
}
|
|
|
|
ThreadedFileReader::Chunk GzippedFileReader::ChunkForOffset(u64 offset)
|
|
{
|
|
ThreadedFileReader::Chunk chunk = {};
|
|
if (static_cast<s64>(offset) >= m_index->uncompressed_size)
|
|
{
|
|
chunk.chunkID = -1;
|
|
}
|
|
else
|
|
{
|
|
chunk.chunkID = static_cast<s64>(offset) / m_index->span;
|
|
chunk.length = m_index->span;
|
|
chunk.offset = static_cast<u64>(chunk.chunkID) * m_index->span;
|
|
}
|
|
|
|
return chunk;
|
|
}
|
|
|
|
int GzippedFileReader::ReadChunk(void* dst, s64 chunkID)
|
|
{
|
|
if (chunkID < 0)
|
|
return -1;
|
|
|
|
const s64 file_offset = chunkID * m_index->span;
|
|
const u32 read_len = static_cast<u32>(std::min<s64>(m_index->uncompressed_size - file_offset, m_index->span));
|
|
return extract(m_src, m_index, file_offset, static_cast<unsigned char*>(dst), read_len, &m_z_state);
|
|
}
|
|
|
|
u32 GzippedFileReader::GetBlockCount() const
|
|
{
|
|
return (m_index->uncompressed_size + (m_blocksize - 1)) / m_blocksize;
|
|
}
|