diff --git a/.gitmodules b/.gitmodules
index 1ec7d94fc..5bf470970 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -16,3 +16,6 @@
[submodule "ext/hat-trie"]
path = ext/hat-trie
url = https://github.com/Tessil/hat-trie
+[submodule "ext/zstd"]
+ path = ext/zstd
+ url = https://github.com/facebook/zstd
diff --git a/README.md b/README.md
index c9297f996..b7ee68604 100644
--- a/README.md
+++ b/README.md
@@ -185,7 +185,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
- | VPK v1-2 |
+
+ VPK v1-2, v54
+ • Counter-Strike: 2 modifications
+ • Counter-Strike: Source ClientMod modifications
+ |
✅ |
✅ |
diff --git a/docs/index.md b/docs/index.md
index 97879cccf..d6477eeb5 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -162,7 +162,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
✅ |
- | VPK v1-2 |
+
+ VPK v1-2, v54
+ • Counter-Strike: 2 modifications
+ • Counter-Strike: Source ClientMod modifications
+ |
✅ |
✅ |
diff --git a/ext/_ext.cmake b/ext/_ext.cmake
index 9f9d9dc3c..62533d539 100644
--- a/ext/_ext.cmake
+++ b/ext/_ext.cmake
@@ -37,19 +37,27 @@ if(NOT TARGET miniz)
endif()
+# zstd
+if(NOT TARGET zstd::libzstd)
+ set(ZSTD_BUILD_PROGRAMS OFF CACHE INTERNAL "" FORCE)
+ set(ZSTD_BUILD_TESTS OFF CACHE INTERNAL "" FORCE)
+ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/zstd/build/cmake")
+ # find_package hacks
+ set(ZSTD_FOUND ON CACHE INTERNAL "" FORCE)
+ set(ZSTD_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/ext/zstd/lib" CACHE INTERNAL "" FORCE)
+ set(ZSTD_LIBRARY_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/ext/zstd/lib" CACHE INTERNAL "" FORCE)
+ set(ZSTD_LIBRARIES "libzstd_static" CACHE INTERNAL "" FORCE)
+endif()
+
+
# minizip-ng
if(NOT TARGET MINIZIP::minizip)
set(MZ_COMPAT OFF CACHE INTERNAL "" FORCE)
- set(MZ_ZLIB ON CACHE INTERNAL "")
- set(MZ_BZIP2 ON CACHE INTERNAL "")
- set(MZ_LZMA ON CACHE INTERNAL "")
- set(MZ_ZSTD ON CACHE INTERNAL "")
- set(MZ_LIBCOMP OFF CACHE INTERNAL "")
+ set(MZ_FETCH_LIBS ON CACHE INTERNAL "")
+ set(MZ_FORCE_FETCH_LIBS OFF CACHE INTERNAL "")
set(MZ_PKCRYPT OFF CACHE INTERNAL "")
set(MZ_WZAES OFF CACHE INTERNAL "")
set(MZ_OPENSSL OFF CACHE INTERNAL "")
- set(MZ_FETCH_LIBS ON CACHE INTERNAL "")
- set(MZ_FORCE_FETCH_LIBS ON CACHE INTERNAL "")
set(SKIP_INSTALL_ALL ON CACHE INTERNAL "" FORCE)
add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/minizip-ng")
diff --git a/ext/zstd b/ext/zstd
new file mode 160000
index 000000000..20707e371
--- /dev/null
+++ b/ext/zstd
@@ -0,0 +1 @@
+Subproject commit 20707e3718ee14250fb8a44b3bf023ea36bd88df
diff --git a/include/vpkpp/Options.h b/include/vpkpp/Options.h
index 0abfaf24a..63296bf66 100644
--- a/include/vpkpp/Options.h
+++ b/include/vpkpp/Options.h
@@ -20,8 +20,8 @@ struct BakeOptions {
/// BSP/ZIP - Override compression type of all stored entries
EntryCompressionType zip_compressionTypeOverride = EntryCompressionType::NO_OVERRIDE;
- /// BSP/ZIP - Compression strength (use -1 for the compression type's default)
- int8_t zip_compressionStrength = -1;
+ /// BSP/VPK/ZIP - Compression strength
+ int8_t zip_compressionStrength = 0;
/// GMA - Write CRCs for files and the overall GMA file when baking
bool gma_writeCRCs = true;
diff --git a/include/vpkpp/format/VPK.h b/include/vpkpp/format/VPK.h
index 46d9466ba..551ecb1d6 100644
--- a/include/vpkpp/format/VPK.h
+++ b/include/vpkpp/format/VPK.h
@@ -129,6 +129,10 @@ class VPK : public PackFile {
void addEntryInternal(Entry& entry, const std::string& path, std::vector& buffer, EntryOptions options) override;
+ [[nodiscard]] bool hasExtendedHeader() const;
+
+ [[nodiscard]] bool hasCompression() const;
+
[[nodiscard]] uint32_t getHeaderLength() const;
uint32_t numArchives = -1;
diff --git a/src/vpkpp/_vpkpp.cmake b/src/vpkpp/_vpkpp.cmake
index da18b4a42..a6d421e6c 100644
--- a/src/vpkpp/_vpkpp.cmake
+++ b/src/vpkpp/_vpkpp.cmake
@@ -1,5 +1,5 @@
add_pretty_parser(vpkpp
- DEPS cryptopp::cryptopp MINIZIP::minizip sourcepp::bsppp sourcepp::kvpp
+ DEPS cryptopp::cryptopp libzstd_static MINIZIP::minizip sourcepp::bsppp sourcepp::kvpp
DEPS_INTERFACE tsl::hat_trie
PRECOMPILED_HEADERS
"${CMAKE_CURRENT_SOURCE_DIR}/include/vpkpp/format/BSP.h"
diff --git a/src/vpkpp/format/VPK.cpp b/src/vpkpp/format/VPK.cpp
index 6304264bc..be539c309 100644
--- a/src/vpkpp/format/VPK.cpp
+++ b/src/vpkpp/format/VPK.cpp
@@ -5,9 +5,7 @@
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include
-
#include
-
#include
#include
#include
@@ -16,6 +14,7 @@
#include
#include
#include
+#include
using namespace kvpp;
using namespace sourcepp;
@@ -53,7 +52,7 @@ VPK::VPK(const std::string& fullFilePath_)
}
std::unique_ptr VPK::create(const std::string& path, uint32_t version) {
- if (version != 1 && version != 2) {
+ if (version != 1 && version != 2 && version != 54) {
return nullptr;
}
@@ -66,7 +65,7 @@ std::unique_ptr VPK::create(const std::string& path, uint32_t version)
header1.treeSize = 1;
stream.write(header1);
- if (version != 1) {
+ if (version == 2 || version == 54) {
Header2 header2{};
header2.fileDataSectionSize = 0;
header2.archiveMD5SectionSize = 0;
@@ -98,7 +97,7 @@ std::unique_ptr VPK::openInternal(const std::string& path, const Entry
}
auto* vpk = new VPK{path};
- auto packFile = std::unique_ptr(vpk);
+ auto packFile = std::unique_ptr{vpk};
FileStream reader{vpk->fullFilePath};
reader.seek_in(0);
@@ -107,7 +106,7 @@ std::unique_ptr VPK::openInternal(const std::string& path, const Entry
// File is not a VPK
return nullptr;
}
- if (vpk->header1.version == 2) {
+ if (vpk->hasExtendedHeader()) {
reader.read(vpk->header2);
} else if (vpk->header1.version != 1) {
// Apex Legends, Titanfall, etc. are not supported
@@ -161,6 +160,10 @@ std::unique_ptr VPK::openInternal(const std::string& path, const Entry
entry.offset = reader.read();
entry.length = reader.read();
+ if (vpk->hasCompression()) {
+ entry.compressedLength = reader.read();
+ }
+
if (reader.read() != VPK_ENTRY_TERM) {
// Invalid terminator!
return nullptr;
@@ -187,24 +190,28 @@ std::unique_ptr VPK::openInternal(const std::string& path, const Entry
// If there are no archives, -1 will be incremented to 0
vpk->numArchives++;
- // Read VPK2-specific data
- if (vpk->header1.version != 2)
+ // VPK v1 has nothing else for us
+ if (!vpk->hasExtendedHeader()) {
return packFile;
+ }
// Skip over file data, if any
reader.seek_in(vpk->header2.fileDataSectionSize, std::ios::cur);
- if (vpk->header2.archiveMD5SectionSize % sizeof(MD5Entry) != 0)
+ if (vpk->header2.archiveMD5SectionSize % sizeof(MD5Entry) != 0) {
return nullptr;
+ }
vpk->md5Entries.clear();
unsigned int entryNum = vpk->header2.archiveMD5SectionSize / sizeof(MD5Entry);
- for (unsigned int i = 0; i < entryNum; i++)
+ for (unsigned int i = 0; i < entryNum; i++) {
vpk->md5Entries.push_back(reader.read());
+ }
- if (vpk->header2.otherMD5SectionSize != 48)
+ if (vpk->header2.otherMD5SectionSize != 48) {
// This should always be 48
return packFile;
+ }
vpk->footer2.treeChecksum = reader.read_bytes<16>();
vpk->footer2.md5EntriesChecksum = reader.read_bytes<16>();
@@ -231,12 +238,12 @@ std::vector VPK::verifyEntryChecksums() const {
}
bool VPK::hasPackFileChecksum() const {
- return this->header1.version == 2;
+ return this->hasExtendedHeader();
}
bool VPK::verifyPackFileChecksum() const {
- // File checksums are only in v2
- if (this->header1.version != 2) {
+ // File checksums aren't in v1
+ if (!this->hasExtendedHeader()) {
return true;
}
@@ -261,7 +268,7 @@ bool VPK::verifyPackFileChecksum() const {
}
bool VPK::hasPackFileSignature() const {
- if (this->header1.version != 2) {
+ if (!this->hasExtendedHeader()) {
return false;
}
if (this->footer2.publicKey.empty() || this->footer2.signature.empty()) {
@@ -271,8 +278,8 @@ bool VPK::hasPackFileSignature() const {
}
bool VPK::verifyPackFileSignature() const {
- // Signatures are only in v2
- if (this->header1.version != 2) {
+ // Signatures aren't in v1
+ if (!this->hasExtendedHeader()) {
return true;
}
@@ -290,6 +297,7 @@ bool VPK::verifyPackFileSignature() const {
return crypto::verifySHA256PublicKey(dirFileBuffer, this->footer2.publicKey, this->footer2.signature);
}
+// NOLINTNEXTLINE(*-no-recursion)
std::optional> VPK::readEntry(const std::string& path_) const {
auto path = this->cleanEntryPath(path_);
auto entry = this->findEntry(path);
@@ -300,54 +308,93 @@ std::optional> VPK::readEntry(const std::string& path_) c
return readUnbakedEntry(*entry);
}
- std::vector output(entry->length, static_cast(0));
+ const auto entryLength = (this->hasCompression() && entry->compressedLength) ? entry->compressedLength : entry->length;
+ if (!entryLength) {
+ return {};
+ }
+ std::vector out(entryLength, static_cast(0));
if (!entry->extraData.empty()) {
- std::copy(entry->extraData.begin(), entry->extraData.end(), output.begin());
+ std::copy(entry->extraData.begin(), entry->extraData.end(), out.begin());
+ }
+ if (entryLength != entry->extraData.size()) {
+ if (entry->archiveIndex != VPK_DIR_INDEX) {
+ // Stored in a numbered archive
+ FileStream stream{this->getTruncatedFilepath() + '_' + string::padNumber(entry->archiveIndex, 3) + std::string{::isFPX(this) ? FPX_EXTENSION : VPK_EXTENSION}};
+ if (!stream) {
+ return std::nullopt;
+ }
+ stream.seek_in_u(entry->offset);
+ auto bytes = stream.read_bytes(entryLength - entry->extraData.size());
+ std::copy(bytes.begin(), bytes.end(), out.begin() + static_cast(entry->extraData.size()));
+ } else {
+ // Stored in this directory VPK
+ FileStream stream{this->fullFilePath};
+ if (!stream) {
+ return std::nullopt;
+ }
+ stream.seek_in_u(this->getHeaderLength() + this->header1.treeSize + entry->offset);
+ auto bytes = stream.read_bytes(entry->length - entry->extraData.size());
+ std::copy(bytes.begin(), bytes.end(), out.begin() + static_cast(entry->extraData.size()));
+ }
}
- if (entry->length == entry->extraData.size()) {
- return output;
+
+ if (!this->hasCompression() || !entry->compressedLength) {
+ return out;
}
- if (entry->archiveIndex != VPK_DIR_INDEX) {
- // Stored in a numbered archive
- FileStream stream{this->getTruncatedFilepath() + '_' + string::padNumber(entry->archiveIndex, 3) + (::isFPX(this) ? FPX_EXTENSION : VPK_EXTENSION).data()};
- if (!stream) {
- return std::nullopt;
- }
- stream.seek_in_u(entry->offset);
- auto bytes = stream.read_bytes(entry->length - entry->extraData.size());
- std::copy(bytes.begin(), bytes.end(), output.begin() + static_cast(entry->extraData.size()));
- } else {
- // Stored in this directory VPK
- FileStream stream{this->fullFilePath};
- if (!stream) {
- return std::nullopt;
- }
- stream.seek_in_u(this->getHeaderLength() + this->header1.treeSize + entry->offset);
- auto bytes = stream.read_bytes(entry->length - entry->extraData.size());
- std::copy(bytes.begin(), bytes.end(), output.begin() + static_cast(entry->extraData.size()));
+ const auto decompressionDict = this->readEntry(this->getTruncatedFilestem() + ".dict");
+ if (!decompressionDict) {
+ return std::nullopt;
+ }
+
+ std::unique_ptr dDict{
+ ZSTD_createDDict(decompressionDict->data(), decompressionDict->size()),
+ [](void* dDict) { ZSTD_freeDDict(reinterpret_cast(dDict)); },
+ };
+ if (!dDict) {
+ return std::nullopt;
+ }
+
+ std::unique_ptr dCtx{
+ ZSTD_createDCtx(),
+ [](void* dCtx) { ZSTD_freeDCtx(reinterpret_cast(dCtx)); },
+ };
+ if (!dCtx) {
+ return std::nullopt;
}
- return output;
+ std::vector decompressedData;
+ decompressedData.resize(entry->length);
+
+ if (ZSTD_isError(ZSTD_decompress_usingDDict(dCtx.get(), decompressedData.data(), decompressedData.size(), out.data(), out.size(), dDict.get()))) {
+ return {};
+ }
+ return decompressedData;
}
void VPK::addEntryInternal(Entry& entry, const std::string& path, std::vector& buffer, EntryOptions options) {
+ if (this->hasCompression()) {
+ // I don't feel like getting this to work right now
+ options.vpk_preloadBytes = 0;
+ }
+
entry.crc32 = crypto::computeCRC32(buffer);
entry.length = buffer.size();
// Offset will be reset when it's baked, assuming we're not replacing an existing chunk (when flags = 1)
+ // Compressed entries will not replace existing chunks, since their size is unknown
entry.flags = 0;
entry.offset = 0;
entry.archiveIndex = options.vpk_saveToDirectory ? VPK_DIR_INDEX : this->numArchives;
- if (!options.vpk_saveToDirectory && !this->freedChunks.empty()) {
+ if (!options.vpk_saveToDirectory && !this->freedChunks.empty() && !this->hasCompression()) {
int64_t bestChunkIndex = -1;
std::size_t currentChunkGap = SIZE_MAX;
for (int64_t i = 0; i < this->freedChunks.size(); i++) {
if (
- (bestChunkIndex < 0 && this->freedChunks[i].length >= entry.length) ||
- (bestChunkIndex >= 0 && this->freedChunks[i].length >= entry.length && (this->freedChunks[i].length - entry.length) < currentChunkGap)
- ) {
+ (bestChunkIndex < 0 && this->freedChunks[i].length >= entry.length) ||
+ (bestChunkIndex >= 0 && this->freedChunks[i].length >= entry.length && (this->freedChunks[i].length - entry.length) < currentChunkGap)
+ ) {
bestChunkIndex = i;
currentChunkGap = this->freedChunks[i].length - entry.length;
}
@@ -408,6 +455,33 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
std::string outputDir = this->getBakeOutputDir(outputDir_);
std::string outputPath = outputDir + '/' + this->getFilename();
+ // Store compression dictionary
+ std::optional> compressionDict;
+ std::unique_ptr cDict{nullptr, nullptr};
+ std::unique_ptr cCtx{nullptr, nullptr};
+ if (this->hasCompression()) {
+ compressionDict = this->readEntry(this->getTruncatedFilestem() + ".dict");
+ if (!compressionDict) {
+ return false;
+ }
+
+ cDict = {
+ ZSTD_createCDict(compressionDict->data(), compressionDict->size(), options.zip_compressionStrength),
+ [](void* cDict) { ZSTD_freeCDict(reinterpret_cast(cDict)); },
+ };
+ if (!cDict) {
+ return false;
+ }
+
+ cCtx = {
+ ZSTD_createCCtx(),
+ [](void* cCtx) { ZSTD_freeCCtx(reinterpret_cast(cCtx)); },
+ };
+ if (!cCtx) {
+ return false;
+ }
+ }
+
// Reconstruct data so we're not looping over it a ton of times
std::unordered_map>>> temp;
this->runForAllEntriesInternal([&temp](const std::string& path, Entry& entry) {
@@ -451,7 +525,7 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
// Helper
const auto getArchiveFilename = [this](const std::string& filename_, uint32_t archiveIndex) {
- std::string out{filename_ + '_' + string::padNumber(archiveIndex, 3) + (::isFPX(this) ? FPX_EXTENSION : VPK_EXTENSION).data()};
+ std::string out{filename_ + '_' + string::padNumber(archiveIndex, 3) + std::string{::isFPX(this) ? FPX_EXTENSION : VPK_EXTENSION}};
string::normalizeSlashes(out);
return out;
};
@@ -477,7 +551,7 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
// Dummy header
outDir.write(this->header1);
- if (this->header1.version == 2) {
+ if (this->hasExtendedHeader()) {
outDir.write(this->header2);
}
@@ -495,12 +569,13 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
if (!entryData) {
continue;
}
- if (entry->length == entry->extraData.size()) {
+
+ if (entry->length == entry->extraData.size() && !this->hasCompression()) {
// Override the archive index, no need for an archive VPK
entry->archiveIndex = VPK_DIR_INDEX;
entry->offset = dirVPKEntryData.size();
} else if (entry->archiveIndex != VPK_DIR_INDEX && (entry->flags & VPK_FLAG_REUSING_CHUNK)) {
- // The entry is replacing pre-existing data in a VPK archive
+ // The entry is replacing pre-existing data in a VPK archive - it's not compressed
auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(this)), entry->archiveIndex);
FileStream stream{archiveFilename, FileStream::OPT_READ | FileStream::OPT_WRITE | FileStream::OPT_CREATE_IF_NONEXISTENT};
stream.seek_out_u(entry->offset);
@@ -510,11 +585,31 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
auto archiveFilename = getArchiveFilename(::removeVPKAndOrDirSuffix(outputPath, ::isFPX(this)), entry->archiveIndex);
entry->offset = std::filesystem::exists(archiveFilename) ? std::filesystem::file_size(archiveFilename) : 0;
FileStream stream{archiveFilename, FileStream::OPT_APPEND | FileStream::OPT_CREATE_IF_NONEXISTENT};
- stream.write(*entryData);
+ if (!this->hasCompression() || !entry->compressedLength) {
+ stream.write(*entryData);
+ } else {
+ std::vector compressedData;
+ compressedData.resize(ZSTD_compressBound(entryData->size()));
+ auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
+ if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
+ return false;
+ }
+ stream.write(std::span{compressedData.data(), compressedSize});
+ }
} else {
// The entry will be added to the directory VPK
entry->offset = dirVPKEntryData.size();
- dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
+ if (!this->hasCompression() || !entry->compressedLength) {
+ dirVPKEntryData.insert(dirVPKEntryData.end(), entryData->data(), entryData->data() + entryData->size());
+ } else {
+ std::vector compressedData;
+ compressedData.resize(ZSTD_compressBound(entryData->size()));
+ auto compressedSize = ZSTD_compress_usingCDict(cCtx.get(), compressedData.data(), compressedData.size(), entryData->data(), entryData->size(), cDict.get());
+ if (ZSTD_isError(compressedSize) || compressedData.size() < compressedSize) {
+ return false;
+ }
+ dirVPKEntryData.insert(dirVPKEntryData.end(), compressedData.data(), compressedData.data() + compressedSize);
+ }
}
// Clear flags
@@ -527,6 +622,11 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
outDir.write(entry->archiveIndex);
outDir.write(entry->offset);
outDir.write(entry->length - entry->extraData.size());
+
+ if (this->hasCompression()) {
+ outDir.write(entry->compressedLength - entry->extraData.size());
+ }
+
outDir.write(VPK_ENTRY_TERM);
if (!entry->extraData.empty()) {
@@ -554,22 +654,21 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
// Calculate Header1
this->header1.treeSize = outDir.tell_out() - dirVPKEntryData.size() - this->getHeaderLength();
- // VPK v2 stuff
- if (this->header1.version == 2) {
+ // Non-v1 stuff
+ if (this->hasExtendedHeader()) {
// Calculate hashes for all entries
this->md5Entries.clear();
if (options.vpk_generateMD5Entries) {
this->runForAllEntries([this](const std::string& path, const Entry& entry) {
- // Believe it or not this should be safe to call by now
auto binData = this->readEntry(path);
if (!binData) {
return;
}
MD5Entry md5Entry{
- .archiveIndex = entry.archiveIndex,
- .offset = static_cast(entry.offset),
- .length = static_cast(entry.length - entry.extraData.size()),
- .checksum = crypto::computeMD5(*binData),
+ .archiveIndex = entry.archiveIndex,
+ .offset = static_cast(entry.offset),
+ .length = static_cast(entry.length - entry.extraData.size()),
+ .checksum = crypto::computeMD5(*binData),
};
this->md5Entries.push_back(md5Entry);
}, false);
@@ -616,8 +715,8 @@ bool VPK::bake(const std::string& outputDir_, BakeOptions options, const EntryCa
outDir.seek_out(0);
outDir.write(this->header1);
- // v2 adds the MD5 hashes and file signature
- if (this->header1.version != 2) {
+ // MD5 hashes, file signature
+ if (!this->hasExtendedHeader()) {
PackFile::setFullFilePath(outputDir);
return true;
}
@@ -689,7 +788,7 @@ bool VPK::generateKeyPairFiles(const std::string& name) {
}
bool VPK::sign(const std::string& filename_) {
- if (this->header1.version != 2 || !std::filesystem::exists(filename_) || std::filesystem::is_directory(filename_)) {
+ if (!this->hasExtendedHeader() || !std::filesystem::exists(filename_) || std::filesystem::is_directory(filename_)) {
return false;
}
@@ -708,18 +807,18 @@ bool VPK::sign(const std::string& filename_) {
}
bool VPK::sign(const std::vector& privateKey, const std::vector& publicKey) {
- if (this->header1.version != 2) {
+ if (!this->hasExtendedHeader()) {
return false;
}
this->header2.signatureSectionSize = this->footer2.publicKey.size() + this->footer2.signature.size() + sizeof(uint32_t) * 2;
{
- FileStream stream{this->getFilepath().data(), FileStream::OPT_READ | FileStream::OPT_WRITE};
+ FileStream stream{std::string{this->getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
stream.seek_out(sizeof(Header1));
stream.write(this->header2);
}
- auto dirFileBuffer = fs::readFileBuffer(this->getFilepath().data());
+ auto dirFileBuffer = fs::readFileBuffer(std::string{this->getFilepath()});
if (dirFileBuffer.size() <= this->header2.signatureSectionSize) {
return false;
}
@@ -730,7 +829,7 @@ bool VPK::sign(const std::vector& privateKey, const std::vectorfooter2.signature = crypto::signDataWithSHA256PrivateKey(dirFileBuffer, privateKey);
{
- FileStream stream{this->getFilepath().data(), FileStream::OPT_READ | FileStream::OPT_WRITE};
+ FileStream stream{std::string{this->getFilepath()}, FileStream::OPT_READ | FileStream::OPT_WRITE};
stream.seek_out(this->getHeaderLength() + this->header1.treeSize + this->header2.fileDataSectionSize + this->header2.archiveMD5SectionSize + this->header2.otherMD5SectionSize);
stream.write(static_cast(this->footer2.publicKey.size()));
stream.write(this->footer2.publicKey);
@@ -745,7 +844,7 @@ uint32_t VPK::getVersion() const {
}
void VPK::setVersion(uint32_t version) {
- if (version != 1 && version != 2) {
+ if (version != 1 && version != 2 && version != 54) {
return;
}
if (::isFPX(this) || version == this->header1.version) {
@@ -754,8 +853,8 @@ void VPK::setVersion(uint32_t version) {
this->header1.version = version;
// Clearing these isn't necessary, but might as well
- this->header2 = Header2{};
- this->footer2 = Footer2{};
+ this->header2 = {};
+ this->footer2 = {};
this->md5Entries.clear();
}
@@ -767,8 +866,16 @@ void VPK::setChunkSize(uint32_t newChunkSize) {
this->chunkSize = newChunkSize;
}
+bool VPK::hasExtendedHeader() const {
+ return this->header1.version == 2 || this->header1.version == 54;
+}
+
+bool VPK::hasCompression() const {
+ return this->header1.version == 54;
+}
+
uint32_t VPK::getHeaderLength() const {
- if (this->header1.version != 2) {
+ if (!this->hasExtendedHeader()) {
return sizeof(Header1);
}
return sizeof(Header1) + sizeof(Header2);