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