SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
PackFile.cpp
Go to the documentation of this file.
1// ReSharper disable CppRedundantQualifier
2
3#include <vpkpp/PackFile.h>
4
5#include <algorithm>
6#include <cstring>
7#include <filesystem>
8#include <ranges>
9#include <sstream>
10#include <utility>
11
12#include <FileStream.h>
13
15#include <sourcepp/FS.h>
16#include <sourcepp/String.h>
17
18// Need to include this so the compiler will think the automatic registry
19// variables in the formats are important enough to initialize :3 (I love C++!)
20#include <vpkpp/vpkpp.h>
21
22using namespace sourcepp;
23using namespace vpkpp;
24
25namespace {
26
27std::string joinPath(const std::vector<std::string>& list) {
28 if (list.empty()) {
29 return "";
30 }
31 std::string result = list.front();
32 for (int i = 1; i < list.size(); ++i) {
33 result += '/' + list[i];
34 }
35 return result;
36}
37
38std::vector<std::string> splitPath(const std::string& string) {
39 std::vector<std::string> result;
40 std::stringstream stream{string};
41 std::string segment;
42 while (std::getline(stream, segment, '/')) {
43 result.push_back(segment);
44 }
45 return result;
46}
47
48#ifdef _WIN32
49
50void replace(std::string& line, const std::string& oldString, const std::string& newString) {
51 const auto oldSize = oldString.length();
52 if (oldSize > line.length()) {
53 return;
54 }
55
56 const auto newSize = newString.length();
57 std::size_t pos = 0;
58 while (true) {
59 pos = line.find(oldString, pos);
60 if (pos == std::string::npos) {
61 break;
62 }
63 if (oldSize == newSize) {
64 line.replace(pos, oldSize, newString);
65 } else {
66 line.erase(pos, oldSize);
67 line.insert(pos, newString);
68 }
69 pos += newSize;
70 }
71}
72
73void fixFilePathForWindows(std::string& path) {
74 // Remove invalid characters
75 ::replace(path, "<", "_");
76 ::replace(path, "<", "_");
77 ::replace(path, ">", "_");
78 ::replace(path, ":", "_");
79 ::replace(path, "\"", "_");
80 ::replace(path, "|", "_");
81 ::replace(path, "?", "_");
82 ::replace(path, "*", "_");
83
84 const std::filesystem::path filePath{path};
85 auto filename = filePath.filename().string();
86 const auto extension = filePath.extension().string();
87 auto stem = filePath.stem().string();
88 string::toUpper(stem);
89
90 // Replace bad filenames
91 if (stem == "CON" || stem == "PRN" || stem == "AUX" || stem == "NUL") {
92 filename = "___" + extension;
93 } else if (stem.length() == 4 && stem[3] != '0' && (stem.starts_with("COM") || stem.starts_with("LPT"))) {
94 filename = "___";
95 filename += stem[3];
96 filename += extension;
97 }
98
99 // Files cannot end with a period - weird
100 if (extension == ".") {
101 filename.pop_back();
102 filename += '_';
103 }
104
105 path = (filePath.parent_path() / filename).string();
106}
107
108#endif
109
110} // namespace
111
112PackFile::PackFile(std::string fullFilePath_)
113 : fullFilePath(std::move(fullFilePath_)) {
115}
116
117std::unique_ptr<PackFile> PackFile::open(const std::string& path, const EntryCallback& callback, const OpenPropertyRequest& requestProperty) {
118 auto extension = std::filesystem::path{path}.extension().string();
119 string::toLower(extension);
120 const auto& registry = getOpenExtensionRegistry();
121 if (registry.contains(extension)) {
122 for (const auto& func : registry.at(extension)) {
123 if (auto packFile = func(path, callback, requestProperty)) {
124 return packFile;
125 }
126 }
127 }
128 return nullptr;
129}
130
131std::vector<std::string> PackFile::getOpenableExtensions() {
132 std::vector<std::string> out;
133 for (const auto& extension : getOpenExtensionRegistry() | std::views::keys) {
134 if (std::ranges::find(out, extension) == out.end()) {
135 out.push_back(extension);
136 }
137 }
138 std::ranges::sort(out);
139 return out;
140}
141
142std::vector<std::string> PackFile::verifyEntryChecksums() const {
143 return {};
144}
145
147 return false;
148}
149
151 return true;
152}
153
155 return false;
156}
157
159 return true;
160}
161
162bool PackFile::hasEntry(const std::string& path, bool includeUnbaked) const {
163 return static_cast<bool>(this->findEntry(path, includeUnbaked));
164}
165
166std::optional<Entry> PackFile::findEntry(const std::string& path_, bool includeUnbaked) const {
167 const auto path = this->cleanEntryPath(path_);
168 if (const auto it = this->entries.find(path); it != this->entries.end()) {
169 return *it;
170 }
171 if (includeUnbaked) {
172 if (const auto it = this->unbakedEntries.find(path); it != this->unbakedEntries.end()) {
173 return *it;
174 }
175 }
176 return std::nullopt;
177}
178
179std::optional<std::string> PackFile::readEntryText(const std::string& path) const {
180 const auto bytes = this->readEntry(path);
181 if (!bytes) {
182 return std::nullopt;
183 }
184 std::string out;
185 for (auto byte : *bytes) {
186 if (byte == static_cast<std::byte>(0))
187 break;
188 out += static_cast<char>(byte);
189 }
190 return out;
191}
192
193void PackFile::addEntry(const std::string& entryPath, const std::string& filepath, EntryOptions options) {
194 if (this->isReadOnly()) {
195 return;
196 }
197
198 auto buffer = fs::readFileBuffer(filepath);
199
200 Entry entry{};
201 entry.unbaked = true;
202 entry.unbakedUsingByteBuffer = false;
203 entry.unbakedData = filepath;
204 string::normalizeSlashes(std::get<std::string>(entry.unbakedData));
205
206 const auto path = this->cleanEntryPath(entryPath);
207 this->addEntryInternal(entry, path, buffer, options);
208 this->unbakedEntries.emplace(path, entry);
209}
210
211void PackFile::addEntry(const std::string& path, std::vector<std::byte>&& buffer, EntryOptions options) {
212 if (this->isReadOnly()) {
213 return;
214 }
215
216 Entry entry{};
217 entry.unbaked = true;
218 entry.unbakedUsingByteBuffer = true;
219
220 const auto path_ = this->cleanEntryPath(path);
221 this->addEntryInternal(entry, path_, buffer, options);
222 entry.unbakedData = std::move(buffer);
223 this->unbakedEntries.emplace(path_, entry);
224}
225
226void PackFile::addEntry(const std::string& path, const std::byte* buffer, uint64_t bufferLen, EntryOptions options) {
227 std::vector<std::byte> data;
228 if (buffer && bufferLen > 0) {
229 data.resize(bufferLen);
230 std::memcpy(data.data(), buffer, bufferLen);
231 }
232 this->addEntry(path, std::move(data), options);
233}
234
235void PackFile::addDirectory(const std::string& entryBaseDir, const std::string& dir, EntryOptions options) {
236 this->addDirectory(entryBaseDir, dir, [options](const std::string&) {
237 return options;
238 });
239}
240
241void PackFile::addDirectory(const std::string& entryBaseDir_, const std::string& dir, const EntryCreation& creation) {
242 if (!std::filesystem::exists(dir) || std::filesystem::status(dir).type() != std::filesystem::file_type::directory) {
243 return;
244 }
245
246 auto entryBaseDir = this->cleanEntryPath(entryBaseDir_);
247 if (!entryBaseDir.empty()) {
248 entryBaseDir += '/';
249 }
250 const auto dirLen = std::filesystem::absolute(dir).string().length() + 1;
251 for (const auto& file : std::filesystem::recursive_directory_iterator(dir, std::filesystem::directory_options::skip_permission_denied)) {
252 if (!file.is_regular_file()) {
253 continue;
254 }
255 std::string absPath;
256 std::string entryPath;
257 try {
258 absPath = std::filesystem::absolute(file.path()).string();
260 entryPath = this->cleanEntryPath(entryBaseDir + absPath.substr(dirLen));
261 } catch (const std::exception&) {
262 continue; // Likely a Unicode error, unsupported filename
263 }
264 if (entryPath.empty()) {
265 continue;
266 }
267 this->addEntry(entryPath, absPath, creation ? creation(entryPath) : EntryOptions{});
268 }
269}
270
271bool PackFile::renameEntry(const std::string& oldPath_, const std::string& newPath_) {
272 const auto oldPath = this->cleanEntryPath(oldPath_);
273 const auto newPath = this->cleanEntryPath(newPath_);
274 if (this->entries.count(oldPath)) {
275 // Currently there is no pack file format that relies on file path to access data.
276 // If there ever is one, we're in trouble! (Well, no, just override the method.)
277 auto entry = this->entries.at(oldPath);
278 this->entries.erase(oldPath);
279 this->entries.emplace(newPath, entry);
280 return true;
281 }
282 if (this->unbakedEntries.count(oldPath)) {
283 auto entry = this->unbakedEntries.at(oldPath);
284 this->unbakedEntries.erase(oldPath);
285 this->unbakedEntries.emplace(newPath, entry);
286 return true;
287 }
288 return false;
289}
290
291bool PackFile::renameDirectory(const std::string& oldDir_, const std::string& newDir_) {
292 auto oldDir = this->cleanEntryPath(oldDir_) + '/';
293 const auto newDir = this->cleanEntryPath(newDir_) + '/';
294
295 std::vector<std::string> entryPaths;
296 std::vector<std::string> unbakedEntryPaths;
297 this->runForAllEntries([&oldDir, &entryPaths, &unbakedEntryPaths](const std::string& path, const Entry& entry) {
298 if (path.starts_with(oldDir)) {
299 if (entry.unbaked) {
300 unbakedEntryPaths.push_back(path);
301 } else {
302 entryPaths.push_back(path);
303 }
304 }
305 });
306
307 for (const auto& entryPath : entryPaths) {
308 auto entry = this->entries.at(entryPath);
309 this->entries.erase(entryPath);
310 this->entries.emplace(newDir + entryPath.substr(oldDir.length()), entry);
311 }
312 for (const auto& entryPath : unbakedEntryPaths) {
313 auto entry = this->unbakedEntries.at(entryPath);
314 this->unbakedEntries.erase(entryPath);
315 this->unbakedEntries.emplace(newDir + entryPath.substr(oldDir.length()), entry);
316 }
317 return !entryPaths.empty() || !unbakedEntryPaths.empty();
318}
319
320bool PackFile::removeEntry(const std::string& path_) {
321 if (this->isReadOnly()) {
322 return false;
323 }
324
325 const auto path = this->cleanEntryPath(path_);
326 if (this->entries.find(path) != this->entries.end()) {
327 this->entries.erase(path);
328 return true;
329 }
330 if (this->unbakedEntries.find(path) != this->unbakedEntries.end()) {
331 this->unbakedEntries.erase(path);
332 return true;
333 }
334 return false;
335}
336
337std::size_t PackFile::removeDirectory(const std::string& dirName_) {
338 if (this->isReadOnly()) {
339 return false;
340 }
341
342 auto dirName = this->cleanEntryPath(dirName_);
343 dirName += '/';
344 if (dirName == "/") {
345 const auto size = this->getEntryCount();
346 this->entries.clear();
347 this->unbakedEntries.clear();
348 return size;
349 }
350 std::size_t count = this->entries.erase_prefix(dirName);
351 count += this->unbakedEntries.erase_prefix(dirName);
352 return count;
353}
354
355bool PackFile::extractEntry(const std::string& entryPath, const std::string& filepath) const {
356 if (filepath.empty()) {
357 return false;
358 }
359
360 const auto data = this->readEntry(entryPath);
361 if (!data) {
362 return false;
363 }
364
365 FileStream stream{filepath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
366 if (!stream) {
367 return false;
368 }
369
370 stream.write(*data);
371 return true;
372}
373
374bool PackFile::extractDirectory(const std::string& dir_, const std::string& outputDir) const {
375 auto dir = this->cleanEntryPath(dir_);
376 dir += '/';
377 if (dir == "/") {
378 return this->extractAll(outputDir, false);
379 }
380
381 auto outputDirPath = std::filesystem::path{outputDir} / std::filesystem::path{dir}.filename();
382 bool noneFailed = true;
383 this->runForAllEntries([this, &dir, &outputDirPath, &noneFailed](const std::string& path, const Entry&) {
384 if (!path.starts_with(dir)) {
385 return;
386 }
387
388 std::string outputPath = path.substr(dir.length());
389#ifdef _WIN32
390 ::fixFilePathForWindows(outputPath);
391#endif
392 if (!this->extractEntry(path, (outputDirPath / outputPath).string())) {
393 noneFailed = false;
394 }
395 });
396 return noneFailed;
397}
398
399bool PackFile::extractAll(const std::string& outputDir, bool createUnderPackFileDir) const {
400 if (outputDir.empty()) {
401 return false;
402 }
403
404 std::filesystem::path outputDirPath{outputDir};
405 if (createUnderPackFileDir) {
406 outputDirPath /= this->getTruncatedFilestem();
407 }
408 bool noneFailed = true;
409 this->runForAllEntries([this, &outputDirPath, &noneFailed](const std::string& path, const Entry&) {
410 std::string entryPath = path; // NOLINT(*-unnecessary-copy-initialization)
411#ifdef _WIN32
412 ::fixFilePathForWindows(entryPath);
413#endif
414 if (!this->extractEntry(path, (outputDirPath / entryPath).string())) {
415 noneFailed = false;
416 }
417 });
418 return noneFailed;
419}
420
421bool PackFile::extractAll(const std::string& outputDir, const EntryPredicate& predicate, bool stripSharedDirs) const {
422 if (outputDir.empty() || !predicate) {
423 return false;
424 }
425
426 // Get list of paths
427 std::vector<std::string> saveEntryPaths;
428 this->runForAllEntries([&predicate, &saveEntryPaths](const std::string& path, const Entry& entry) {
429 if (predicate(path, entry)) {
430 saveEntryPaths.push_back(path);
431 }
432 });
433 if (saveEntryPaths.empty()) {
434 return false;
435 }
436
437 std::size_t rootDirLen = 0;
438 if (stripSharedDirs) {
439 // Strip shared directories until we have a root folder
440 std::vector<std::string> rootDirList;
441
442 std::vector<std::vector<std::string>> pathSplits;
443 pathSplits.reserve(saveEntryPaths.size());
444 for (const auto& path : saveEntryPaths) {
445 pathSplits.push_back(::splitPath(path));
446 }
447 while (true) {
448 bool allTheSame = true;
449 const std::string& first = pathSplits[0][0];
450 for (const auto& path : pathSplits) {
451 if (path.size() == 1) {
452 allTheSame = false;
453 break;
454 }
455 if (path[0] != first) {
456 allTheSame = false;
457 break;
458 }
459 }
460 if (!allTheSame) {
461 break;
462 }
463 rootDirList.push_back(first);
464 for (auto& path : pathSplits) {
465 path.erase(path.begin());
466 }
467 }
468 rootDirLen = ::joinPath(rootDirList).length() + 1;
469 }
470
471 // Extract
472 const std::filesystem::path outputDirPath{outputDir};
473 bool noneFailed = true;
474 for (const auto& path : saveEntryPaths) {
475 auto savePath = path;
476#ifdef _WIN32
477 ::fixFilePathForWindows(savePath);
478#endif
479 if (!this->extractEntry(path, (outputDirPath / savePath.substr(rootDirLen)).string())) {
480 noneFailed = false;
481 }
482 }
483 return noneFailed;
484}
485
487 return this->entries;
488}
489
491 return this->unbakedEntries;
492}
493
494std::size_t PackFile::getEntryCount(bool includeUnbaked) const {
495 std::size_t count = 0;
496 count += this->entries.size();
497 if (includeUnbaked) {
498 count += this->unbakedEntries.size();
499 }
500 return count;
501}
502
503void PackFile::runForAllEntries(const EntryCallback& operation, bool includeUnbaked) const {
504 std::string key;
505 for (auto entry = this->entries.cbegin(); entry != this->entries.cend(); ++entry) {
506 entry.key(key);
507 operation(key, entry.value());
508 }
509 if (includeUnbaked) {
510 for (auto entry = this->unbakedEntries.cbegin(); entry != this->unbakedEntries.cend(); ++entry) {
511 entry.key(key);
512 operation(key, entry.value());
513 }
514 }
515}
516
517void PackFile::runForAllEntries(const std::string& parentDir, const EntryCallback& operation, bool recursive, bool includeUnbaked) const {
518 auto dir = this->cleanEntryPath(parentDir) + '/';
519
520 std::string key;
521 for (auto [entry, end] = this->entries.equal_prefix_range(dir); entry != end; ++entry) {
522 entry.key(key);
523 if (!recursive) {
524 auto keyView = std::string_view{key}.substr(dir.length());
525 if (std::ranges::find(keyView, '/') != keyView.end()) {
526 continue;
527 }
528 }
529 operation(key, entry.value());
530 }
531 if (includeUnbaked) {
532 for (auto [entry, end] = this->unbakedEntries.equal_prefix_range(dir); entry != end; ++entry) {
533 entry.key(key);
534 if (!recursive) {
535 auto keyView = std::string_view{key}.substr(dir.length());
536 if (std::ranges::find(keyView, '/') != keyView.end()) {
537 continue;
538 }
539 }
540 operation(key, entry.value());
541 }
542 }
543}
544
545void PackFile::runForAllEntriesInternal(const std::function<void(const std::string&, Entry&)>& operation, bool includeUnbaked) {
546 std::string key;
547 for (auto entry = this->entries.begin(); entry != this->entries.end(); ++entry) {
548 entry.key(key);
549 operation(key, entry.value());
550 }
551 if (includeUnbaked) {
552 for (auto entry = this->unbakedEntries.begin(); entry != this->unbakedEntries.end(); ++entry) {
553 entry.key(key);
554 operation(key, entry.value());
555 }
556 }
557}
558
559void PackFile::runForAllEntriesInternal(const std::string& parentDir, const std::function<void(const std::string&, Entry&)>& operation, bool recursive, bool includeUnbaked) {
560 auto dir = this->cleanEntryPath(parentDir) + '/';
561
562 std::string key;
563 for (auto [entry, end] = this->entries.equal_prefix_range(dir); entry != end; ++entry) {
564 entry.key(key);
565 if (!recursive) {
566 auto keyView = std::string_view{key}.substr(dir.length());
567 if (std::ranges::find(keyView, '/') != keyView.end()) {
568 continue;
569 }
570 }
571 operation(key, entry.value());
572 }
573 if (includeUnbaked) {
574 for (auto [entry, end] = this->unbakedEntries.equal_prefix_range(dir); entry != end; ++entry) {
575 entry.key(key);
576 if (!recursive) {
577 auto keyView = std::string_view{key}.substr(dir.length());
578 if (std::ranges::find(keyView, '/') != keyView.end()) {
579 continue;
580 }
581 }
582 operation(key, entry.value());
583 }
584 }
585}
586
587std::string_view PackFile::getFilepath() const {
588 return this->fullFilePath;
589}
590
592 return std::filesystem::path{this->fullFilePath}.parent_path().string() + '/' + this->getTruncatedFilestem();
593}
594
595std::string PackFile::getFilename() const {
596 return std::filesystem::path{this->fullFilePath}.filename().string();
597}
598
600 const std::filesystem::path path{this->fullFilePath};
601 return this->getTruncatedFilestem() + path.extension().string();
602}
603
604std::string PackFile::getFilestem() const {
605 return std::filesystem::path{this->fullFilePath}.stem().string();
606}
607
609 return this->getFilestem();
610}
611
613 return Attribute::NONE;
614}
615
616PackFile::operator std::string() const {
617 return this->getTruncatedFilename();
618}
619
620std::string PackFile::escapeEntryPathForWrite(const std::string& path) {
621#ifdef _WIN32
622 auto copy = path;
623 ::fixFilePathForWindows(copy);
624 return copy;
625#else
626 return path;
627#endif
628}
629
630std::vector<std::string> PackFile::verifyEntryChecksumsUsingCRC32() const {
631 std::vector<std::string> out;
632 this->runForAllEntries([this, &out](const std::string& path, const Entry& entry) {
633 if (!entry.crc32) {
634 return;
635 }
636 auto data = this->readEntry(path);
637 if (!data || crypto::computeCRC32(*data) != entry.crc32) {
638 out.push_back(path);
639 }
640 }, false); // Don't include unbaked since we probably calculate those manually on write
641 return out;
642}
643
644std::string PackFile::getBakeOutputDir(const std::string& outputDir) const {
645 std::string out = outputDir;
646 if (!out.empty()) {
647 string::normalizeSlashes(out, false);
648 } else {
649 out = this->fullFilePath;
650 if (const auto lastSlash = out.rfind('/'); lastSlash != std::string::npos) {
651 out = this->getFilepath().substr(0, lastSlash);
652 } else {
653 out = ".";
654 }
655 }
656 return out;
657}
658
660 std::string key;
661 for (auto entry = this->unbakedEntries.begin(); entry != this->unbakedEntries.end(); ++entry) {
662 entry.key(key);
663
664 entry->unbaked = false;
665
666 // Clear any data that might be stored in it
667 entry->unbakedUsingByteBuffer = false;
668 entry->unbakedData = "";
669
670 this->entries.insert(key, *entry);
671 }
672 this->unbakedEntries.clear();
673}
674
675void PackFile::setFullFilePath(const std::string& outputDir) {
676 // Assumes PackFile::getBakeOutputDir is the input for outputDir
677 this->fullFilePath = outputDir + '/' + this->getFilename();
678}
679
680std::string PackFile::cleanEntryPath(const std::string& path) const {
681 auto path_ = path;
682 string::normalizeSlashes(path_, true);
683 if (!this->isCaseSensitive()) {
684 string::toLower(path_);
685 }
686 return path_;
687}
688
690 return {};
691}
692
693std::optional<std::vector<std::byte>> PackFile::readUnbakedEntry(const Entry& entry) {
694 if (!entry.unbaked) {
695 return std::nullopt;
696 }
697
698 // Get the stored data
699 std::vector<std::byte> unbakedData;
700 if (entry.unbakedUsingByteBuffer) {
701 unbakedData = std::get<std::vector<std::byte>>(entry.unbakedData);
702 } else {
703 unbakedData = fs::readFileBuffer(std::get<std::string>(entry.unbakedData));
704 }
705 return unbakedData;
706}
707
708std::unordered_map<std::string, std::vector<PackFile::OpenFactoryFunction>>& PackFile::getOpenExtensionRegistry() {
709 static std::unordered_map<std::string, std::vector<PackFile::OpenFactoryFunction>> extensionRegistry;
710 return extensionRegistry;
711}
712
714 return registerOpenExtensionForTypeFactory(extension, [factory](const std::string& path, const EntryCallback& callback, const OpenPropertyRequest&) {
715 return factory(path, callback);
716 });
717}
718
720 const std::string extensionStr{extension};
721 auto& registry = getOpenExtensionRegistry();
722 if (!registry.contains(extensionStr)) {
723 registry[extensionStr] = {};
724 }
725 registry[extensionStr].push_back(factory);
726 return factory;
727}
728
729PackFileReadOnly::PackFileReadOnly(const std::string& fullFilePath_)
730 : PackFile(fullFilePath_) {}
731
732PackFileReadOnly::operator std::string() const {
733 return PackFile::operator std::string() + " (Read-Only)";
734}
735
736void PackFileReadOnly::addEntryInternal(Entry&, const std::string&, std::vector<std::byte>&, EntryOptions) {
737 // Stubbed
738}
739
740bool PackFileReadOnly::bake(const std::string&, BakeOptions, const EntryCallback&) {
741 return false; // Stubbed
742}
This class represents the metadata that a file has inside a PackFile.
Definition: Entry.h:14
bool unbaked
Used to check if entry is saved to disk.
Definition: Entry.h:43
uint32_t crc32
CRC32 checksum - 0 if unused.
Definition: Entry.h:40
bool bake(const std::string &outputDir_, BakeOptions options, const EntryCallback &callback) final
If output folder is an empty string, it will overwrite the original.
Definition: PackFile.cpp:740
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) final
Definition: PackFile.cpp:736
PackFileReadOnly(const std::string &fullFilePath_)
Definition: PackFile.cpp:729
std::function< std::unique_ptr< PackFile >(const std::string &path, const EntryCallback &callback)> OpenFactoryFunctionBasic
Definition: PackFile.h:220
tsl::htrie_map< char, Entry > EntryTrie
Definition: PackFile.h:43
std::optional< std::string > readEntryText(const std::string &path) const
Try to read the entry's data to a string.
Definition: PackFile.cpp:179
bool extractAll(const std::string &outputDir, bool createUnderPackFileDir=true) const
Extract the contents of the pack file to disk at the given directory.
Definition: PackFile.cpp:399
virtual bool hasPackFileSignature() const
Returns true if the file is signed.
Definition: PackFile.cpp:154
EntryCallbackBase< void > EntryCallback
Definition: PackFile.h:37
virtual std::size_t removeDirectory(const std::string &dirName_)
Remove a directory.
Definition: PackFile.cpp:337
static std::unordered_map< std::string, std::vector< OpenFactoryFunction > > & getOpenExtensionRegistry()
Definition: PackFile.cpp:708
static const OpenFactoryFunction & registerOpenExtensionForTypeFactory(std::string_view extension, const OpenFactoryFunctionBasic &factory)
Definition: PackFile.cpp:713
virtual bool renameDirectory(const std::string &oldDir_, const std::string &newDir_)
Rename an existing directory.
Definition: PackFile.cpp:291
void mergeUnbakedEntries()
Definition: PackFile.cpp:659
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
Definition: PackFile.cpp:166
virtual bool verifyPackFileChecksum() const
Verify the checksum of the entire file, returns true on success Will return true if there is no check...
Definition: PackFile.cpp:150
bool extractDirectory(const std::string &dir_, const std::string &outputDir) const
Extract the given directory to disk under the given output directory.
Definition: PackFile.cpp:374
virtual bool verifyPackFileSignature() const
Verify the file signature, returns true on success Will return true if there is no signature ability ...
Definition: PackFile.cpp:158
virtual void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options)=0
bool extractEntry(const std::string &entryPath, const std::string &filepath) const
Extract the given entry to disk at the given file path.
Definition: PackFile.cpp:355
std::string fullFilePath
Definition: PackFile.h:229
virtual std::string getTruncatedFilestem() const
/home/user/pak01_dir.vpk -> pak01
Definition: PackFile.cpp:608
virtual std::vector< std::string > verifyEntryChecksums() const
Verify the checksums of each file, if a file fails the check its path will be added to the vector If ...
Definition: PackFile.cpp:142
EntryTrie entries
Definition: PackFile.h:230
virtual bool hasPackFileChecksum() const
Returns true if the entire file has a checksum.
Definition: PackFile.cpp:146
std::vector< std::string > verifyEntryChecksumsUsingCRC32() const
Definition: PackFile.cpp:630
virtual constexpr bool isReadOnly() const noexcept
Definition: PackFile.h:109
EntryCallbackBase< bool > EntryPredicate
Definition: PackFile.h:38
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr, const OpenPropertyRequest &requestProperty=nullptr)
Open a generic pack file. The parser is selected based on the file extension.
Definition: PackFile.cpp:117
virtual constexpr bool isCaseSensitive() const
Does the format support case-sensitive file names?
Definition: PackFile.h:93
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
Definition: PackFile.cpp:545
std::function< std::vector< std::byte >(PackFile *packFile, OpenProperty property)> OpenPropertyRequest
Definition: PackFile.h:32
std::string getFilestem() const
/home/user/pak01_dir.vpk -> pak01_dir
Definition: PackFile.cpp:604
bool hasEntry(const std::string &path, bool includeUnbaked=true) const
Check if an entry exists given the file path.
Definition: PackFile.cpp:162
virtual bool renameEntry(const std::string &oldPath_, const std::string &newPath_)
Rename an existing entry.
Definition: PackFile.cpp:271
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
Definition: PackFile.cpp:595
std::string getBakeOutputDir(const std::string &outputDir) const
Definition: PackFile.cpp:644
static std::string escapeEntryPathForWrite(const std::string &path)
On Windows, some characters and file names are invalid - this escapes the given entry path.
Definition: PackFile.cpp:620
std::string getTruncatedFilepath() const
/home/user/pak01_dir.vpk -> /home/user/pak01
Definition: PackFile.cpp:591
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition: PackFile.cpp:503
void setFullFilePath(const std::string &outputDir)
Definition: PackFile.cpp:675
std::function< std::unique_ptr< PackFile >(const std::string &path, const EntryCallback &callback, const OpenPropertyRequest &requestProperty)> OpenFactoryFunction
Definition: PackFile.h:221
void addDirectory(const std::string &entryBaseDir, const std::string &dir, EntryOptions options={})
Adds new entries using the contents of a given directory.
Definition: PackFile.cpp:235
std::function< EntryOptions(const std::string &path)> EntryCreation
Definition: PackFile.h:41
void addEntry(const std::string &entryPath, const std::string &filepath, EntryOptions options={})
Add a new entry from a file path - the first parameter is the path in the PackFile,...
Definition: PackFile.cpp:193
std::string cleanEntryPath(const std::string &path) const
Definition: PackFile.cpp:680
virtual Attribute getSupportedEntryAttributes() const
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition: PackFile.cpp:612
virtual std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const =0
Try to read the entry's data to a bytebuffer.
PackFile(const PackFile &other)=delete
static std::vector< std::string > getOpenableExtensions()
Returns a sorted list of supported extensions for opening, e.g. {".bsp", ".vpk"}.
Definition: PackFile.cpp:131
const EntryTrie & getBakedEntries() const
Get entries saved to disk.
Definition: PackFile.cpp:486
static Entry createNewEntry()
Definition: PackFile.cpp:689
EntryTrie unbakedEntries
Definition: PackFile.h:231
std::string getTruncatedFilename() const
/home/user/pak01_dir.vpk -> pak01.vpk
Definition: PackFile.cpp:599
std::size_t getEntryCount(bool includeUnbaked=true) const
Get the number of entries in the pack file.
Definition: PackFile.cpp:494
virtual bool removeEntry(const std::string &path_)
Remove an entry.
Definition: PackFile.cpp:320
std::string_view getFilepath() const
/home/user/pak01_dir.vpk
Definition: PackFile.cpp:587
const EntryTrie & getUnbakedEntries() const
Get entries that have been added but not yet baked.
Definition: PackFile.cpp:490
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Definition: PackFile.cpp:693
uint32_t computeCRC32(std::span< const std::byte > buffer)
Definition: CRC32.cpp:7
std::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
Definition: FS.cpp:9
void normalizeSlashes(std::string &path, bool stripSlashPrefix=false, bool stripSlashSuffix=true)
Definition: String.cpp:226
void toUpper(std::string &input)
Definition: String.cpp:175
void toLower(std::string &input)
Definition: String.cpp:165
Definition: LZMA.h:11
Definition: Attribute.h:5
Attribute
Definition: Attribute.h:7