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 auto fsPath = std::filesystem::path{path};
519 const auto ext = fsPath.extension().string();
520 while (!fsPath.extension().string().empty()) {
521 fsPath.replace_extension();
523 fsPath.replace_extension(ext);
524 path = this->cleanEntryPath(fsPath.string());
527 auto extension = fsPath.extension().
string();
528 if (extension.starts_with(
'.')) {
529 extension = extension.substr(1);
531 const auto parentDir = fsPath.parent_path().string();
533 if (extension.empty()) {
536 if (!temp.contains(extension)) {
537 temp[extension] = {};
539 if (!temp.at(extension).contains(parentDir)) {
540 temp.at(extension)[parentDir] = {};
543 auto& files = temp.at(extension).at(parentDir);
544 if (options.vpk_useBuggyExtensionHandling && std::find_if(files.begin(), files.end(), [&path](
const std::pair<std::string, Entry*>& pair) {
545 return pair.first == path;
549 files.emplace_back(path, &entry);
553 std::vector<std::byte> dirVPKEntryData;
554 std::size_t newDirEntryOffset = 0;
555 this->runForAllEntriesInternal([
this, &dirVPKEntryData, &newDirEntryOffset](
const std::string& path,
Entry& entry) {
560 auto binData = this->readEntry(path);
564 dirVPKEntryData.reserve(dirVPKEntryData.size() + entry.
length - entry.
extraData.size());
565 dirVPKEntryData.insert(dirVPKEntryData.end(), binData->begin() +
static_cast<std::vector<std::byte>::difference_type
>(entry.
extraData.size()), binData->end());
567 entry.
offset = newDirEntryOffset;
572 const auto getArchiveFilename = [
this](
const std::string& filename_, uint32_t archiveIndex) {
579 if (!outputDir_.empty()) {
580 for (uint32_t archiveIndex = 0; archiveIndex < this->numArchives; archiveIndex++) {
581 std::string from = getArchiveFilename(this->getTruncatedFilepath(), archiveIndex);
582 if (!std::filesystem::exists(from)) {
585 std::string dest = getArchiveFilename(outputDir +
'/' + this->getTruncatedFilestem(), archiveIndex);
589 std::filesystem::copy_file(from, dest, std::filesystem::copy_options::overwrite_existing);
593 FileStream outDir{outputPath, FileStream::OPT_READ | FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
598 if (this->header1.version > 0) {
599 outDir.write(this->header1);
600 if (this->hasExtendedHeader()) {
601 outDir.write(this->header2);
606 for (
auto& [ext, dirs] : temp) {
609 for (
auto& [dir, tempEntries] : dirs) {
610 outDir.write(!dir.empty() ? dir :
" ");
612 for (
auto& [path, entry] : tempEntries) {
615 auto entryData = readUnbakedEntry(*entry);
620 if (entry->
length == entry->
extraData.size() && !this->hasCompression()) {
623 entry->
offset = dirVPKEntryData.size();
626 auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(
this)), entry->
archiveIndex);
627 FileStream stream{archiveFilename, FileStream::OPT_READ | FileStream::OPT_WRITE | FileStream::OPT_CREATE_IF_NONEXISTENT};
628 stream.seek_out_u(entry->
offset);
629 stream.write(*entryData);
632 auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(
this)), entry->
archiveIndex);
633 entry->
offset = std::filesystem::exists(archiveFilename) ? std::filesystem::file_size(archiveFilename) : 0;
634 FileStream stream{archiveFilename, FileStream::OPT_APPEND | FileStream::OPT_CREATE_IF_NONEXISTENT};
635#ifndef VPKPP_SUPPORT_VPK_V54
636 stream.write(*entryData);
638 if (!this->hasCompression() || path == this->getTruncatedFilestem() +
".dict") {
639 stream.write(*entryData);
641 std::vector<std::byte> compressedData;
642 compressedData.resize(ZSTD_compressBound(entryData->size()));
643 auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
644 if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
647 stream.write(std::span{compressedData.data(), compressedSize});
653 entry->
offset = dirVPKEntryData.size();
654#ifndef VPKPP_SUPPORT_VPK_V54
655 dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
657 if (!this->hasCompression() || path == this->getTruncatedFilestem() +
".dict") {
658 dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
660 std::vector<std::byte> compressedData;
661 compressedData.resize(ZSTD_compressBound(entryData->size()));
662 auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
663 if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
666 dirVPKEntryData.insert(dirVPKEntryData.end(), compressedData.data(), compressedData.data() + compressedSize);
676 outDir.write(std::filesystem::path{path}.stem().
string());
677 outDir.write(entry->
crc32);
678 outDir.write<uint16_t>(entry->
extraData.size());
680 outDir.write<uint32_t>(entry->
offset);
683 if (this->hasCompression()) {
694 callback(path, *entry);
704 if (!dirVPKEntryData.empty()) {
705 outDir.write(dirVPKEntryData);
709 this->mergeUnbakedEntries();
712 this->header1.treeSize = outDir.tell_out() - dirVPKEntryData.size() - this->getHeaderLength();
715 if (this->hasExtendedHeader()) {
717 this->md5Entries.clear();
718 if (options.vpk_generateMD5Entries) {
719 this->runForAllEntries([
this](
const std::string& path,
const Entry& entry) {
720 const auto binData = this->readEntry(path);
724 const MD5Entry md5Entry{
726 .offset =
static_cast<uint32_t
>(entry.
offset),
727 .length =
static_cast<uint32_t
>(entry.
length - entry.
extraData.size()),
730 this->md5Entries.push_back(md5Entry);
735 this->header2.fileDataSectionSize = dirVPKEntryData.size();
736 this->header2.archiveMD5SectionSize = this->md5Entries.size() *
sizeof(MD5Entry);
737 this->header2.otherMD5SectionSize = 48;
738 this->header2.signatureSectionSize = 0;
741 CryptoPP::Weak::MD5 wholeFileChecksumMD5;
744 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(&this->header1),
sizeof(Header1));
745 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(&this->header2),
sizeof(Header2));
748 outDir.seek_in(
sizeof(Header1) +
sizeof(Header2));
749 std::vector<std::byte> treeData = outDir.read_bytes(this->header1.treeSize);
750 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(treeData.data()), treeData.size());
753 if (!dirVPKEntryData.empty()) {
754 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(dirVPKEntryData.data()), dirVPKEntryData.size());
757 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->md5Entries.data()), this->md5Entries.size() *
sizeof(MD5Entry));
758 CryptoPP::Weak::MD5 md5EntriesChecksumMD5;
759 md5EntriesChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->md5Entries.data()), this->md5Entries.size() *
sizeof(MD5Entry));
760 md5EntriesChecksumMD5.Final(
reinterpret_cast<CryptoPP::byte*
>(this->footer2.md5EntriesChecksum.data()));
762 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->footer2.treeChecksum.data()), this->footer2.treeChecksum.size());
763 wholeFileChecksumMD5.Update(
reinterpret_cast<const CryptoPP::byte*
>(this->footer2.md5EntriesChecksum.data()), this->footer2.md5EntriesChecksum.size());
764 wholeFileChecksumMD5.Final(
reinterpret_cast<CryptoPP::byte*
>(this->footer2.wholeFileChecksum.data()));
767 this->footer2.publicKey.clear();
768 this->footer2.signature.clear();
772 if (this->header1.version == 0) {
779 outDir.write(this->header1);
782 if (!this->hasExtendedHeader()) {
787 outDir.write(this->header2);
790 outDir.seek_out_u(
sizeof(Header1) +
sizeof(Header2) + this->header1.treeSize + dirVPKEntryData.size());
791 outDir.write(this->md5Entries);
792 outDir.write(this->footer2.treeChecksum);
793 outDir.write(this->footer2.md5EntriesChecksum);
794 outDir.write(this->footer2.wholeFileChecksum);
805 filestem = filestem.substr(0, filestem.length() - 4);
815VPK::operator std::string()
const {
816 return PackFile::operator std::string() +
817 " | Version v" + std::to_string(this->header1.version);
823 auto privateKeyPath = name +
".privatekey.vdf";
824 FileStream stream{privateKeyPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
833 stream.write(output,
false);
836 auto publicKeyPath = name +
".publickey.vdf";
837 FileStream stream{publicKeyPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
846 stream.write(output,
false);
852 if (!this->
hasExtendedHeader() || !std::filesystem::exists(filename_) || std::filesystem::is_directory(filename_)) {
858 const auto privateKeyHex = fileKV[
"private_key"][
"rsa_private_key"].getValue();
859 if (privateKeyHex.empty()) {
862 const auto publicKeyHex = fileKV[
"private_key"][
"public_key"][
"rsa_public_key"].getValue();
863 if (publicKeyHex.empty()) {
870bool VPK::sign(
const std::vector<std::byte>& privateKey,
const std::vector<std::byte>& publicKey) {
877 FileStream stream{std::string{this->
getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
878 stream.seek_out(
sizeof(
Header1));
887 dirFileBuffer.pop_back();
893 FileStream stream{std::string{this->
getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
894 stream.seek_out(this->
getHeaderLength() + this->
header1.
treeSize + this->header2.fileDataSectionSize + this->header2.archiveMD5SectionSize + this->header2.otherMD5SectionSize);
909 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.
bool vpk_useBuggyExtensionHandling
VPK - Enable the stupid bug in ASw/Portal 2 to fix model VVDs not loading.
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.