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
110bool checkForExtractionEscapeAttempt(const std::filesystem::path& outputDir, const std::string& filepath) {
111 const auto absOutDir = std::filesystem::absolute(outputDir).lexically_normal();
112 auto absOutDirString = absOutDir.string();
113 sourcepp::string::normalizeSlashes(absOutDirString);
114 absOutDirString += '/';
115
116 const auto absFilePath = std::filesystem::absolute(outputDir / std::filesystem::path{filepath}).lexically_normal();
117 auto absFilePathString = absFilePath.string();
118 sourcepp::string::normalizeSlashes(absFilePathString);
119
120 return !absFilePathString.starts_with(absOutDirString);
121}
122
123} // namespace
124
125PackFile::PackFile(std::string fullFilePath_)
126 : fullFilePath(std::move(fullFilePath_)) {
128}
129
130std::unique_ptr<PackFile> PackFile::open(const std::string& path, const EntryCallback& callback, const OpenPropertyRequest& requestProperty) {
131 auto extension = std::filesystem::path{path}.extension().string();
132 string::toLower(extension);
133 const auto& registry = getOpenExtensionRegistry();
134 if (registry.contains(extension)) {
135 for (const auto& func : registry.at(extension)) {
136 if (auto packFile = func(path, callback, requestProperty)) {
137 return packFile;
138 }
139 }
140 }
141 return nullptr;
142}
143
144std::vector<std::string> PackFile::getOpenableExtensions() {
145 std::vector<std::string> out;
146 for (const auto& extension : getOpenExtensionRegistry() | std::views::keys) {
147 if (std::ranges::find(out, extension) == out.end()) {
148 out.push_back(extension);
149 }
150 }
151 std::ranges::sort(out);
152 return out;
153}
154
155std::vector<std::string> PackFile::verifyEntryChecksums() const {
156 return {};
157}
158
160 return false;
161}
162
164 return true;
165}
166
168 return false;
169}
170
172 return true;
173}
174
175bool PackFile::hasEntry(const std::string& path, bool includeUnbaked) const {
176 return static_cast<bool>(this->findEntry(path, includeUnbaked));
177}
178
179std::optional<Entry> PackFile::findEntry(const std::string& path_, bool includeUnbaked) const {
180 const auto path = this->cleanEntryPath(path_);
181 if (const auto it = this->entries.find(path); it != this->entries.end()) {
182 return *it;
183 }
184 if (includeUnbaked) {
185 if (const auto it = this->unbakedEntries.find(path); it != this->unbakedEntries.end()) {
186 return *it;
187 }
188 }
189 return std::nullopt;
190}
191
192std::optional<std::vector<std::byte>> PackFile::operator[](const std::string& path_) const {
193 return this->readEntry(path_);
194}
195
196std::optional<std::string> PackFile::readEntryText(const std::string& path) const {
197 const auto bytes = this->readEntry(path);
198 if (!bytes) {
199 return std::nullopt;
200 }
201 std::string out;
202 for (auto byte : *bytes) {
203 if (byte == static_cast<std::byte>(0))
204 break;
205 out += static_cast<char>(byte);
206 }
207 return out;
208}
209
210bool PackFile::addEntry(const std::string& entryPath, const std::string& filepath, EntryOptions options) {
211 if (this->isReadOnly()) {
212 return false;
213 }
214
215 bool fileExists;
216 auto buffer = fs::readFileBuffer(filepath, fileExists);
217 if (!fileExists) {
218 return false;
219 }
220
221 Entry entry{};
222 entry.unbaked = true;
223 entry.unbakedUsingByteBuffer = false;
224 entry.unbakedData = filepath;
225 string::normalizeSlashes(std::get<std::string>(entry.unbakedData));
226
227 const auto path = this->cleanEntryPath(entryPath);
228 this->addEntryInternal(entry, path, buffer, options);
229 this->unbakedEntries.emplace(path, entry);
230 return true;
231}
232
233bool PackFile::addEntry(const std::string& path, std::vector<std::byte>&& buffer, EntryOptions options) {
234 if (this->isReadOnly()) {
235 return false;
236 }
237
238 Entry entry{};
239 entry.unbaked = true;
240 entry.unbakedUsingByteBuffer = true;
241
242 const auto path_ = this->cleanEntryPath(path);
243 this->addEntryInternal(entry, path_, buffer, options);
244 entry.unbakedData = std::move(buffer);
245 this->unbakedEntries.emplace(path_, entry);
246 return true;
248
249bool PackFile::addEntry(const std::string& path, std::span<const std::byte> buffer, EntryOptions options) {
250 this->addEntry(path, std::vector<std::byte>{buffer.begin(), buffer.end()}, options);
251 return true;
252}
253
254int64_t PackFile::addDirectory(const std::string& entryBaseDir, const std::string& dir, EntryOptions options) {
255 return this->addDirectory(entryBaseDir, dir, [options](const std::string&) {
256 return options;
257 });
258}
259
260int64_t PackFile::addDirectory(const std::string& entryBaseDir_, const std::string& dir, const EntryCreation& creation) {
261 if (this->isReadOnly() || !std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
262 return -1;
263 }
264
265 auto entryBaseDir = this->cleanEntryPath(entryBaseDir_);
266 if (!entryBaseDir.empty()) {
267 entryBaseDir += '/';
268 }
269 const auto dirLen = std::filesystem::absolute(dir).string().length() + 1;
270 int64_t filesAdded = 0;
271 for (const auto& file : std::filesystem::recursive_directory_iterator(dir, std::filesystem::directory_options::skip_permission_denied)) {
272 if (!file.is_regular_file()) {
273 continue;
274 }
275 std::string absPath;
276 std::string entryPath;
277 try {
278 absPath = std::filesystem::absolute(file.path()).string();
280 entryPath = this->cleanEntryPath(entryBaseDir + absPath.substr(dirLen));
281 } catch (const std::exception&) {
282 continue; // Likely a Unicode error, unsupported filename
283 }
284 if (entryPath.empty()) {
285 continue;
286 }
287 filesAdded += this->addEntry(entryPath, absPath, creation ? creation(entryPath) : EntryOptions{});
288 }
289 return filesAdded;
290}
291
292bool PackFile::renameEntry(const std::string& oldPath_, const std::string& newPath_) {
293 const auto oldPath = this->cleanEntryPath(oldPath_);
294 const auto newPath = this->cleanEntryPath(newPath_);
295 if (this->entries.count(oldPath)) {
296 // Currently there is no pack file format that relies on file path to access data.
297 // If there ever is one, we're in trouble! (Well, no, just override the method.)
298 auto entry = this->entries.at(oldPath);
299 this->entries.erase(oldPath);
300 this->entries.emplace(newPath, entry);
301 return true;
302 }
303 if (this->unbakedEntries.count(oldPath)) {
304 auto entry = this->unbakedEntries.at(oldPath);
305 this->unbakedEntries.erase(oldPath);
306 this->unbakedEntries.emplace(newPath, entry);
307 return true;
308 }
309 return false;
310}
311
312bool PackFile::renameDirectory(const std::string& oldDir_, const std::string& newDir_) {
313 auto oldDir = this->cleanEntryPath(oldDir_) + '/';
314 const auto newDir = this->cleanEntryPath(newDir_) + '/';
315
316 std::vector<std::string> entryPaths;
317 std::vector<std::string> unbakedEntryPaths;
318 this->runForAllEntries([&oldDir, &entryPaths, &unbakedEntryPaths](const std::string& path, const Entry& entry) {
319 if (path.starts_with(oldDir)) {
320 if (entry.unbaked) {
321 unbakedEntryPaths.push_back(path);
322 } else {
323 entryPaths.push_back(path);
324 }
325 }
326 });
327
328 for (const auto& entryPath : entryPaths) {
329 auto entry = this->entries.at(entryPath);
330 this->entries.erase(entryPath);
331 this->entries.emplace(newDir + entryPath.substr(oldDir.length()), entry);
332 }
333 for (const auto& entryPath : unbakedEntryPaths) {
334 auto entry = this->unbakedEntries.at(entryPath);
335 this->unbakedEntries.erase(entryPath);
336 this->unbakedEntries.emplace(newDir + entryPath.substr(oldDir.length()), entry);
337 }
338 return !entryPaths.empty() || !unbakedEntryPaths.empty();
339}
340
341bool PackFile::removeEntry(const std::string& path_) {
342 if (this->isReadOnly()) {
343 return false;
344 }
345
346 const auto path = this->cleanEntryPath(path_);
347 if (this->entries.find(path) != this->entries.end()) {
348 this->entries.erase(path);
349 return true;
350 }
351 if (this->unbakedEntries.find(path) != this->unbakedEntries.end()) {
352 this->unbakedEntries.erase(path);
353 return true;
354 }
355 return false;
356}
357
358std::size_t PackFile::removeDirectory(const std::string& dirName_) {
359 if (this->isReadOnly()) {
360 return false;
361 }
362
363 auto dirName = this->cleanEntryPath(dirName_);
364 dirName += '/';
365 if (dirName == "/") {
366 const auto size = this->getEntryCount();
367 this->entries.clear();
368 this->unbakedEntries.clear();
369 return size;
370 }
371 std::size_t count = this->entries.erase_prefix(dirName);
372 count += this->unbakedEntries.erase_prefix(dirName);
373 return count;
374}
375
377 return this->bake("", {}, nullptr);
378}
379
380bool PackFile::bake(const std::string& outputDir_) {
381 return this->bake(outputDir_, {}, nullptr);
382}
383
384bool PackFile::bake(const std::string& outputDir_, BakeOptions options) {
385 return this->bake(outputDir_, options, nullptr);
386}
387
388bool PackFile::extractEntry(const std::string& entryPath, const std::string& filepath) const {
389 if (filepath.empty()) {
390 return false;
391 }
392
393 const auto data = this->readEntry(entryPath);
394 if (!data) {
395 return false;
396 }
397
398 FileStream stream{filepath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
399 if (!stream) {
400 return false;
401 }
402
403 stream.write(*data);
404 return true;
405}
406
407bool PackFile::extractDirectory(const std::string& dir_, const std::string& outputDir) const {
408 auto dir = this->cleanEntryPath(dir_);
409 dir += '/';
410 if (dir == "/") {
411 return this->extractAll(outputDir, false);
412 }
413
414 auto outputDirPath = std::filesystem::path{outputDir} / std::filesystem::path{dir}.filename();
415 bool noneFailed = true;
416 this->runForAllEntries([this, &dir, &outputDirPath, &noneFailed](const std::string& path, const Entry&) {
417 if (!path.starts_with(dir)) {
418 return;
419 }
420
421 std::string outputPath = path.substr(dir.length());
422#ifdef _WIN32
423 ::fixFilePathForWindows(outputPath);
424#endif
425 if (::checkForExtractionEscapeAttempt(outputDirPath, outputPath) || !this->extractEntry(path, (outputDirPath / outputPath).string())) {
426 noneFailed = false;
427 }
428 });
429 return noneFailed;
430}
431
432bool PackFile::extractAll(const std::string& outputDir, bool createUnderPackFileDir) const {
433 if (outputDir.empty()) {
434 return false;
435 }
436
437 std::filesystem::path outputDirPath{outputDir};
438 if (createUnderPackFileDir) {
439 outputDirPath /= this->getTruncatedFilestem();
440 }
441 bool noneFailed = true;
442 this->runForAllEntries([this, &outputDirPath, &noneFailed](const std::string& path, const Entry&) {
443 std::string entryPath = path; // NOLINT(*-unnecessary-copy-initialization)
444#ifdef _WIN32
445 ::fixFilePathForWindows(entryPath);
446#endif
447 if (::checkForExtractionEscapeAttempt(outputDirPath, entryPath) || !this->extractEntry(path, (outputDirPath / entryPath).string())) {
448 noneFailed = false;
449 }
450 });
451 return noneFailed;
452}
453
454bool PackFile::extractAll(const std::string& outputDir, const EntryPredicate& predicate, bool stripSharedDirs) const {
455 if (outputDir.empty() || !predicate) {
456 return false;
457 }
458
459 // Get list of paths
460 std::vector<std::string> saveEntryPaths;
461 this->runForAllEntries([&predicate, &saveEntryPaths](const std::string& path, const Entry& entry) {
462 if (predicate(path, entry)) {
463 saveEntryPaths.push_back(path);
464 }
465 });
466 if (saveEntryPaths.empty()) {
467 return false;
468 }
469
470 std::size_t rootDirLen = 0;
471 if (stripSharedDirs) {
472 // Strip shared directories until we have a root folder
473 std::vector<std::string> rootDirList;
474
475 std::vector<std::vector<std::string>> pathSplits;
476 pathSplits.reserve(saveEntryPaths.size());
477 for (const auto& path : saveEntryPaths) {
478 pathSplits.push_back(::splitPath(path));
479 }
480 while (true) {
481 bool allTheSame = true;
482 const std::string& first = pathSplits[0][0];
483 for (const auto& path : pathSplits) {
484 if (path.size() == 1) {
485 allTheSame = false;
486 break;
487 }
488 if (path[0] != first) {
489 allTheSame = false;
490 break;
491 }
492 }
493 if (!allTheSame) {
494 break;
495 }
496 rootDirList.push_back(first);
497 for (auto& path : pathSplits) {
498 path.erase(path.begin());
499 }
500 }
501 rootDirLen = ::joinPath(rootDirList).length() + 1;
502 }
503
504 // Extract
505 const std::filesystem::path outputDirPath{outputDir};
506 bool noneFailed = true;
507 for (const auto& path : saveEntryPaths) {
508 auto savePath = path;
509#ifdef _WIN32
510 ::fixFilePathForWindows(savePath);
511#endif
512 if (::checkForExtractionEscapeAttempt(outputDirPath, savePath.substr(rootDirLen)) || !this->extractEntry(path, (outputDirPath / savePath.substr(rootDirLen)).string())) {
513 noneFailed = false;
514 }
515 }
516 return noneFailed;
517}
518
520 return this->entries;
521}
522
524 return this->unbakedEntries;
525}
526
527std::size_t PackFile::getEntryCount(bool includeUnbaked) const {
528 std::size_t count = 0;
529 count += this->entries.size();
530 if (includeUnbaked) {
531 count += this->unbakedEntries.size();
532 }
533 return count;
534}
535
536void PackFile::runForAllEntries(const EntryCallback& operation, bool includeUnbaked) const {
537 std::string key;
538 for (auto entry = this->entries.cbegin(); entry != this->entries.cend(); ++entry) {
539 entry.key(key);
540 operation(key, entry.value());
541 }
542 if (includeUnbaked) {
543 for (auto entry = this->unbakedEntries.cbegin(); entry != this->unbakedEntries.cend(); ++entry) {
544 entry.key(key);
545 operation(key, entry.value());
546 }
547 }
548}
549
550void PackFile::runForAllEntries(const std::string& parentDir, const EntryCallback& operation, bool recursive, bool includeUnbaked) const {
551 auto dir = this->cleanEntryPath(parentDir) + '/';
552
553 std::string key;
554 for (auto [entry, end] = this->entries.equal_prefix_range(dir); entry != end; ++entry) {
555 entry.key(key);
556 if (!recursive) {
557 auto keyView = std::string_view{key}.substr(dir.length());
558 if (std::ranges::find(keyView, '/') != keyView.end()) {
559 continue;
560 }
561 }
562 operation(key, entry.value());
563 }
564 if (includeUnbaked) {
565 for (auto [entry, end] = this->unbakedEntries.equal_prefix_range(dir); entry != end; ++entry) {
566 entry.key(key);
567 if (!recursive) {
568 auto keyView = std::string_view{key}.substr(dir.length());
569 if (std::ranges::find(keyView, '/') != keyView.end()) {
570 continue;
571 }
572 }
573 operation(key, entry.value());
574 }
575 }
576}
577
578void PackFile::runForAllEntriesInternal(const std::function<void(const std::string&, Entry&)>& operation, bool includeUnbaked) {
579 std::string key;
580 for (auto entry = this->entries.begin(); entry != this->entries.end(); ++entry) {
581 entry.key(key);
582 operation(key, entry.value());
583 }
584 if (includeUnbaked) {
585 for (auto entry = this->unbakedEntries.begin(); entry != this->unbakedEntries.end(); ++entry) {
586 entry.key(key);
587 operation(key, entry.value());
588 }
589 }
590}
591
592void PackFile::runForAllEntriesInternal(const std::string& parentDir, const std::function<void(const std::string&, Entry&)>& operation, bool recursive, bool includeUnbaked) {
593 auto dir = this->cleanEntryPath(parentDir) + '/';
594
595 std::string key;
596 for (auto [entry, end] = this->entries.equal_prefix_range(dir); entry != end; ++entry) {
597 entry.key(key);
598 if (!recursive) {
599 auto keyView = std::string_view{key}.substr(dir.length());
600 if (std::ranges::find(keyView, '/') != keyView.end()) {
601 continue;
602 }
603 }
604 operation(key, entry.value());
605 }
606 if (includeUnbaked) {
607 for (auto [entry, end] = this->unbakedEntries.equal_prefix_range(dir); entry != end; ++entry) {
608 entry.key(key);
609 if (!recursive) {
610 auto keyView = std::string_view{key}.substr(dir.length());
611 if (std::ranges::find(keyView, '/') != keyView.end()) {
612 continue;
613 }
614 }
615 operation(key, entry.value());
616 }
617 }
618}
619
620std::string_view PackFile::getFilepath() const {
621 return this->fullFilePath;
622}
623
625 return std::filesystem::path{this->fullFilePath}.parent_path().string() + '/' + this->getTruncatedFilestem();
626}
627
628std::string PackFile::getFilename() const {
629 return std::filesystem::path{this->fullFilePath}.filename().string();
630}
631
633 const std::filesystem::path path{this->fullFilePath};
634 return this->getTruncatedFilestem() + path.extension().string();
635}
636
637std::string PackFile::getFilestem() const {
638 return std::filesystem::path{this->fullFilePath}.stem().string();
639}
640
642 return this->getFilestem();
643}
644
648
649PackFile::operator std::string() const {
650 return this->getTruncatedFilename();
651}
652
653std::string PackFile::escapeEntryPathForWrite(const std::string& path) {
654#ifdef _WIN32
655 auto copy = path;
656 ::fixFilePathForWindows(copy);
657 return copy;
658#else
659 return path;
660#endif
661}
662
663std::vector<std::string> PackFile::verifyEntryChecksumsUsingCRC32() const {
664 std::vector<std::string> out;
665 this->runForAllEntries([this, &out](const std::string& path, const Entry& entry) {
666 if (!entry.crc32) {
667 return;
668 }
669 auto data = this->readEntry(path);
670 if (!data || crypto::computeCRC32(*data) != entry.crc32) {
671 out.push_back(path);
672 }
673 }, false); // Don't include unbaked since we probably calculate those manually on write
674 return out;
675}
676
677std::string PackFile::getBakeOutputDir(const std::string& outputDir) const {
678 std::string out = outputDir;
679 if (!out.empty()) {
680 string::normalizeSlashes(out, false);
681 } else {
682 out = this->fullFilePath;
683 if (const auto lastSlash = out.rfind('/'); lastSlash != std::string::npos) {
684 out = this->getFilepath().substr(0, lastSlash);
685 } else {
686 out = ".";
687 }
688 }
689 return out;
690}
691
693 std::string key;
694 for (auto entry = this->unbakedEntries.begin(); entry != this->unbakedEntries.end(); ++entry) {
695 entry.key(key);
696
697 entry->unbaked = false;
698
699 // Clear any data that might be stored in it
700 entry->unbakedUsingByteBuffer = false;
701 entry->unbakedData = "";
702
703 this->entries.insert(key, *entry);
704 }
705 this->unbakedEntries.clear();
706}
707
708void PackFile::setFullFilePath(const std::string& outputDir) {
709 // Assumes PackFile::getBakeOutputDir is the input for outputDir
710 this->fullFilePath = outputDir + '/' + this->getFilename();
711}
712
713std::string PackFile::cleanEntryPath(const std::string& path) const {
714 auto path_ = path;
715 string::normalizeSlashes(path_, true);
716 if (!this->isCaseSensitive()) {
717 string::toLower(path_);
718 }
719 return path_;
720}
721
723 return {};
724}
725
726std::optional<std::vector<std::byte>> PackFile::readUnbakedEntry(const Entry& entry) {
727 if (!entry.unbaked) {
728 return std::nullopt;
729 }
730
731 // Get the stored data
732 std::vector<std::byte> unbakedData;
733 if (entry.unbakedUsingByteBuffer) {
734 unbakedData = std::get<std::vector<std::byte>>(entry.unbakedData);
735 } else {
736 unbakedData = fs::readFileBuffer(std::get<std::string>(entry.unbakedData));
737 }
738 return unbakedData;
739}
740
741std::unordered_map<std::string, std::vector<PackFile::OpenFactoryFunction>>& PackFile::getOpenExtensionRegistry() {
742 static std::unordered_map<std::string, std::vector<PackFile::OpenFactoryFunction>> extensionRegistry;
743 return extensionRegistry;
744}
745
747 return registerOpenExtensionForTypeFactory(extension, [factory](const std::string& path, const EntryCallback& callback, const OpenPropertyRequest&) {
748 return factory(path, callback);
749 });
750}
751
753 const std::string extensionStr{extension};
754 auto& registry = getOpenExtensionRegistry();
755 if (!registry.contains(extensionStr)) {
756 registry[extensionStr] = {};
757 }
758 registry[extensionStr].push_back(factory);
759 return factory;
760}
761
762PackFileReadOnly::PackFileReadOnly(const std::string& fullFilePath_)
763 : PackFile(fullFilePath_) {}
764
765PackFileReadOnly::operator std::string() const {
766 return PackFile::operator std::string() + " (Read-Only)";
767}
768
769void PackFileReadOnly::addEntryInternal(Entry&, const std::string&, std::vector<std::byte>&, EntryOptions) {
770 // Stubbed
771}
772
773bool PackFileReadOnly::bake(const std::string&, BakeOptions, const EntryCallback&) {
774 return false; // Stubbed
775}
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
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) final
Definition PackFile.cpp:769
PackFileReadOnly(const std::string &fullFilePath_)
Definition PackFile.cpp:762
std::function< std::unique_ptr< PackFile >(const std::string &path, const EntryCallback &callback)> OpenFactoryFunctionBasic
Definition PackFile.h:224
tsl::htrie_map< char, Entry > EntryTrie
Definition PackFile.h:46
std::optional< std::string > readEntryText(const std::string &path) const
Try to read the entry's data to a string.
Definition PackFile.cpp:196
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:432
virtual bool hasPackFileSignature() const
Returns true if the file is signed.
Definition PackFile.cpp:167
EntryCallbackBase< void > EntryCallback
Definition PackFile.h:40
virtual std::size_t removeDirectory(const std::string &dirName_)
Remove a directory.
Definition PackFile.cpp:358
static std::unordered_map< std::string, std::vector< OpenFactoryFunction > > & getOpenExtensionRegistry()
Definition PackFile.cpp:741
static const OpenFactoryFunction & registerOpenExtensionForTypeFactory(std::string_view extension, const OpenFactoryFunctionBasic &factory)
Definition PackFile.cpp:746
virtual bool renameDirectory(const std::string &oldDir_, const std::string &newDir_)
Rename an existing directory.
Definition PackFile.cpp:312
void mergeUnbakedEntries()
Definition PackFile.cpp:692
std::optional< Entry > findEntry(const std::string &path_, bool includeUnbaked=true) const
Try to find an entry given the file path.
Definition PackFile.cpp:179
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:163
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:407
virtual bool verifyPackFileSignature() const
Verify the file signature, returns true on success Will return true if there is no signature ability ...
Definition PackFile.cpp:171
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:388
int64_t addDirectory(const std::string &entryBaseDir, const std::string &dir, EntryOptions options={})
Adds new entries using the contents of a given directory.
Definition PackFile.cpp:254
std::string fullFilePath
Definition PackFile.h:233
virtual std::string getTruncatedFilestem() const
/home/user/pak01_dir.vpk -> pak01
Definition PackFile.cpp:641
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:155
EntryTrie entries
Definition PackFile.h:234
virtual bool hasPackFileChecksum() const
Returns true if the entire file has a checksum.
Definition PackFile.cpp:159
std::vector< std::string > verifyEntryChecksumsUsingCRC32() const
Definition PackFile.cpp:663
virtual constexpr bool isReadOnly() const noexcept
Definition PackFile.h:104
EntryCallbackBase< bool > EntryPredicate
Definition PackFile.h:41
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:130
bool 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:210
virtual constexpr bool isCaseSensitive() const
Does the format support case-sensitive file names?
Definition PackFile.h:86
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
Definition PackFile.cpp:578
std::function< std::vector< std::byte >(PackFile *packFile, OpenProperty property)> OpenPropertyRequest
Definition PackFile.h:35
std::string getFilestem() const
/home/user/pak01_dir.vpk -> pak01_dir
Definition PackFile.cpp:637
bool hasEntry(const std::string &path, bool includeUnbaked=true) const
Check if an entry exists given the file path.
Definition PackFile.cpp:175
virtual bool renameEntry(const std::string &oldPath_, const std::string &newPath_)
Rename an existing entry.
Definition PackFile.cpp:292
bool bake()
If output folder is an empty string, it will overwrite the original.
Definition PackFile.cpp:376
std::string getFilename() const
/home/user/pak01_dir.vpk -> pak01_dir.vpk
Definition PackFile.cpp:628
std::string getBakeOutputDir(const std::string &outputDir) const
Definition PackFile.cpp:677
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:653
std::string getTruncatedFilepath() const
/home/user/pak01_dir.vpk -> /home/user/pak01
Definition PackFile.cpp:624
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition PackFile.cpp:536
void setFullFilePath(const std::string &outputDir)
Definition PackFile.cpp:708
std::function< std::unique_ptr< PackFile >(const std::string &path, const EntryCallback &callback, const OpenPropertyRequest &requestProperty)> OpenFactoryFunction
Definition PackFile.h:225
std::function< EntryOptions(const std::string &path)> EntryCreation
Definition PackFile.h:44
std::string cleanEntryPath(const std::string &path) const
Definition PackFile.cpp:713
virtual Attribute getSupportedEntryAttributes() const
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition PackFile.cpp:645
std::optional< std::vector< std::byte > > operator[](const std::string &path_) const
Definition PackFile.cpp:192
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:144
const EntryTrie & getBakedEntries() const
Get entries saved to disk.
Definition PackFile.cpp:519
static Entry createNewEntry()
Definition PackFile.cpp:722
EntryTrie unbakedEntries
Definition PackFile.h:235
std::string getTruncatedFilename() const
/home/user/pak01_dir.vpk -> pak01.vpk
Definition PackFile.cpp:632
std::size_t getEntryCount(bool includeUnbaked=true) const
Get the number of entries in the pack file.
Definition PackFile.cpp:527
virtual bool removeEntry(const std::string &path_)
Remove an entry.
Definition PackFile.cpp:341
std::string_view getFilepath() const
/home/user/pak01_dir.vpk
Definition PackFile.cpp:620
const EntryTrie & getUnbakedEntries() const
Get entries that have been added but not yet baked.
Definition PackFile.cpp:523
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Definition PackFile.cpp:726
uint32_t computeCRC32(std::span< const std::byte > buffer)
Definition CRC32.cpp:117
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
Definition FS.cpp:7
void normalizeSlashes(std::string &path, bool stripSlashPrefix=false, bool stripSlashSuffix=true)
Definition String.cpp:260
void toUpper(std::string &input)
Definition String.cpp:210
void toLower(std::string &input)
Definition String.cpp:200
Attribute
Definition Attribute.h:7