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