SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
PAK.cpp
Go to the documentation of this file.
1#include <vpkpp/format/PAK.h>
2
3#include <filesystem>
4
5#include <FileStream.h>
6
7using namespace sourcepp;
8using namespace vpkpp;
9
10std::unique_ptr<PackFile> PAK::create(const std::string& path, bool hrot) {
11 {
12 FileStream stream{path, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
13 stream
14 .write(hrot ? PAK_HROT_SIGNATURE : PAK_SIGNATURE)
15 .write<uint32_t>(0)
16 .write<uint32_t>(0);
17 }
18 return PAK::open(path);
19}
20
21std::unique_ptr<PackFile> PAK::open(const std::string& path, const EntryCallback& callback) {
22 if (!std::filesystem::exists(path)) {
23 // File does not exist
24 return nullptr;
25 }
26
27 auto* pak = new PAK{path};
28 auto packFile = std::unique_ptr<PackFile>(pak);
29
30 FileStream reader{pak->fullFilePath};
31 reader.seek_in(0);
32
33 if (auto signature = reader.read<uint32_t>(); signature == PAK_SIGNATURE) {
34 pak->isHROT_ = false;
35 } else if (signature == PAK_HROT_SIGNATURE) {
36 pak->isHROT_ = true;
37 } else {
38 // File is not a PAK
39 return nullptr;
40 }
41
42 auto directoryOffset = reader.read<uint32_t>();
43 // Directory size / file entry size
44 auto fileCount = reader.read<uint32_t>() / (pak->isHROT() ? 128 : 64);
45
46 reader.seek_in(directoryOffset);
47 for (uint32_t i = 0; i < fileCount; i++) {
48 Entry entry = createNewEntry();
49
50 auto entryPath = pak->cleanEntryPath(reader.read_string(pak->isHROT() ? PAK_HROT_FILENAME_MAX_SIZE : PAK_FILENAME_MAX_SIZE));
51
52 entry.offset = reader.read<uint32_t>();
53 entry.length = reader.read<uint32_t>();
54
55 pak->entries.emplace(entryPath, entry);
56
57 if (callback) {
58 callback(entryPath, entry);
59 }
60 }
61
62 return packFile;
63}
64
65std::optional<std::vector<std::byte>> PAK::readEntry(const std::string& path_) const {
66 auto path = this->cleanEntryPath(path_);
67 auto entry = this->findEntry(path);
68 if (!entry) {
69 return std::nullopt;
70 }
71 if (entry->unbaked) {
72 return readUnbakedEntry(*entry);
73 }
74
75 // It's baked into the file on disk
76 FileStream stream{this->fullFilePath};
77 if (!stream) {
78 return std::nullopt;
79 }
80 stream.seek_in_u(entry->offset);
81 return stream.read_bytes(entry->length);
82}
83
84void PAK::addEntryInternal(Entry& entry, const std::string& path, std::vector<std::byte>& buffer, EntryOptions options) {
85 entry.length = buffer.size();
86
87 // Offset will be reset when it's baked
88 entry.offset = 0;
89}
90
91bool PAK::bake(const std::string& outputDir_, BakeOptions options, const EntryCallback& callback) {
92 // Get the proper file output folder
93 std::string outputDir = this->getBakeOutputDir(outputDir_);
94 std::string outputPath = outputDir + '/' + this->getFilename();
95
96 // Reconstruct data for ease of access
97 std::vector<std::pair<std::string, Entry*>> entriesToBake;
98 this->runForAllEntriesInternal([&entriesToBake](const std::string& path, Entry& entry) {
99 entriesToBake.emplace_back(path, &entry);
100 });
101
102 // Read data before overwriting, we don't know if we're writing to ourself
103 std::vector<std::byte> fileData;
104 for (auto& [path, entry] : entriesToBake) {
105 if (auto binData = this->readEntry(path)) {
106 entry->offset = fileData.size();
107
108 fileData.insert(fileData.end(), binData->begin(), binData->end());
109 } else {
110 entry->offset = 0;
111 entry->length = 0;
112 }
113 }
114
115 {
116 FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
117 stream.seek_out(0);
118
119 // Signature
120 stream.write(this->isHROT() ? PAK_HROT_SIGNATURE : PAK_SIGNATURE);
121
122 // Index and size of directory
123 static constexpr uint32_t DIRECTORY_INDEX = sizeof(PAK_SIGNATURE) + sizeof(uint32_t) * 2;
124 stream.write(DIRECTORY_INDEX);
125 const uint32_t directorySize = entriesToBake.size() * (this->isHROT() ? 128 : 64);
126 stream.write(directorySize);
127
128 // Directory
129 for (const auto& [path, entry] : entriesToBake) {
130 stream.write(path, false, this->isHROT() ? PAK_HROT_FILENAME_MAX_SIZE : PAK_FILENAME_MAX_SIZE);
131 stream.write(static_cast<uint32_t>(entry->offset + DIRECTORY_INDEX + directorySize));
132 stream.write(static_cast<uint32_t>(entry->length));
133
134 if (callback) {
135 callback(path, *entry);
136 }
137 }
138
139 // File data
140 stream.write(fileData);
141 }
142
143 // Clean up
144 this->mergeUnbakedEntries();
145 PackFile::setFullFilePath(outputDir);
146 return true;
147}
148
150 using enum Attribute;
151 return LENGTH;
152}
153
154bool PAK::isHROT() const {
155 return this->isHROT_;
156}
157
158void PAK::setHROT(bool hrot) {
159 this->isHROT_ = hrot;
160}
This class represents the metadata that a file has inside a PackFile.
Definition: Entry.h:14
uint64_t offset
Offset, format-specific meaning - 0 if unused, or if the offset genuinely is 0.
Definition: Entry.h:33
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length)
Definition: Entry.h:26
Definition: PAK.h:17
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a PAK file.
Definition: PAK.cpp:21
static std::unique_ptr< PackFile > create(const std::string &path, bool forHROT=false)
Create a PAK file.
Definition: PAK.cpp:10
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition: PAK.cpp:65
bool isHROT() const
Definition: PAK.cpp:154
void setHROT(bool hrot)
Definition: PAK.cpp:158
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition: PAK.cpp:149
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
Definition: PAK.cpp:84
bool isHROT_
Definition: PAK.h:46
bool bake(const std::string &outputDir_, BakeOptions options, const EntryCallback &callback) override
If output folder is an empty string, it will overwrite the original.
Definition: PAK.cpp:91
EntryCallbackBase< void > EntryCallback
Definition: PackFile.h:30
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
std::string fullFilePath
Definition: PackFile.h:219
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
Definition: PackFile.cpp:541
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
void setFullFilePath(const std::string &outputDir)
Definition: PackFile.cpp:672
std::string cleanEntryPath(const std::string &path) const
Definition: PackFile.cpp:677
static Entry createNewEntry()
Definition: PackFile.cpp:686
static std::optional< std::vector< std::byte > > readUnbakedEntry(const Entry &entry)
Definition: PackFile.cpp:690
Definition: LZMA.h:11
Definition: Attribute.h:5
constexpr auto PAK_HROT_SIGNATURE
Definition: PAK.h:13
constexpr int8_t PAK_FILENAME_MAX_SIZE
Definition: PAK.h:9
constexpr auto PAK_SIGNATURE
Definition: PAK.h:10
Attribute
Definition: Attribute.h:7
constexpr int8_t PAK_HROT_FILENAME_MAX_SIZE
Definition: PAK.h:12