6#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
7#include <cryptopp/md5.h>
18#ifdef VPKPP_SUPPORT_VPK_V54
31std::string removeVPKAndOrDirSuffix(
const std::string& path,
bool isFPX) {
32 std::string filename = path;
34 filename = filename.substr(0, filename.length() - 4);
40 filename = filename.substr(0, filename.length() - 4);
46bool isFPX(
const VPK* vpk) {
52std::unique_ptr<PackFile>
VPK::create(
const std::string& path, uint32_t version) {
53 if (version != 0 && version != 1 && version != 2 && version != 54) {
58 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
82 std::unique_ptr<PackFile> vpk;
85 if (path.length() >= 8) {
86 auto dirPath = path.substr(0, path.length() - 8) +
"_dir.vpk";
87 auto pathEnd = path.substr(path.length() - 8, path.length());
88 if (
string::matches(pathEnd,
"_%d%d%d.vpk") && std::filesystem::exists(dirPath)) {
100 if (!std::filesystem::exists(path)) {
105 auto* vpk =
new VPK{path};
106 auto packFile = std::unique_ptr<PackFile>{vpk};
110 reader.read(vpk->header1);
112 reader.seek_in(3, std::ios::end);
113 if (reader.read<
char>() ==
'\0' && reader.read<
char>() ==
'\0' && reader.read<
char>() ==
'\0') {
116 if (std::filesystem::file_size(vpk->fullFilePath) == 9) {
122 vpk->header1.version = 0;
123 vpk->header1.treeSize = 0;
131 if (vpk->hasExtendedHeader()) {
132 reader.read(vpk->header2);
133 }
else if (vpk->header1.version != 0 && vpk->header1.version != 1) {
140 std::string extension;
141 reader.read(extension);
142 if (extension.empty())
147 std::string directory;
148 reader.read(directory);
149 if (directory.empty())
153 if (directory ==
" ") {
161 std::string entryName;
162 reader.read(entryName);
163 if (entryName.empty())
168 std::string entryPath;
169 if (extension ==
" ") {
170 entryPath = fullDir.empty() ?
"" : fullDir +
'/';
171 entryPath += entryName;
173 entryPath = fullDir.empty() ?
"" : fullDir +
'/';
174 entryPath += entryName +
'.';
175 entryPath += extension;
177 entryPath = vpk->cleanEntryPath(entryPath);
179 reader.read(entry.
crc32);
180 auto preloadedDataSize = reader.read<uint16_t>();
182 entry.
offset = reader.read<uint32_t>();
183 entry.
length = reader.read<uint32_t>();
185 if (vpk->hasCompression()) {
194 if (preloadedDataSize > 0) {
195 entry.
extraData = reader.read_bytes(preloadedDataSize);
196 entry.
length += preloadedDataSize;
200 vpk->numArchives =
static_cast<int32_t
>(entry.
archiveIndex);
204 vpk->entries.emplace(entryPath, entry);
207 callback(entryPath, entry);
217 if (!vpk->hasExtendedHeader()) {
222 reader.seek_in(vpk->header2.fileDataSectionSize, std::ios::cur);
224 if (vpk->header2.archiveMD5SectionSize %
sizeof(
MD5Entry) != 0) {
228 vpk->md5Entries.clear();
229 unsigned int entryNum = vpk->header2.archiveMD5SectionSize /
sizeof(
MD5Entry);
230 for (
unsigned int i = 0; i < entryNum; i++) {
231 vpk->md5Entries.push_back(reader.read<
MD5Entry>());
234 if (vpk->header2.otherMD5SectionSize != 48) {
239 vpk->footer2.treeChecksum = reader.read_bytes<16>();
240 vpk->footer2.md5EntriesChecksum = reader.read_bytes<16>();
241 vpk->footer2.wholeFileChecksum = reader.read_bytes<16>();
243 if (!vpk->header2.signatureSectionSize) {
247 auto publicKeySize = reader.read<int32_t>();
248 if (vpk->header2.signatureSectionSize == 20 && publicKeySize ==
VPK_SIGNATURE) {
253 vpk->footer2.publicKey = reader.read_bytes(publicKeySize);
254 vpk->footer2.signature = reader.read_bytes(reader.read<int32_t>());
286 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)))) {
314 if (dirFileBuffer.size() <= signatureSectionSize) {
317 for (
int i = 0; i < signatureSectionSize; i++) {
318 dirFileBuffer.pop_back();
324std::optional<std::vector<std::byte>>
VPK::readEntry(
const std::string& path_)
const {
330 if (entry->unbaked) {
334 const auto entryLength = (this->
hasCompression() && entry->compressedLength) ? entry->compressedLength : entry->length;
335 if (entryLength == 0) {
336 return std::vector<std::byte>{};
338 std::vector out(entryLength,
static_cast<std::byte
>(0));
340 if (!entry->extraData.empty()) {
341 std::copy(entry->extraData.begin(), entry->extraData.end(), out.begin());
343 if (entryLength != entry->extraData.size()) {
350 stream.seek_in_u(entry->offset);
351 auto bytes = stream.read_bytes(entryLength - entry->extraData.size());
352 std::copy(bytes.begin(), bytes.end(), out.begin() +
static_cast<long long>(entry->extraData.size()));
360 auto bytes = stream.read_bytes(entry->length - entry->extraData.size());
361 std::copy(bytes.begin(), bytes.end(), out.begin() +
static_cast<long long>(entry->extraData.size()));
365#ifndef VPKPP_SUPPORT_VPK_V54
373 if (!decompressionDict) {
377 std::unique_ptr<ZSTD_DDict, void(*)(
void*)> dDict{
378 ZSTD_createDDict(decompressionDict->data(), decompressionDict->size()),
379 [](
void* dDict_) { ZSTD_freeDDict(
static_cast<ZSTD_DDict*
>(dDict_)); },
385 std::unique_ptr<ZSTD_DCtx, void(*)(
void*)> dCtx{
387 [](
void* dCtx_) { ZSTD_freeDCtx(
static_cast<ZSTD_DCtx*
>(dCtx_)); },
393 std::vector<std::byte> decompressedData;
394 decompressedData.resize(entry->length);
396 if (ZSTD_isError(ZSTD_decompress_usingDDict(dCtx.get(), decompressedData.data(), decompressedData.size(), out.data(), out.size(), dDict.get()))) {
399 return decompressedData;
410 entry.
length = buffer.size();
417 if (!options.
vpk_saveToDirectory && !this->freedChunks.empty() && !this->hasCompression()) {
418 int64_t bestChunkIndex = -1;
419 std::size_t currentChunkGap = SIZE_MAX;
420 for (int64_t i = 0; i < this->
freedChunks.size(); i++) {
423 (bestChunkIndex >= 0 && this->freedChunks[i].length >= entry.
length && (this->freedChunks[i].length - entry.
length) < currentChunkGap)
429 if (bestChunkIndex >= 0) {
434 if (currentChunkGap < SIZE_MAX && currentChunkGap > 0) {
443 entry.
extraData.resize(clampedPreloadBytes);
444 std::memcpy(entry.
extraData.data(), buffer.data(), clampedPreloadBytes);
445 buffer.erase(buffer.begin(), buffer.begin() + clampedPreloadBytes);
462 this->
freedChunks.push_back({entry->offset, entry->length, entry->archiveIndex});
469 if (!dirName.empty()) {
474 this->freedChunks.push_back({entry.offset, entry.length, entry.archiveIndex});
483 std::string outputPath = outputDir +
'/' + this->
getFilename();
485#ifdef VPKPP_SUPPORT_VPK_V54
487 std::optional<std::vector<std::byte>> compressionDict;
488 std::unique_ptr<ZSTD_CDict, void(*)(
void*)> cDict{
nullptr,
nullptr};
489 std::unique_ptr<ZSTD_CCtx, void(*)(
void*)> cCtx{
nullptr,
nullptr};
492 if (!compressionDict) {
498 [](
void* cDict_) { ZSTD_freeCDict(
static_cast<ZSTD_CDict*
>(cDict_)); },
506 [](
void* cCtx_) { ZSTD_freeCCtx(
static_cast<ZSTD_CCtx*
>(cCtx_)); },
515 std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::pair<std::string, Entry*>>>> temp;
517 const auto fsPath = std::filesystem::path{path};
518 auto extension = fsPath.extension().
string();
519 if (extension.starts_with(
'.')) {
520 extension = extension.substr(1);
522 const auto parentDir = fsPath.parent_path().string();
524 if (extension.empty()) {
527 if (!temp.contains(extension)) {
528 temp[extension] = {};
530 if (!temp.at(extension).contains(parentDir)) {
531 temp.at(extension)[parentDir] = {};
533 temp.at(extension).at(parentDir).emplace_back(path, &entry);
537 std::vector<std::byte> dirVPKEntryData;
538 std::size_t newDirEntryOffset = 0;
539 this->runForAllEntriesInternal([
this, &dirVPKEntryData, &newDirEntryOffset](
const std::string& path,
Entry& entry) {
544 auto binData = this->readEntry(path);
548 dirVPKEntryData.reserve(dirVPKEntryData.size() + entry.
length - entry.
extraData.size());
549 dirVPKEntryData.insert(dirVPKEntryData.end(), binData->begin() +
static_cast<std::vector<std::byte>::difference_type
>(entry.
extraData.size()), binData->end());
551 entry.
offset = newDirEntryOffset;
556 const auto getArchiveFilename = [
this](
const std::string& filename_, uint32_t archiveIndex) {
563 if (!outputDir_.empty()) {
564 for (uint32_t archiveIndex = 0; archiveIndex < this->numArchives; archiveIndex++) {
565 std::string from = getArchiveFilename(this->getTruncatedFilepath(), archiveIndex);
566 if (!std::filesystem::exists(from)) {
569 std::string dest = getArchiveFilename(outputDir +
'/' + this->getTruncatedFilestem(), archiveIndex);
573 std::filesystem::copy_file(from, dest, std::filesystem::copy_options::overwrite_existing);
577 FileStream outDir{outputPath, FileStream::OPT_READ | FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
582 if (this->header1.version > 0) {
583 outDir.write(this->header1);
584 if (this->hasExtendedHeader()) {
585 outDir.write(this->header2);
590 for (
auto& [ext, dirs] : temp) {
593 for (
auto& [dir, tempEntries] : dirs) {
594 outDir.write(!dir.empty() ? dir :
" ");
596 for (
auto& [path, entry] : tempEntries) {
599 auto entryData = readUnbakedEntry(*entry);
604 if (entry->
length == entry->
extraData.size() && !this->hasCompression()) {
607 entry->
offset = dirVPKEntryData.size();
610 auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(
this)), entry->
archiveIndex);
611 FileStream stream{archiveFilename, FileStream::OPT_READ | FileStream::OPT_WRITE | FileStream::OPT_CREATE_IF_NONEXISTENT};
612 stream.seek_out_u(entry->
offset);
613 stream.write(*entryData);
616 auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(
this)), entry->
archiveIndex);
617 entry->
offset = std::filesystem::exists(archiveFilename) ? std::filesystem::file_size(archiveFilename) : 0;
618 FileStream stream{archiveFilename, FileStream::OPT_APPEND | FileStream::OPT_CREATE_IF_NONEXISTENT};
619#ifndef VPKPP_SUPPORT_VPK_V54
620 stream.write(*entryData);
622 if (!this->hasCompression() || path == this->getTruncatedFilestem() +
".dict") {
623 stream.write(*entryData);
625 std::vector<std::byte> compressedData;
626 compressedData.resize(ZSTD_compressBound(entryData->size()));
627 auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
628 if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
631 stream.write(std::span{compressedData.data(), compressedSize});
637 entry->
offset = dirVPKEntryData.size();
638#ifndef VPKPP_SUPPORT_VPK_V54
639 dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
641 if (!this->hasCompression() || path == this->getTruncatedFilestem() +
".dict") {
642 dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
644 std::vector<std::byte> compressedData;
645 compressedData.resize(ZSTD_compressBound(entryData->size()));
646 auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
647 if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
650 dirVPKEntryData.insert(dirVPKEntryData.end(), compressedData.data(), compressedData.data() + compressedSize);
660 outDir.write(std::filesystem::path{path}.stem().
string());
661 outDir.write(entry->
crc32);
662 outDir.write<uint16_t>(entry->
extraData.size());
664 outDir.write<uint32_t>(entry->
offset);
667 if (this->hasCompression()) {
678 callback(path, *entry);
688 if (!dirVPKEntryData.empty()) {
689 outDir.write(dirVPKEntryData);
693 this->mergeUnbakedEntries();
696 this->header1.treeSize = outDir.tell_out() - dirVPKEntryData.size() - this->getHeaderLength();
699 if (this->hasExtendedHeader()) {
701 this->md5Entries.clear();
702 if (options.vpk_generateMD5Entries) {
703 this->runForAllEntries([
this](
const std::string& path,
const Entry& entry) {
704 const auto binData = this->readEntry(path);
708 const MD5Entry md5Entry{
710 .offset =
static_cast<uint32_t
>(entry.
offset),
711 .length =
static_cast<uint32_t
>(entry.
length - entry.
extraData.size()),
714 this->md5Entries.push_back(md5Entry);
719 this->header2.fileDataSectionSize = dirVPKEntryData.size();
720 this->header2.archiveMD5SectionSize = this->md5Entries.size() *
sizeof(MD5Entry);
721 this->header2.otherMD5SectionSize = 48;
722 this->header2.signatureSectionSize = 0;
725 CryptoPP::Weak::MD5 wholeFileChecksumMD5;
728 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(&this->header1),
sizeof(Header1));
729 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(&this->header2),
sizeof(Header2));
732 outDir.seek_in(
sizeof(Header1) +
sizeof(Header2));
733 std::vector<std::byte> treeData = outDir.read_bytes(this->header1.treeSize);
734 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(treeData.data()), treeData.size());
737 if (!dirVPKEntryData.empty()) {
738 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(dirVPKEntryData.data()), dirVPKEntryData.size());
741 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->md5Entries.data()), this->md5Entries.size() *
sizeof(MD5Entry));
742 CryptoPP::Weak::MD5 md5EntriesChecksumMD5;
743 md5EntriesChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->md5Entries.data()), this->md5Entries.size() *
sizeof(MD5Entry));
744 md5EntriesChecksumMD5.Final(
reinterpret_cast<CryptoPP::byte*
>(this->footer2.md5EntriesChecksum.data()));
746 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->footer2.treeChecksum.data()), this->footer2.treeChecksum.size());
747 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->footer2.md5EntriesChecksum.data()), this->footer2.md5EntriesChecksum.size());
748 wholeFileChecksumMD5.Final(
reinterpret_cast<CryptoPP::byte*
>(this->footer2.wholeFileChecksum.data()));
751 this->footer2.publicKey.clear();
752 this->footer2.signature.clear();
756 if (this->header1.version == 0) {
763 outDir.write(this->header1);
766 if (!this->hasExtendedHeader()) {
771 outDir.write(this->header2);
774 outDir.seek_out_u(
sizeof(Header1) +
sizeof(Header2) + this->header1.treeSize + dirVPKEntryData.size());
775 outDir.write(this->md5Entries);
776 outDir.write(this->footer2.treeChecksum);
777 outDir.write(this->footer2.md5EntriesChecksum);
778 outDir.write(this->footer2.wholeFileChecksum);
789 filestem = filestem.substr(0, filestem.length() - 4);
799VPK::operator std::string()
const {
800 return PackFile::operator std::string() +
801 " | Version v" + std::to_string(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) {
861 FileStream stream{std::string{this->
getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
862 stream.seek_out(
sizeof(
Header1));
871 dirFileBuffer.pop_back();
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);
893 if ((version != 0 && version != 1 && version != 2 && version != 54) || ::isFPX(
this) || version == this->
header1.
version) {
constexpr uint32_t VPK_FLAG_REUSING_CHUNK
Runtime-only flag that indicates a file is going to be written to an existing archive file.
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.
bool isInstanceOf() const
Check if the pack file is an instance of the given pack file class.
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
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
bool bake(const std::string &outputDir_, BakeOptions options, const EntryCallback &callback) override
If output folder is an empty string, it will overwrite the original.
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::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
std::string readFileText(const std::string &filepath, std::size_t startOffset=0)
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.
std::string padNumber(int64_t number, int width, char pad='0')
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.
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.