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