7#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
8#include <cryptopp/md5.h>
19#ifdef VPKPP_SUPPORT_VPK_V54
32std::string removeVPKAndOrDirSuffix(
const std::string& path,
bool isFPX) {
33 std::string filename = path;
35 filename = filename.substr(0, filename.length() - 4);
41 filename = filename.substr(0, filename.length() - 4);
47bool isFPX(
const VPK* vpk) {
48 return dynamic_cast<const FPX*
>(vpk);
53std::unique_ptr<PackFile>
VPK::create(
const std::string& path, uint32_t version) {
54 if (version != 0 && version != 1 && version != 2 && version != 54) {
59 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
70 header2.fileDataSectionSize = 0;
71 header2.archiveMD5SectionSize = 0;
72 header2.otherMD5SectionSize = 0;
73 header2.signatureSectionSize = 0;
83 std::unique_ptr<PackFile> vpk;
86 if (path.length() >= 8) {
87 auto dirPath = path.substr(0, path.length() - 8) +
"_dir.vpk";
88 auto pathEnd = path.substr(path.length() - 8, path.length());
89 if (
string::matches(pathEnd,
"_%d%d%d.vpk") && std::filesystem::exists(dirPath)) {
101 if (!std::filesystem::exists(path)) {
106 auto* vpk =
new VPK{path};
107 auto packFile = std::unique_ptr<PackFile>{vpk};
109 FileStream reader{vpk->fullFilePath};
111 reader.read(vpk->header1);
113 reader.seek_in(3, std::ios::end);
114 if (reader.read<
char>() ==
'\0' && reader.read<
char>() ==
'\0' && reader.read<
char>() ==
'\0') {
117 if (std::filesystem::file_size(vpk->fullFilePath) == 9) {
123 vpk->header1.version = 0;
124 vpk->header1.treeSize = 0;
132 if (vpk->hasExtendedHeader()) {
133 reader.read(vpk->header2);
134 }
else if (vpk->header1.version != 0 && vpk->header1.version != 1) {
141 std::string extension;
142 reader.read(extension);
143 if (extension.empty())
148 std::string directory;
149 reader.read(directory);
150 if (directory.empty())
154 if (directory ==
" ") {
162 std::string entryName;
163 reader.read(entryName);
164 if (entryName.empty())
169 std::string entryPath;
170 if (extension ==
" ") {
171 entryPath = fullDir.empty() ?
"" : fullDir +
'/';
172 entryPath += entryName;
174 entryPath = fullDir.empty() ?
"" : fullDir +
'/';
175 entryPath += entryName +
'.';
176 entryPath += extension;
178 entryPath = vpk->cleanEntryPath(entryPath);
180 reader.read(entry.
crc32);
181 auto preloadedDataSize = reader.read<uint16_t>();
183 entry.
offset = reader.read<uint32_t>();
184 entry.
length = reader.read<uint32_t>();
186 if (vpk->hasCompression()) {
195 if (preloadedDataSize > 0) {
196 entry.
extraData = reader.read_bytes(preloadedDataSize);
197 entry.
length += preloadedDataSize;
201 vpk->numArchives =
static_cast<int32_t
>(entry.
archiveIndex);
205 vpk->entries.emplace(entryPath, entry);
208 callback(entryPath, entry);
218 if (!vpk->hasExtendedHeader()) {
223 reader.seek_in(vpk->header2.fileDataSectionSize, std::ios::cur);
225 if (vpk->header2.archiveMD5SectionSize %
sizeof(
MD5Entry) != 0) {
229 vpk->md5Entries.clear();
230 unsigned int entryNum = vpk->header2.archiveMD5SectionSize /
sizeof(
MD5Entry);
231 for (
unsigned int i = 0; i < entryNum; i++) {
232 vpk->md5Entries.push_back(reader.read<
MD5Entry>());
235 if (vpk->header2.otherMD5SectionSize != 48) {
240 vpk->footer2.treeChecksum = reader.read_bytes<16>();
241 vpk->footer2.md5EntriesChecksum = reader.read_bytes<16>();
242 vpk->footer2.wholeFileChecksum = reader.read_bytes<16>();
244 if (!vpk->header2.signatureSectionSize) {
248 auto publicKeySize = reader.read<int32_t>();
249 if (vpk->header2.signatureSectionSize == 20 && publicKeySize ==
VPK_SIGNATURE) {
254 vpk->footer2.publicKey = reader.read_bytes(publicKeySize);
255 vpk->footer2.signature = reader.read_bytes(reader.read<int32_t>());
287 if (this->
footer2.wholeFileChecksum !=
crypto::computeMD5(stream.read_bytes(this->getHeaderLength() + this->header1.treeSize + this->header2.fileDataSectionSize + this->header2.archiveMD5SectionSize + this->header2.otherMD5SectionSize -
sizeof(this->footer2.wholeFileChecksum)))) {
298 if (this->
footer2.publicKey.empty() || this->footer2.signature.empty()) {
310 if (this->
footer2.publicKey.empty() || this->footer2.signature.empty()) {
314 const auto signatureSectionSize = this->
footer2.publicKey.size() + this->
footer2.signature.size() +
sizeof(uint32_t) * 2;
315 if (dirFileBuffer.size() <= signatureSectionSize) {
318 for (
int i = 0; i < signatureSectionSize; i++) {
319 dirFileBuffer.pop_back();
325std::optional<std::vector<std::byte>>
VPK::readEntry(
const std::string& path_)
const {
331 if (entry->unbaked) {
335 const auto entryLength = (this->
hasCompression() && entry->compressedLength) ? entry->compressedLength : entry->length;
336 if (entryLength == 0) {
337 return std::vector<std::byte>{};
339 std::vector out(entryLength,
static_cast<std::byte
>(0));
341 if (!entry->extraData.empty()) {
342 std::copy(entry->extraData.begin(), entry->extraData.end(), out.begin());
344 if (entryLength != entry->extraData.size()) {
351 stream.seek_in_u(entry->offset);
352 auto bytes = stream.read_bytes(entryLength - entry->extraData.size());
353 std::copy(bytes.begin(), bytes.end(), out.begin() +
static_cast<long long>(entry->extraData.size()));
361 auto bytes = stream.read_bytes(entry->length - entry->extraData.size());
362 std::copy(bytes.begin(), bytes.end(), out.begin() +
static_cast<long long>(entry->extraData.size()));
366#ifndef VPKPP_SUPPORT_VPK_V54
374 if (!decompressionDict) {
378 std::unique_ptr<ZSTD_DDict, void(*)(
void*)> dDict{
379 ZSTD_createDDict(decompressionDict->data(), decompressionDict->size()),
380 [](
void* dDict_) { ZSTD_freeDDict(
static_cast<ZSTD_DDict*
>(dDict_)); },
386 std::unique_ptr<ZSTD_DCtx, void(*)(
void*)> dCtx{
388 [](
void* dCtx_) { ZSTD_freeDCtx(
static_cast<ZSTD_DCtx*
>(dCtx_)); },
394 std::vector<std::byte> decompressedData;
395 decompressedData.resize(entry->length);
397 if (ZSTD_isError(ZSTD_decompress_usingDDict(dCtx.get(), decompressedData.data(), decompressedData.size(), out.data(), out.size(), dDict.get()))) {
400 return decompressedData;
411 entry.
length = buffer.size();
418 if (!options.
vpk_saveToDirectory && !this->freedChunks.empty() && !this->hasCompression()) {
419 int64_t bestChunkIndex = -1;
420 std::size_t currentChunkGap = SIZE_MAX;
421 for (int64_t i = 0; i < this->
freedChunks.size(); i++) {
424 (bestChunkIndex >= 0 && this->freedChunks[i].length >= entry.
length && (this->freedChunks[i].length - entry.
length) < currentChunkGap)
430 if (bestChunkIndex >= 0) {
435 if (currentChunkGap < SIZE_MAX && currentChunkGap > 0) {
444 entry.
extraData.resize(clampedPreloadBytes);
445 std::memcpy(entry.
extraData.data(), buffer.data(), clampedPreloadBytes);
446 buffer.erase(buffer.begin(), buffer.begin() + clampedPreloadBytes);
463 this->
freedChunks.push_back({entry->offset, entry->length, entry->archiveIndex});
470 if (!dirName.empty()) {
484 std::string outputPath = outputDir +
'/' + this->
getFilename();
486#ifdef VPKPP_SUPPORT_VPK_V54
488 std::optional<std::vector<std::byte>> compressionDict;
489 std::unique_ptr<ZSTD_CDict, void(*)(
void*)> cDict{
nullptr,
nullptr};
490 std::unique_ptr<ZSTD_CCtx, void(*)(
void*)> cCtx{
nullptr,
nullptr};
493 if (!compressionDict) {
499 [](
void* cDict_) { ZSTD_freeCDict(
static_cast<ZSTD_CDict*
>(cDict_)); },
507 [](
void* cCtx_) { ZSTD_freeCCtx(
static_cast<ZSTD_CCtx*
>(cCtx_)); },
516 std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::pair<std::string, Entry*>>>> temp;
518 const auto fsPath = std::filesystem::path{path};
519 auto extension = fsPath.extension().string();
520 if (extension.starts_with(
'.')) {
521 extension = extension.substr(1);
523 const auto parentDir = fsPath.parent_path().string();
525 if (extension.empty()) {
528 if (!temp.contains(extension)) {
529 temp[extension] = {};
531 if (!temp.at(extension).contains(parentDir)) {
532 temp.at(extension)[parentDir] = {};
534 temp.at(extension).at(parentDir).emplace_back(path, &entry);
538 std::vector<std::byte> dirVPKEntryData;
539 std::size_t newDirEntryOffset = 0;
549 dirVPKEntryData.reserve(dirVPKEntryData.size() + entry.
length - entry.
extraData.size());
550 dirVPKEntryData.insert(dirVPKEntryData.end(), binData->begin() +
static_cast<std::vector<std::byte>::difference_type
>(entry.
extraData.size()), binData->end());
552 entry.
offset = newDirEntryOffset;
557 const auto getArchiveFilename = [
this](
const std::string& filename_, uint32_t archiveIndex) {
564 if (!outputDir_.empty()) {
565 for (uint32_t archiveIndex = 0; archiveIndex < this->
numArchives; archiveIndex++) {
567 if (!std::filesystem::exists(from)) {
570 std::string dest = getArchiveFilename(outputDir +
'/' + this->
getTruncatedFilestem(), archiveIndex);
574 std::filesystem::copy_file(from, dest, std::filesystem::copy_options::overwrite_existing);
578 FileStream outDir{outputPath, FileStream::OPT_READ | FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
583 if (this->
header1.version > 0) {
591 for (
auto& [ext, dirs] : temp) {
594 for (
auto& [dir, tempEntries] : dirs) {
595 outDir.write(!dir.empty() ? dir :
" ");
597 for (
auto& [path, entry] : tempEntries) {
605 if (entry->
length == entry->
extraData.size() && !this->hasCompression()) {
608 entry->
offset = dirVPKEntryData.size();
611 auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(
this)), entry->
archiveIndex);
612 FileStream stream{archiveFilename, FileStream::OPT_READ | FileStream::OPT_WRITE | FileStream::OPT_CREATE_IF_NONEXISTENT};
613 stream.seek_out_u(entry->
offset);
614 stream.write(*entryData);
617 auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(
this)), entry->
archiveIndex);
618 entry->
offset = std::filesystem::exists(archiveFilename) ? std::filesystem::file_size(archiveFilename) : 0;
619 FileStream stream{archiveFilename, FileStream::OPT_APPEND | FileStream::OPT_CREATE_IF_NONEXISTENT};
620#ifndef VPKPP_SUPPORT_VPK_V54
621 stream.write(*entryData);
624 stream.write(*entryData);
626 std::vector<std::byte> compressedData;
627 compressedData.resize(ZSTD_compressBound(entryData->size()));
628 auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
629 if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
632 stream.write(std::span{compressedData.data(), compressedSize});
638 entry->
offset = dirVPKEntryData.size();
639#ifndef VPKPP_SUPPORT_VPK_V54
640 dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
643 dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
645 std::vector<std::byte> compressedData;
646 compressedData.resize(ZSTD_compressBound(entryData->size()));
647 auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
648 if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
651 dirVPKEntryData.insert(dirVPKEntryData.end(), compressedData.data(), compressedData.data() + compressedSize);
661 outDir.write(std::filesystem::path{path}.stem().
string());
662 outDir.write(entry->
crc32);
663 outDir.write<uint16_t>(entry->
extraData.size());
665 outDir.write<uint32_t>(entry->
offset);
679 callback(path, *entry);
689 if (!dirVPKEntryData.empty()) {
690 outDir.write(dirVPKEntryData);
705 const auto binData = this->
readEntry(path);
711 .offset =
static_cast<uint32_t
>(entry.
offset),
712 .length =
static_cast<uint32_t
>(entry.
length - entry.
extraData.size()),
720 this->
header2.fileDataSectionSize = dirVPKEntryData.size();
722 this->
header2.otherMD5SectionSize = 48;
723 this->
header2.signatureSectionSize = 0;
726 CryptoPP::Weak::MD5 wholeFileChecksumMD5;
729 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(&this->
header1),
sizeof(
Header1));
730 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(&this->
header2),
sizeof(
Header2));
734 std::vector<std::byte> treeData = outDir.read_bytes(this->
header1.treeSize);
735 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(treeData.data()), treeData.size());
738 if (!dirVPKEntryData.empty()) {
739 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(dirVPKEntryData.data()), dirVPKEntryData.size());
742 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->
md5Entries.data()), this->md5Entries.size() *
sizeof(
MD5Entry));
743 CryptoPP::Weak::MD5 md5EntriesChecksumMD5;
744 md5EntriesChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->
md5Entries.data()), this->md5Entries.size() *
sizeof(
MD5Entry));
745 md5EntriesChecksumMD5.Final(
reinterpret_cast<CryptoPP::byte*
>(this->
footer2.md5EntriesChecksum.data()));
747 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->
footer2.treeChecksum.data()), this->footer2.treeChecksum.size());
748 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->
footer2.md5EntriesChecksum.data()), this->footer2.md5EntriesChecksum.size());
749 wholeFileChecksumMD5.Final(
reinterpret_cast<CryptoPP::byte*
>(this->
footer2.wholeFileChecksum.data()));
752 this->
footer2.publicKey.clear();
753 this->
footer2.signature.clear();
757 if (this->
header1.version == 0) {
775 outDir.seek_out_u(
sizeof(
Header1) +
sizeof(
Header2) + this->
header1.treeSize + dirVPKEntryData.size());
777 outDir.write(this->
footer2.treeChecksum);
778 outDir.write(this->
footer2.md5EntriesChecksum);
779 outDir.write(this->
footer2.wholeFileChecksum);
790 filestem = filestem.substr(0, filestem.length() - 4);
800VPK::operator std::string()
const {
801 return PackFile::operator std::string() + std::format(
" | Version v{}", this->
header1.version);
807 auto privateKeyPath = name +
".privatekey.vdf";
808 FileStream stream{privateKeyPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
817 stream.write(output,
false);
820 auto publicKeyPath = name +
".publickey.vdf";
821 FileStream stream{publicKeyPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
830 stream.write(output,
false);
836 if (!this->
hasExtendedHeader() || !std::filesystem::exists(filename_) || std::filesystem::is_directory(filename_)) {
842 const auto privateKeyHex = fileKV[
"private_key"][
"rsa_private_key"].
getValue();
843 if (privateKeyHex.empty()) {
846 const auto publicKeyHex = fileKV[
"private_key"][
"public_key"][
"rsa_public_key"].
getValue();
847 if (publicKeyHex.empty()) {
854bool VPK::sign(
const std::vector<std::byte>& privateKey,
const std::vector<std::byte>& publicKey) {
859 this->
header2.signatureSectionSize = this->
footer2.publicKey.size() + this->
footer2.signature.size() +
sizeof(uint32_t) * 2;
861 FileStream stream{std::string{this->
getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
862 stream.seek_out(
sizeof(
Header1));
867 if (dirFileBuffer.size() <= this->header2.signatureSectionSize) {
870 for (
int i = 0; i < this->
header2.signatureSectionSize; i++) {
871 dirFileBuffer.pop_back();
873 this->
footer2.publicKey = publicKey;
877 FileStream stream{std::string{this->
getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
878 stream.seek_out(this->
getHeaderLength() + this->
header1.treeSize + this->header2.fileDataSectionSize + this->header2.archiveMD5SectionSize + this->header2.otherMD5SectionSize);
879 stream.write(
static_cast<uint32_t
>(this->
footer2.publicKey.size()));
880 stream.write(this->
footer2.publicKey);
881 stream.write(
static_cast<uint32_t
>(this->
footer2.signature.size()));
882 stream.write(this->
footer2.signature);
893 if ((version != 0 && version != 1 && version != 2 && version != 54) || ::isFPX(
this) || version == this->
header1.version) {
896 this->
header1.version = version;
917 return this->
header1.version == 54;
constexpr uint32_t VPK_FLAG_REUSING_CHUNK
Runtime-only flag that indicates a file is going to be written to an existing archive file.
std::string_view getValue() const
Get the value associated with the element.
This class represents the metadata that a file has inside a PackFile.
bool unbaked
Used to check if entry is saved to disk.
uint32_t flags
Format-specific flags (PCK: File flags, VPK: Internal parser state, ZIP: Compression method / strengt...
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 archiveIndex
Which external archive this entry is in.
uint32_t crc32
CRC32 checksum - 0 if unused.
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length).
std::vector< std::byte > extraData
Format-specific (PCK: MD5 hash, VPK: Preloaded data).
EntryCallbackBase< void > EntryCallback
virtual std::size_t removeDirectory(const std::string &dirName_)
Remove a directory.
void mergeUnbakedEntries()
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
std::vector< std::string > verifyEntryChecksumsUsingCRC32() const
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
std::string getFilestem() const
/home/user/pak01_dir.vpk -> pak01_dir
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
std::string getTruncatedFilepath() const
/home/user/pak01_dir.vpk -> /home/user/pak01
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
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.
std::string_view getFilepath() const
/home/user/pak01_dir.vpk
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
static std::unique_ptr< PackFile > create(const std::string &path, uint32_t version=2)
Create a new directory VPK file - should end in "_dir.vpk"! This is not enforced but STRONGLY recomme...
std::size_t removeDirectory(const std::string &dirName_) override
Remove a directory.
void setChunkSize(uint32_t newChunkSize)
Set the VPK chunk size in bytes (size of generated archives when baking).
uint32_t getHeaderLength() const
bool hasCompression() const
bool verifyPackFileSignature() const override
Verify the file signature, returns true on success Will return true if there is no signature ability ...
uint32_t getChunkSize() const
Get the VPK chunk size in bytes (size of generated archives when baking).
std::vector< std::string > verifyEntryChecksums() const override
Verify the checksums of each file, if a file fails the check its path will be added to the vector If ...
uint32_t getVersion() const
Returns 1 for v1, 2 for v2.
uint32_t currentlyFilledChunkSize
bool hasPackFileSignature() const override
Returns true if the file is signed.
bool hasExtendedHeader() const
static bool generateKeyPairFiles(const std::string &name)
Generate keypair files, which can be used to sign a VPK Input is a truncated file path,...
std::vector< FreedChunk > freedChunks
bool hasPackFileChecksum() const override
Returns true if the entire file has a checksum.
bool verifyPackFileChecksum() const override
Verify the checksum of the entire file, returns true on success Will return true if there is no check...
void setVersion(uint32_t version)
Change the version of the VPK. Valid values are 1 and 2.
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
bool sign(const std::string &filename_)
Sign the VPK with the given private key KeyValues file. (See below comment).
bool removeEntry(const std::string &filename_) override
Remove an entry.
std::string getTruncatedFilestem() const override
/home/user/pak01_dir.vpk -> pak01
std::vector< MD5Entry > md5Entries
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a VPK file.
static std::unique_ptr< PackFile > openInternal(const std::string &path, const EntryCallback &callback=nullptr)
std::vector< std::byte > signDataWithSHA256PrivateKey(std::span< const std::byte > buffer, std::span< const std::byte > privateKey)
std::array< std::byte, 16 > computeMD5(std::span< const std::byte > buffer)
bool verifySHA256PublicKey(std::span< const std::byte > buffer, std::span< const std::byte > publicKey, std::span< const std::byte > signature)
std::pair< std::string, std::string > computeSHA256KeyPair(uint16_t size=2048)
uint32_t computeCRC32(std::span< const std::byte > buffer)
std::vector< std::byte > decodeHexString(std::string_view hex)
std::string readFileText(const std::filesystem::path &filepath, std::size_t startOffset=0)
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
std::string padNumber(int64_t number, int width)
void normalizeSlashes(std::string &path, bool stripSlashPrefix=false, bool stripSlashSuffix=true)
bool matches(std::string_view in, std::string_view search)
A very basic regex-like pattern checker for ASCII strings.
constexpr uint32_t VPK_SIGNATURE
constexpr std::string_view VPK_DIR_SUFFIX
constexpr std::string_view VPK_KEYPAIR_PUBLIC_KEY_TEMPLATE
constexpr uint16_t VPK_ENTRY_TERM
constexpr std::string_view FPX_DIR_SUFFIX
constexpr std::string_view VPK_EXTENSION
constexpr std::string_view VPK_KEYPAIR_PRIVATE_KEY_TEMPLATE
constexpr std::string_view FPX_EXTENSION
constexpr uint16_t VPK_DIR_INDEX
constexpr uint16_t VPK_MAX_PRELOAD_BYTES
Maximum preload data size in bytes.
bool vpk_generateMD5Entries
VPK - Generate MD5 hashes for each file (VPK v2 only).
int16_t zip_compressionStrength
BSP/VPK/ZIP - Compression strength.
uint16_t vpk_preloadBytes
VPK - The amount in bytes of the file to preload. Maximum is controlled by VPK_MAX_PRELOAD_BYTES (for...
bool vpk_saveToDirectory
VPK - Save this entry to the directory VPK.