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