10#include <BufferStream.h>
11#include <FileStream.h>
22std::unique_ptr<PackFile>
FGP::create(
const std::string& path) {
24 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
36 if (!std::filesystem::exists(path)) {
41 auto* fgp =
new FGP{path};
42 auto packFile = std::unique_ptr<PackFile>(fgp);
44 FileStream reader{fgp->fullFilePath};
52 reader.set_big_endian(
true);
54 reader >> fgp->version;
55 if (fgp->version != 2 && fgp->version != 3) {
59 const auto fileCount = reader.read<uint32_t>();
62 const auto loadingScreen = reader.read<uint32_t>();
65 std::unordered_map<uint32_t, std::string> crackedHashes;
66 if (fgp->version == 3) {
67 const auto directoryPos = reader.tell_in();
69 reader.seek_in_u(reader.seek_in(
sizeof(uint64_t) * 2, std::ios::end).read<uint64_t>());
71 const auto filepathCount = reader.read<uint32_t>();
72 for (
int i = 0; i < filepathCount; i++) {
73 const auto hash = reader.read<uint32_t>();
74 crackedHashes[hash] = fgp->cleanEntryPath(reader.read_string(reader.read<uint16_t>()));
78 reader.seek_in_u(directoryPos);
81 for (uint32_t i = 0; i < fileCount; i++) {
84 std::string entryPath;
85 if (fgp->version == 2) {
86 entryPath = fgp->cleanEntryPath(reader.read_string(260));
92 entry.
crc32 = reader.read<uint32_t>();
93 if (crackedHashes.contains(entry.
crc32)) {
94 entryPath = crackedHashes[entry.
crc32];
98 if (loadingScreen > 0 && i == loadingScreen) {
99 fgp->loadingScreenPath = entryPath;
103 entry.
offset = reader.read<uint32_t>() + headerSize;
104 entry.
length = reader.read<uint32_t>();
107 fgp->entries.emplace(entryPath, entry);
110 callback(entryPath, entry);
117std::optional<std::vector<std::byte>>
FGP::readEntry(
const std::string& path_)
const {
119 const auto entry = this->
findEntry(path);
123 if (entry->unbaked) {
129 stream.seek_in_u(entry->offset);
130 if (entry->compressedLength == 0) {
131 return stream.read_bytes(entry->length);
135 const auto compressedData = stream.read_bytes(entry->compressedLength);
136 std::vector<std::byte> data(entry->length);
137 mz_ulong len = entry->length;
138 if (mz_uncompress(
reinterpret_cast<unsigned char*
>(data.data()), &len,
reinterpret_cast<const unsigned char*
>(compressedData.data()), entry->compressedLength) != MZ_OK) {
187 entry.
length = buffer.size();
197 const std::string outputPath = outputDir +
'/' + this->
getFilename();
200 std::vector<std::pair<std::string, Entry*>> entriesToBake;
202 entriesToBake.emplace_back(path, &entry);
207 std::vector<std::byte> fileData;
210 for (
const auto& [path, entry] : entriesToBake) {
212 stream.seek_in_u(entry->
offset);
214 entry->
offset = headerSize + fileData.size();
215 fileData.insert(fileData.end(), binData.begin(), binData.end());
216 }
else if (
const auto binData = this->
readEntry(path)) {
217 entry->
offset = headerSize + fileData.size();
218 entry->
length = binData->size();
222 fileData.insert(fileData.end(), binData->begin(), binData->end());
224 mz_ulong compressedSize = mz_compressBound(binData->size());
225 std::vector<std::byte> out(compressedSize);
228 while ((status = mz_compress2(
reinterpret_cast<unsigned char*
>(out.data()), &compressedSize,
reinterpret_cast<const unsigned char*
>(binData->data()), binData->size(), options.
zip_compressionStrength)) == MZ_BUF_ERROR) {
230 out.resize(compressedSize);
233 if (status != MZ_OK) {
234 fileData.insert(fileData.end(), binData->begin(), binData->end());
237 out.resize(compressedSize);
238 fileData.insert(fileData.end(), out.begin(), out.end());
250 FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
254 stream.set_big_endian(
true);
257 .write<uint32_t>(this->
version)
258 .write<uint32_t>(entriesToBake.size());
260 const auto loadingScreenPos = stream.tell_out();
261 stream.write<uint32_t>(0);
265 for (
const auto& [path, entry] : entriesToBake) {
267 const auto curPos = stream.tell_out();
268 stream.seek_out_u(loadingScreenPos).write<uint32_t>(i).seek_out_u(curPos);
272 stream.write(path,
false, 260);
274 stream.write<uint32_t>(entry->
crc32);
278 .write<uint32_t>(entry->
offset - headerSize)
279 .write<uint32_t>(entry->
length)
283 callback(path, *entry);
290 stream.write(fileData);
294 const auto filenameMappingPos = stream.tell_out();
298 const auto filepathCountPos = stream.tell_out();
299 stream.write<uint32_t>(0);
300 uint32_t filepathCount = 0;
301 for (
const auto& [path, entry] : entriesToBake) {
306 .write<uint32_t>(entry->
crc32)
307 .write<uint16_t>(path.size())
312 .write<uint64_t>(filenameMappingPos)
314 .seek_out_u(filepathCountPos)
315 .write<uint32_t>(filepathCount);
330FGP::operator std::string()
const {
331 return PackFile::operator std::string() +
332 " | Version v" + std::to_string(this->
version);
346 return std::accumulate(filepath.begin(), filepath.end(), 0xAAAAAAAAu, [](uint32_t hash,
char c) { return (hash << 5) + hash + static_cast<uint8_t>(tolower(c)); });
350 return ((
version == 2 ? 260 :
sizeof(uint32_t)) +
sizeof(uint32_t) * 3) * fileCount +
sizeof(uint32_t) * 4;
constexpr std::string_view FGP_V2_STRIP_PATH_PREFIX
This class represents the metadata that a file has inside a PackFile.
bool unbaked
Used to check if entry is saved to disk.
uint64_t offset
Offset, format-specific meaning - 0 if unused, or if the offset genuinely is 0.
uint64_t compressedLength
If the format supports compression, this is the compressed length.
uint32_t crc32
CRC32 checksum - 0 if unused.
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length)
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
void setLoadingScreenFilePath(const std::string &path)
static std::unique_ptr< PackFile > create(const std::string &path)
Create an FGP file.
std::string loadingScreenPath
bool renameEntry(const std::string &oldPath, const std::string &newPath) override
Rename an existing entry.
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
bool renameDirectory(const std::string &oldDir, const std::string &newDir) override
Rename an existing directory.
std::size_t removeDirectory(const std::string &dirName) override
Remove a directory.
std::string getLoadingScreenFilePath() const
static uint32_t hashFilePath(const std::string &filepath)
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
bool removeEntry(const std::string &path) override
Remove an entry.
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open an FGP file.
static uint32_t getHeaderSize(uint32_t version, uint32_t fileCount)
bool bake(const std::string &outputDir_, BakeOptions options, const EntryCallback &callback) override
If output folder is an empty string, it will overwrite the original.
EntryCallbackBase< void > EntryCallback
virtual std::size_t removeDirectory(const std::string &dirName_)
Remove a directory.
virtual bool renameDirectory(const std::string &oldDir_, const std::string &newDir_)
Rename an existing directory.
void mergeUnbakedEntries()
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
bool hasEntry(const std::string &path, bool includeUnbaked=true) const
Check if an entry exists given the file path.
virtual bool renameEntry(const std::string &oldPath_, const std::string &newPath_)
Rename an existing entry.
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
std::string getBakeOutputDir(const std::string &outputDir) const
void setFullFilePath(const std::string &outputDir)
std::string cleanEntryPath(const std::string &path) const
static Entry createNewEntry()
virtual bool removeEntry(const std::string &path_)
Remove an entry.
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
std::string encodeHexString(std::span< const std::byte > hex)
constexpr std::string_view FGP_HASHED_FILEPATH_PREFIX
constexpr auto FGP_SOURCEPP_FILENAMES_SIGNATURE
constexpr auto FGP_SIGNATURE
int16_t zip_compressionStrength
BSP/VPK/ZIP - Compression strength.