11#include <BufferStream.h>
12#include <FileStream.h>
23std::unique_ptr<PackFile>
FGP::create(
const std::string& path) {
25 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
37 if (!std::filesystem::exists(path)) {
42 auto* fgp =
new FGP{path};
43 auto packFile = std::unique_ptr<PackFile>(fgp);
45 FileStream reader{fgp->fullFilePath};
53 reader.set_big_endian(
true);
55 reader >> fgp->version;
56 if (fgp->version != 2 && fgp->version != 3) {
60 const auto fileCount = reader.read<uint32_t>();
63 const auto loadingScreen = reader.read<uint32_t>();
66 std::unordered_map<uint32_t, std::string> crackedHashes;
67 if (fgp->version == 3) {
68 const auto directoryPos = reader.tell_in();
70 reader.seek_in_u(reader.seek_in(
sizeof(uint64_t) * 2, std::ios::end).read<uint64_t>());
72 const auto filepathCount = reader.read<uint32_t>();
73 for (
int i = 0; i < filepathCount; i++) {
74 const auto hash = reader.read<uint32_t>();
75 crackedHashes[hash] = fgp->cleanEntryPath(reader.read_string(reader.read<uint16_t>()));
79 reader.seek_in_u(directoryPos);
82 for (uint32_t i = 0; i < fileCount; i++) {
85 std::string entryPath;
86 if (fgp->version == 2) {
87 entryPath = fgp->cleanEntryPath(reader.read_string(260));
93 entry.
crc32 = reader.read<uint32_t>();
94 if (crackedHashes.contains(entry.
crc32)) {
95 entryPath = crackedHashes[entry.
crc32];
99 if (loadingScreen > 0 && i == loadingScreen) {
100 fgp->loadingScreenPath = entryPath;
104 entry.
offset = reader.read<uint32_t>() + headerSize;
105 entry.
length = reader.read<uint32_t>();
108 fgp->entries.emplace(entryPath, entry);
111 callback(entryPath, entry);
118std::optional<std::vector<std::byte>>
FGP::readEntry(
const std::string& path_)
const {
120 const auto entry = this->
findEntry(path);
124 if (entry->unbaked) {
130 stream.seek_in_u(entry->offset);
131 if (entry->compressedLength == 0) {
132 return stream.read_bytes(entry->length);
136 const auto compressedData = stream.read_bytes(entry->compressedLength);
137 std::vector<std::byte> data(entry->length);
138 mz_ulong len = entry->length;
139 if (mz_uncompress(
reinterpret_cast<unsigned char*
>(data.data()), &len,
reinterpret_cast<const unsigned char*
>(compressedData.data()), entry->compressedLength) != MZ_OK) {
188 entry.
length = buffer.size();
198 const std::string outputPath = outputDir +
'/' + this->
getFilename();
201 std::vector<std::pair<std::string, Entry*>> entriesToBake;
203 entriesToBake.emplace_back(path, &entry);
208 std::vector<std::byte> fileData;
211 for (
const auto& [path, entry] : entriesToBake) {
213 stream.seek_in_u(entry->
offset);
215 entry->
offset = headerSize + fileData.size();
216 fileData.insert(fileData.end(), binData.begin(), binData.end());
217 }
else if (
const auto binData = this->
readEntry(path)) {
218 entry->
offset = headerSize + fileData.size();
219 entry->
length = binData->size();
223 fileData.insert(fileData.end(), binData->begin(), binData->end());
225 mz_ulong compressedSize = mz_compressBound(binData->size());
226 std::vector<std::byte> out(compressedSize);
229 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) {
231 out.resize(compressedSize);
234 if (status != MZ_OK) {
235 fileData.insert(fileData.end(), binData->begin(), binData->end());
238 out.resize(compressedSize);
239 fileData.insert(fileData.end(), out.begin(), out.end());
251 FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
255 stream.set_big_endian(
true);
258 .write<uint32_t>(this->
version)
259 .write<uint32_t>(entriesToBake.size());
261 const auto loadingScreenPos = stream.tell_out();
262 stream.write<uint32_t>(0);
266 for (
const auto& [path, entry] : entriesToBake) {
268 const auto curPos = stream.tell_out();
269 stream.seek_out_u(loadingScreenPos).write<uint32_t>(i).seek_out_u(curPos);
273 stream.write(path,
false, 260);
275 stream.write<uint32_t>(entry->
crc32);
279 .write<uint32_t>(entry->
offset - headerSize)
280 .write<uint32_t>(entry->
length)
284 callback(path, *entry);
291 stream.write(fileData);
295 const auto filenameMappingPos = stream.tell_out();
299 const auto filepathCountPos = stream.tell_out();
300 stream.write<uint32_t>(0);
301 uint32_t filepathCount = 0;
302 for (
const auto& [path, entry] : entriesToBake) {
307 .write<uint32_t>(entry->
crc32)
308 .write<uint16_t>(path.size())
313 .write<uint64_t>(filenameMappingPos)
315 .seek_out_u(filepathCountPos)
316 .write<uint32_t>(filepathCount);
331FGP::operator std::string()
const {
332 return PackFile::operator std::string() + std::format(
" | Version v{}", 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)
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.
bool bake()
If output folder is an empty string, it will overwrite the original.
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.