SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
GCF.cpp
Go to the documentation of this file.
1#include <vpkpp/format/GCF.h>
2
3#include <algorithm>
4#include <filesystem>
5#include <format>
6
7#include <FileStream.h>
8#include <miniz.h>
10#include <sourcepp/crypto/AES.h>
12
13using namespace sourcepp;
14using namespace vpkpp;
15
16std::unique_ptr<PackFile> GCF::open(const std::string& path, const EntryCallback& callback, const OpenPropertyRequest& requestProperty) {
17 // TODO: Add v5 and perhaps v4 support
18
19 if (!std::filesystem::exists(path)) {
20 // File does not exist
21 return nullptr;
22 }
23
24 auto* gcf = new GCF{path};
25 auto packFile = std::unique_ptr<PackFile>(gcf);
26
27 FileStream reader(gcf->fullFilePath);
28 reader.seek_in(0);
29
30 // read the main header here (not the block header)
31 reader.read(gcf->header);
32 if (gcf->header.dummy1 != 1 && gcf->header.dummy2 != 1 && gcf->header.gcfversion != 6) {
39 return nullptr;
40 }
41
42 if (gcf->header.filesize != std::filesystem::file_size(gcf->fullFilePath)) {
43 // again, this should never occur with a valid gcf file
44 return nullptr;
45 }
46
47 reader.read(gcf->blockheader);
48 if (gcf->blockheader.count != gcf->header.blockcount) {
49 return nullptr;
50 }
51
52 // if you're having a headache reading the if statement below heres a quick explaination of what it actually does
53 // it just adds all blockheader entries together and compares it against the checksum
54 // if it's not the same then we bail out with a nullptr
55 if (gcf->blockheader.count +
56 gcf->blockheader.used +
57 gcf->blockheader.dummy1 +
58 gcf->blockheader.dummy2 +
59 gcf->blockheader.dummy3 +
60 gcf->blockheader.dummy4 +
61 gcf->blockheader.dummy5 != gcf->blockheader.checksum) {
62 return nullptr;
63 }
64
65 // block headers!!!!!!
66 bool requestKey = false;
67 for (int i = 0; i < gcf->header.blockcount; i++) {
68 Block& block = gcf->blockdata.emplace_back();
69 reader.read(block);
70 if (block.isEncrypted()) {
71 requestKey = true;
72 }
73 }
74 if (requestKey) {
75 const auto key = requestProperty(gcf, OpenProperty::DECRYPTION_KEY);
76 if (key.size() != gcf->decryption_key.size()) {
77 return nullptr;
78 }
79 std::ranges::copy(key, gcf->decryption_key.begin());
80 }
81
82 // Fragmentation Map header
83 // not worth keeping around after verifying stuff so no struct def
84 // if you want one this is how one should look like
91 // actually if we want to implement writing later this might be a better idea
92 // if anyone wants to touch this piece of shit format again anyways
93
94 auto blkcount = reader.read<uint32_t>();
95 auto d1 = reader.read<uint32_t>();
96 auto d2 = reader.read<uint32_t>();
97 auto checksum = reader.read<uint32_t>();
98
99 if (blkcount + d1 + d2 != checksum || blkcount != gcf->blockheader.count) {
100 return nullptr;
101 }
102
103 // Fragmentation Map (list of dwords)
104
105 for (int i = 0; i < blkcount; i++) {
106 gcf->fragmap.push_back(reader.read<uint32_t>());
107 }
108
109 // Directory stuff starts here
110
111 //Reading the header
112 uint64_t temp = reader.tell_in();
113 reader.read(gcf->dirheader);
114
115 uint64_t diroffset = reader.tell_in() + (gcf->dirheader.itemcount * 28);
116
117 std::vector<DirectoryEntry2> direntries{};
118
119 for (int i = 0; i < gcf->dirheader.itemcount; i++) {
120 DirectoryEntry2& entry = direntries.emplace_back();
121 reader.read(entry.entry_real);
122 auto currentoffset = reader.tell_in();
123 reader.seek_in_u(diroffset + entry.entry_real.nameoffset);
124 reader.read(entry.filename);
125 if (entry.entry_real.dirtype != 0) { // if not directory
126 std::string dirname;
127 DirectoryEntry2 current_filename_entry = entry;
128 while (current_filename_entry.entry_real.parentindex != 0xffffffff) {
129 current_filename_entry = direntries[current_filename_entry.entry_real.parentindex];
130 dirname.insert(0, "/");
131 dirname.insert(0, current_filename_entry.filename);
132 }
133
134 auto gcfEntryPath = gcf->cleanEntryPath(dirname);
135 if (!gcfEntryPath.empty()) {
136 gcfEntryPath += '/';
137 }
138 gcfEntryPath += entry.filename;
139
140 Entry gcfEntry = createNewEntry();
141 gcfEntry.length = entry.entry_real.itemsize;
142 gcfEntry.crc32 = entry.entry_real.fileid; // INDEX INTO THE CHECKSUM MAP VECTOR NOT CRC32!!!
143 gcfEntry.offset = i; // THIS IS THE STRUCT INDEX NOT SOME OFFSET!!!
144 //printf("%s\n", gcfEntry.path.c_str());
145 gcf->entries.emplace(gcfEntryPath, gcfEntry);
146
147 if (callback) {
148 callback(gcfEntryPath, gcfEntry);
149 }
150 }
151 reader.seek_in_u(currentoffset);
152 }
153
154 // Directory Map
155
156 // Directory Map header
157 reader.seek_in_u(temp + gcf->dirheader.dirsize);
158
159 //auto dmap = reader.read<DirectoryMapHeader>();
160 reader.skip_in<DirectoryMapHeader>();
161
162 // Directory Map entries
163 for (int i = 0; i < gcf->dirheader.itemcount; i++) {
164 DirectoryMapEntry& entry = gcf->dirmap_entries.emplace_back();
165 reader.read(entry);
166 }
167
168 // Checksum header
169 //auto dummy0 = reader.read<uint32_t>();
170 reader.skip_in<uint32_t>();
171 auto checksumsize = reader.read<uint32_t>();
172 std::size_t checksums_start = reader.tell_in();
173
174 //printf("checksums start: %llu\n", checksums_start);
175 //printf("%lu %lu %lu %lu\n", gcf->header.blockcount, gcf->blockheader.used, gcf->header.appid, gcf->header.appversion);
176 // map header
177
178 auto chksummapheader = reader.read<ChecksumMapHeader>();
179 if (chksummapheader.dummy1 != 0x14893721 || chksummapheader.dummy2 != 0x1) {
180 return nullptr;
181 }
182
183 //printf("%lu %lu\n", chksummapheader.checksum_count, chksummapheader.item_count);
184
185 for (int i = 0; i < chksummapheader.item_count; i++) {
186 auto& cur_entry = gcf->chksum_map.emplace_back();
187 reader.read(cur_entry);
188 }
189
190 for (int i = 0; i < chksummapheader.checksum_count; i++) {
191 auto& currentChecksum = gcf->checksums.emplace_back();
192 reader.read(currentChecksum);
193 }
194 //printf("current pos: %llu, block header: %llu should be: %llu", reader.tellInput(), reader.tellInput() + 0x80, checksums_start + checksumsize);
195 // TODO: check the checksum RSA signature... later.. if ever...
196
197 reader.seek_in_u(checksums_start + checksumsize);
198
199 reader.read(gcf->datablockheader);
200 return packFile;
201}
202
203std::vector<std::string> GCF::verifyEntryChecksums() const {
204 std::vector<std::string> bad;
205 this->runForAllEntries([this, &bad](const std::string& path, const Entry& entry) {
206 auto bytes = this->readEntry(path);
207 if (!bytes || bytes->empty()) {
208 return;
209 }
210 std::size_t tocheck = bytes->size();
211 uint32_t idx = entry.crc32;
212 uint32_t count = this->chksum_map[idx].count;
213 uint32_t checksumstart = this->chksum_map[idx].firstindex;
214 for (int i = 0; i < count; i++) {
215 uint32_t csum = this->checksums[checksumstart + i];
216 std::size_t toread = std::min(static_cast<std::size_t>(0x8000), tocheck);
217 const auto* data = bytes->data() + (i * 0x8000);
218 uint32_t checksum = crypto::computeCRC32({data, toread}) ^ crypto::computeAdler32({data, toread});
219 if (checksum != csum) {
220 bad.push_back(path);
221 }
222 tocheck -= toread;
223 }
224 });
225 return bad;
226}
227
228std::optional<std::vector<std::byte>> GCF::readEntry(const std::string& path_) const {
229 auto path = this->cleanEntryPath(path_);
230 auto entry = this->findEntry(path);
231 if (!entry) {
232 return std::nullopt;
233 }
234 if (entry->unbaked) {
235 return readUnbakedEntry(*entry);
236 }
237
238 std::vector<std::byte> filedata;
239 if (entry->length == 0) {
240 // don't bother
241 return filedata;
242 }
243
244 uint32_t dir_index = entry->offset;
245 //printf(" extracting file: %s\n", entry.path.c_str());
246
247 std::vector<Block> toread;
248 for (const auto& v : this->blockdata) {
249 if (v.dir_index == dir_index && (v.flags & 0x8000)) { // need to check for 0x8000 here because valve dumps uninitialized memory
250 toread.push_back(v);
251 }
252 }
253
254 if (toread.empty()) {
255 //printf("could not find any directory index for %lu", entry.vpk_offset);
256 return std::nullopt;
257 }
258 std::sort(toread.begin(), toread.end(), [](const Block& lhs, const Block& rhs) {
259 return lhs.file_data_offset < rhs.file_data_offset;
260 });
261
262 FileStream stream{this->fullFilePath};
263 if (!stream) {
264 return std::nullopt;
265 }
266
267 uint64_t remaining = entry->length;
268 bool needs_decrypt = false;
270 for (auto& block : toread) {
271 uint32_t currindex = block.first_data_block_index;
272 while (currindex <= this->blockheader.count) {
273 uint64_t curfilepos = static_cast<uint64_t>(this->datablockheader.firstblockoffset) + (static_cast<std::uint64_t>(0x2000) * static_cast<std::uint64_t>(currindex));
274 stream.seek_in_u(curfilepos);
275 //printf("off %lli block %lu toread %lli should be %llu\n", stream.tellInput(), currindex, remaining, curfilepos);
276 uint32_t toreadAmt = std::min(remaining, static_cast<uint64_t>(0x2000));
277 auto streamvec = stream.read_bytes(toreadAmt);
278 filedata.insert(filedata.end(), streamvec.begin(), streamvec.end());
279 remaining -= toreadAmt;
280 currindex = this->fragmap[currindex];
281 //printf("curridx now: %lu\n", currindex);
282 }
283 needs_decrypt = block.isEncrypted();
284 filemode = block.getCompressionType();
285 }
286 if (needs_decrypt) {
287 switch (filemode) {
288 using enum Block::CompressionType;
289 case UNCOMPRESSED:
290 case COMPRESSED:
291 break;
292 case ENCRYPTED: {
293 auto real_size = filedata.size();
294 if (filedata.size() % 10 != 0) {
295 filedata.resize(filedata.size() + (0x10 - (filedata.size() % 10)), {});
296 }
297 auto remaining_encrypted = filedata.size();
298 auto offset = 0;
299 while (remaining_encrypted) {
300 auto current_batch = std::min(remaining_encrypted, static_cast<size_t>(0x8000));
301 crypto::decryptAES_CFB({filedata.data() + offset, current_batch}, this->decryption_key);
302 remaining_encrypted -= current_batch;
303 offset += static_cast<int>(current_batch);
304 }
305 filedata.resize(real_size);
306 break;
307 }
308 case COMPRESSED_AND_ENCRYPTED: {
309 std::vector<std::byte> processed_data;
310 BufferStream s(filedata.data(), filedata.size());
311 while (s.size() != s.tell()) {
312 static constexpr auto allocate_block = [](std::vector<std::byte>& vec, size_t x) {
313 const size_t old = vec.size();
314 vec.resize(old + x);
315 return vec.data() + old;
316 };
317
318 auto encrypted_size = s.read<uint32_t>();
319 mz_ulong decompressed_size = s.read<uint32_t>();
320
321 auto buffer = s.read_bytes(encrypted_size);
322 auto real_size = buffer.size();
323 if (buffer.size() % 10 != 0) {
324 buffer.resize(buffer.size() + (0x10 - (buffer.size() % 10)), {});
325 }
327 buffer.resize(real_size);
328
329 auto start = allocate_block(processed_data, decompressed_size);
330 if (mz_uncompress(reinterpret_cast<uint8_t*>(start), &decompressed_size, reinterpret_cast<uint8_t*>(buffer.data()), buffer.size()) != MZ_OK) {
331 return std::nullopt;
332 }
333
334 //auto remaining = s.size() - s.tell();
335 if (entry->length == processed_data.size()) {
336 break;
337 }
338 s.seek_u(s.tell() + (0x8000 - s.tell() % 0x8000));
339 }
340 filedata = processed_data;
341 break;
342 }
343 }
344 }
345 return filedata;
346}
347
349 using enum Attribute;
350 return LENGTH;
351}
352
353GCF::operator std::string() const {
354 return PackFileReadOnly::operator std::string() + std::format(" | Version v{} | AppID {} | App Version v{}", this->header.gcfversion, this->header.appid, this->header.appversion);
355}
356
357uint32_t GCF::getVersion() const {
358 return this->header.gcfversion;
359}
360
361uint32_t GCF::getAppID() const {
362 return this->header.appid;
363}
364
365uint32_t GCF::getAppVersion() const {
366 return this->header.appversion;
367}
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
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr, const OpenPropertyRequest &requestProperty=nullptr)
Definition GCF.cpp:16
Header header
Definition GCF.h:168
std::vector< Block > blockdata
Definition GCF.h:170
BlockHeader blockheader
Definition GCF.h:169
std::vector< ChecksumMapEntry > chksum_map
Definition GCF.h:176
std::array< std::byte, 16 > decryption_key
Definition GCF.h:178
uint32_t getVersion() const
Definition GCF.cpp:357
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 GCF.cpp:203
uint32_t getAppVersion() const
Definition GCF.cpp:365
DataBlockHeader datablockheader
Definition GCF.h:175
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition GCF.cpp:348
std::vector< uint32_t > checksums
Definition GCF.h:177
uint32_t getAppID() const
Definition GCF.cpp:361
std::optional< std::vector< std::byte > > readEntry(const std::string &path_) const override
Try to read the entry's data to a bytebuffer.
Definition GCF.cpp:228
std::vector< uint32_t > fragmap
Definition GCF.h:171
EntryCallbackBase< void > EntryCallback
Definition PackFile.h:38
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
std::function< std::vector< std::byte >(PackFile *packFile, OpenProperty property)> OpenPropertyRequest
Definition PackFile.h:33
void runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition PackFile.cpp:529
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
bool decryptAES_CFB(std::span< std::byte > buffer, std::span< const std::byte > key, std::span< const std::byte > iv=NULL_IV)
Definition AES.cpp:15
uint32_t computeAdler32(std::span< const std::byte > buffer)
Definition Adler32.cpp:10
uint32_t computeCRC32(std::span< const std::byte > buffer)
Definition CRC32.cpp:10
Attribute
Definition Attribute.h:7
bool isEncrypted() const
Definition GCF.h:72
std::string filename
Definition GCF.h:106
DirectoryEntry entry_real
Definition GCF.h:105