SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
GMA.cpp
Go to the documentation of this file.
1#include <vpkpp/format/GMA.h>
2
3#include <filesystem>
4#include <format>
5
6#include <FileStream.h>
8#include <sourcepp/FS.h>
9
10using namespace sourcepp;
11using namespace vpkpp;
12
13std::unique_ptr<PackFile> GMA::open(const std::string& path, const EntryCallback& callback) {
14 if (!std::filesystem::exists(path)) {
15 // File does not exist
16 return nullptr;
17 }
18
19 auto* gma = new GMA{path};
20 auto packFile = std::unique_ptr<PackFile>(gma);
21
22 FileStream reader{gma->fullFilePath};
23 reader.seek_in(0);
24
25 reader.read(gma->header.signature);
26 if (gma->header.signature != GMA_SIGNATURE) {
27 // File is not a GMA
28 return nullptr;
29 }
30 reader.read(gma->header.version);
31 reader.read(gma->header.steamID);
32 reader.read(gma->header.timestamp);
33 reader.read(gma->header.requiredContent);
34 reader.read(gma->header.addonName);
35 reader.read(gma->header.addonDescription);
36 reader.read(gma->header.addonAuthor);
37 reader.read(gma->header.addonVersion);
38
39 std::vector<std::pair<std::string, Entry>> entries;
40 while (reader.read<uint32_t>() > 0) {
41 Entry entry = createNewEntry();
42
43 auto entryPath = gma->cleanEntryPath(reader.read_string());
44
45 entry.length = reader.read<uint64_t>();
46 reader.read(entry.crc32);
47
48 entries.emplace_back(entryPath, entry);
49 }
50
51 // At this point we've reached the file data section, calculate the offsets and then add the entries
52 std::size_t offset = reader.tell_in();
53 for (auto& [entryPath, entry] : entries) {
54 entry.offset = offset;
55 offset += entry.length;
56
57 gma->entries.emplace(entryPath, entry);
58
59 if (callback) {
60 callback(entryPath, entry);
61 }
62 }
63
64 return packFile;
65}
66
67std::vector<std::string> GMA::verifyEntryChecksums() const {
68 return this->verifyEntryChecksumsUsingCRC32();
69}
70
72 return true;
73}
74
76 auto data = fs::readFileBuffer(this->fullFilePath);
77 if (data.size() <= 4) {
78 return true;
79 }
80
81 const auto checksum = *(reinterpret_cast<uint32_t*>(data.data() + data.size()) - 1);
82 data.pop_back();
83 data.pop_back();
84 data.pop_back();
85 data.pop_back();
86 return checksum == crypto::computeCRC32(data);
87}
88
89std::optional<std::vector<std::byte>> GMA::readEntry(const std::string& path_) const {
90 auto path = this->cleanEntryPath(path_);
91 auto entry = this->findEntry(path);
92 if (!entry) {
93 return std::nullopt;
94 }
95 if (entry->unbaked) {
96 return readUnbakedEntry(*entry);
97 }
98
99 // It's baked into the file on disk
100 FileStream stream{this->fullFilePath};
101 if (!stream) {
102 return std::nullopt;
103 }
104 stream.seek_in_u(entry->offset);
105 return stream.read_bytes(entry->length);
106}
107
108void GMA::addEntryInternal(Entry& entry, const std::string& path, std::vector<std::byte>& buffer, EntryOptions options) {
109 entry.length = buffer.size();
110 entry.crc32 = crypto::computeCRC32(buffer);
111
112 // Offset will be reset when it's baked
113 entry.offset = 0;
114}
115
116bool GMA::bake(const std::string& outputDir_, BakeOptions options, const EntryCallback& callback) {
117 // Get the proper file output folder
118 std::string outputDir = this->getBakeOutputDir(outputDir_);
119 std::string outputPath = outputDir + '/' + this->getFilename();
120
121 // Reconstruct data for ease of access
122 std::vector<std::pair<std::string, Entry*>> entriesToBake;
123 this->runForAllEntriesInternal([&entriesToBake](const std::string& path, Entry& entry) {
124 entriesToBake.emplace_back(path, &entry);
125 });
126
127 // Read data before overwriting, we don't know if we're writing to ourself
128 std::vector<std::byte> fileData;
129 for (auto& [path, entry] : entriesToBake) {
130 if (auto binData = this->readEntry(path)) {
131 fileData.insert(fileData.end(), binData->begin(), binData->end());
132 } else {
133 entry->length = 0;
134 }
135 }
136
137 {
138 FileStream stream{outputPath, FileStream::OPT_TRUNCATE | FileStream::OPT_CREATE_IF_NONEXISTENT};
139 stream.seek_out(0);
140
141 // Header
142 stream.write(this->header.signature);
143 stream.write(this->header.version);
144 stream.write(this->header.steamID);
145 stream.write(this->header.timestamp);
146 stream.write(this->header.requiredContent);
147 stream.write(this->header.addonName);
148 stream.write(this->header.addonDescription);
149 stream.write(this->header.addonAuthor);
150 stream.write(this->header.addonVersion);
151
152 // File tree
153 for (uint32_t i = 1; i <= entriesToBake.size(); i++) {
154 stream.write(i);
155 const auto& [path, entry] = entriesToBake[i - 1];
156 stream.write(path);
157 stream.write(entry->length);
158 stream.write<uint32_t>(options.gma_writeCRCs ? entry->crc32 : 0);
159
160 if (callback) {
161 callback(path, *entry);
162 }
163 }
164 stream.write<uint32_t>(0);
165
166 // Fix offsets
167 std::size_t offset = stream.tell_out();
168 for (auto& [path, entry] : entriesToBake) {
169 entry->offset = offset;
170 offset += entry->length;
171 }
172
173 // File data
174 stream.write(fileData);
175 }
176
177 // CRC of everything that's been written
178 uint32_t crc = 0;
179 if (options.gma_writeCRCs) {
180 auto fileSize = std::filesystem::file_size(outputPath);
181 FileStream stream{outputPath};
182 stream.seek_in(0);
183 crc = crypto::computeCRC32(stream.read_bytes(fileSize));
184 }
185 {
186 FileStream stream{outputPath, FileStream::OPT_APPEND};
187 stream.write(crc);
188 }
189
190 // Clean up
191 this->mergeUnbakedEntries();
192 PackFile::setFullFilePath(outputDir);
193 return true;
194}
195
197 using enum Attribute;
198 return LENGTH | CRC32;
199}
200
201GMA::operator std::string() const {
202 return PackFile::operator std::string() + std::format(" | Version v{} | Addon Name: \"{}\"", this->header.version, this->header.addonName);
203}
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
uint32_t crc32
CRC32 checksum - 0 if unused.
Definition Entry.h:40
uint64_t length
Length in bytes (in formats with compression, this is the uncompressed length).
Definition Entry.h:26
void addEntryInternal(Entry &entry, const std::string &path, std::vector< std::byte > &buffer, EntryOptions options) override
Definition GMA.cpp:108
Header header
Definition GMA.h:55
bool verifyPackFileChecksum() const override
Verify the checksum of the entire file, returns true on success Will return true if there is no check...
Definition GMA.cpp:75
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition GMA.cpp:196
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Open a GMA file.
Definition GMA.cpp:13
std::vector< std::string > verifyEntryChecksums() const override
Verify the checksums of each file, if a file fails the check its path will be added to the vector If ...
Definition GMA.cpp:67
bool hasPackFileChecksum() const override
Returns true if the entire file has a checksum.
Definition GMA.cpp:71
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition GMA.cpp:89
EntryCallbackBase< void > EntryCallback
Definition PackFile.h:38
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
std::string fullFilePath
Definition PackFile.h:231
EntryTrie entries
Definition PackFile.h:232
std::vector< std::string > verifyEntryChecksumsUsingCRC32() const
Definition PackFile.cpp:656
void runForAllEntriesInternal(const std::function< void(const std::string &, Entry &)> &operation, bool includeUnbaked=true)
Definition PackFile.cpp:571
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
void setFullFilePath(const std::string &outputDir)
Definition PackFile.cpp:701
std::string cleanEntryPath(const std::string &path) const
Definition PackFile.cpp:706
static Entry createNewEntry()
Definition PackFile.cpp:715
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
Attribute
Definition Attribute.h:7
constexpr auto GMA_SIGNATURE
Definition GMA.h:11
bool gma_writeCRCs
GMA - Write CRCs for files and the overall GMA file when baking.
Definition Options.h:28