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
6#include <FileStream.h>
9
10using namespace sourcepp;
11using namespace vpkpp;
12
13std::unique_ptr<PackFile> GCF::open(const std::string& path, const EntryCallback& callback) {
14 // TODO: Add v5 and perhaps v4 support
15
16 if (!std::filesystem::exists(path)) {
17 // File does not exist
18 return nullptr;
19 }
20
21 auto* gcf = new GCF{path};
22 auto packFile = std::unique_ptr<PackFile>(gcf);
23
24 FileStream reader(gcf->fullFilePath);
25 reader.seek_in(0);
26
27 // read the main header here (not the block header)
28 reader.read(gcf->header);
29 if (gcf->header.dummy1 != 1 && gcf->header.dummy2 != 1 && gcf->header.gcfversion != 6) {
36 return nullptr;
37 }
38
39 if (gcf->header.filesize != std::filesystem::file_size(gcf->fullFilePath)) {
40 // again, this should never occur with a valid gcf file
41 return nullptr;
42 }
43
44 reader.read(gcf->blockheader);
45 if (gcf->blockheader.count != gcf->header.blockcount) {
46 return nullptr;
47 }
48
49 // if you're having a headache reading the if statement below heres a quick explaination of what it actually does
50 // it just adds all blockheader entries together and compares it against the checksum
51 // if it's not the same then we bail out with a nullptr
52 if (gcf->blockheader.count +
53 gcf->blockheader.used +
54 gcf->blockheader.dummy1 +
55 gcf->blockheader.dummy2 +
56 gcf->blockheader.dummy3 +
57 gcf->blockheader.dummy4 +
58 gcf->blockheader.dummy5 != gcf->blockheader.checksum) {
59 return nullptr;
60 }
61
62 // block headers!!!!!!
63 for (int i = 0; i < gcf->header.blockcount; i++) {
64 Block& block = gcf->blockdata.emplace_back();
65 reader.read(block);
66 }
67
68 // Fragmentation Map header
69 // not worth keeping around after verifying stuff so no struct def
70 // if you want one this is how one should look like
77 // actually if we want to implement writing later this might be a better idea
78 // if anyone wants to touch this piece of shit format again anyways
79
80 auto blkcount = reader.read<uint32_t>();
81 auto d1 = reader.read<uint32_t>();
82 auto d2 = reader.read<uint32_t>();
83 auto checksum = reader.read<uint32_t>();
84
85 if (blkcount + d1 + d2 != checksum || blkcount != gcf->blockheader.count) {
86 return nullptr;
87 }
88
89 // Fragmentation Map (list of dwords)
90
91 for (int i = 0; i < blkcount; i++) {
92 gcf->fragmap.push_back(reader.read<uint32_t>());
93 }
94
95 // Directory stuff starts here
96
97 //Reading the header
98 uint64_t temp = reader.tell_in();
99 reader.read(gcf->dirheader);
100
101 uint64_t diroffset = reader.tell_in() + (gcf->dirheader.itemcount * 28);
102
103 std::vector<DirectoryEntry2> direntries{};
104
105 for (int i = 0; i < gcf->dirheader.itemcount; i++) {
106 DirectoryEntry2& entry = direntries.emplace_back();
107 reader.read(entry.entry_real);
108 auto currentoffset = reader.tell_in();
109 reader.seek_in_u(diroffset + entry.entry_real.nameoffset);
110 reader.read(entry.filename);
111 if (entry.entry_real.dirtype != 0) { // if not directory
112 std::string dirname;
113 DirectoryEntry2 current_filename_entry = entry;
114 while (current_filename_entry.entry_real.parentindex != 0xffffffff) {
115 current_filename_entry = direntries[current_filename_entry.entry_real.parentindex];
116 dirname.insert(0, "/");
117 dirname.insert(0, current_filename_entry.filename);
118 }
119
120 auto gcfEntryPath = gcf->cleanEntryPath(dirname);
121 if (!gcfEntryPath.empty()) {
122 gcfEntryPath += '/';
123 }
124 gcfEntryPath += entry.filename;
125
126 Entry gcfEntry = createNewEntry();
127 gcfEntry.length = entry.entry_real.itemsize;
128 gcfEntry.crc32 = entry.entry_real.fileid; // INDEX INTO THE CHECKSUM MAP VECTOR NOT CRC32!!!
129 gcfEntry.offset = i; // THIS IS THE STRUCT INDEX NOT SOME OFFSET!!!
130 //printf("%s\n", gcfEntry.path.c_str());
131 gcf->entries.emplace(gcfEntryPath, gcfEntry);
132
133 if (callback) {
134 callback(gcfEntryPath, gcfEntry);
135 }
136 }
137 reader.seek_in_u(currentoffset);
138 }
139
140 // Directory Map
141
142 // Directory Map header
143 reader.seek_in_u(temp + gcf->dirheader.dirsize);
144
145 //auto dmap = reader.read<DirectoryMapHeader>();
146 reader.skip_in<DirectoryMapHeader>();
147
148 // Directory Map entries
149 for (int i = 0; i < gcf->dirheader.itemcount; i++) {
150 DirectoryMapEntry& entry = gcf->dirmap_entries.emplace_back();
151 reader.read(entry);
152 }
153
154 // Checksum header
155 //auto dummy0 = reader.read<uint32_t>();
156 reader.skip_in<uint32_t>();
157 auto checksumsize = reader.read<uint32_t>();
158 std::size_t checksums_start = reader.tell_in();
159
160 //printf("checksums start: %llu\n", checksums_start);
161 //printf("%lu %lu %lu %lu\n", gcf->header.blockcount, gcf->blockheader.used, gcf->header.appid, gcf->header.appversion);
162 // map header
163
164 auto chksummapheader = reader.read<ChecksumMapHeader>();
165 if (chksummapheader.dummy1 != 0x14893721 || chksummapheader.dummy2 != 0x1) {
166 return nullptr;
167 }
168
169 //printf("%lu %lu\n", chksummapheader.checksum_count, chksummapheader.item_count);
170
171 for (int i = 0; i < chksummapheader.item_count; i++) {
172 auto& cur_entry = gcf->chksum_map.emplace_back();
173 reader.read(cur_entry);
174 }
175
176 for (int i = 0; i < chksummapheader.checksum_count; i++) {
177 auto& currentChecksum = gcf->checksums.emplace_back();
178 reader.read(currentChecksum);
179 }
180 //printf("current pos: %llu, block header: %llu should be: %llu", reader.tellInput(), reader.tellInput() + 0x80, checksums_start + checksumsize);
181 // TODO: check the checksum RSA signature... later.. if ever...
182
183 reader.seek_in_u(checksums_start + checksumsize);
184
185 reader.read(gcf->datablockheader);
186 return packFile;
187}
188
189std::vector<std::string> GCF::verifyEntryChecksums() const {
190 std::vector<std::string> bad;
191 this->runForAllEntries([this, &bad](const std::string& path, const Entry& entry) {
192 auto bytes = this->readEntry(path);
193 if (!bytes || bytes->empty()) {
194 return;
195 }
196 std::size_t tocheck = bytes->size();
197 uint32_t idx = entry.crc32;
198 uint32_t count = this->chksum_map[idx].count;
199 uint32_t checksumstart = this->chksum_map[idx].firstindex;
200 for (int i = 0; i < count; i++) {
201 uint32_t csum = this->checksums[checksumstart + i];
202 std::size_t toread = std::min(static_cast<std::size_t>(0x8000), tocheck);
203 const auto* data = bytes->data() + (i * 0x8000);
204 uint32_t checksum = crypto::computeCRC32({data, toread}) ^ crypto::computeAdler32({data, toread});
205 if (checksum != csum) {
206 bad.push_back(path);
207 }
208 tocheck -= toread;
209 }
210 });
211 return bad;
212}
213
214std::optional<std::vector<std::byte>> GCF::readEntry(const std::string& path_) const {
215 auto path = this->cleanEntryPath(path_);
216 auto entry = this->findEntry(path);
217 if (!entry) {
218 return std::nullopt;
219 }
220 if (entry->unbaked) {
221 return readUnbakedEntry(*entry);
222 }
223
224 std::vector<std::byte> filedata;
225 if (entry->length == 0) {
226 // don't bother
227 return filedata;
228 }
229
230 uint32_t dir_index = entry->offset;
231 //printf(" extracting file: %s\n", entry.path.c_str());
232
233 std::vector<Block> toread;
234 for (const auto& v : this->blockdata) {
235 if (v.dir_index == dir_index) {
236 toread.push_back(v);
237 }
238 }
239
240 if (toread.empty()) {
241 //printf("could not find any directory index for %lu", entry.vpk_offset);
242 return std::nullopt;
243 }
244 std::sort(toread.begin(), toread.end(), [](const Block& lhs, const Block& rhs) {
245 return lhs.file_data_offset < rhs.file_data_offset;
246 });
247
248 FileStream stream{this->fullFilePath};
249 if (!stream) {
250 return std::nullopt;
251 }
252
253 uint64_t remaining = entry->length;
254
255 for (const auto& block : toread) {
256 uint32_t currindex = block.first_data_block_index;
257 while (currindex <= this->blockheader.count) {
258 uint64_t curfilepos = static_cast<uint64_t>(this->datablockheader.firstblockoffset) + (static_cast<std::uint64_t>(0x2000) * static_cast<std::uint64_t>(currindex));
259 stream.seek_in_u(curfilepos);
260 //printf("off %lli block %lu toread %lli should be %llu\n", stream.tellInput(), currindex, remaining, curfilepos);
261 uint32_t toreadAmt = std::min(remaining, static_cast<uint64_t>(0x2000));
262 auto streamvec = stream.read_bytes(toreadAmt);
263 filedata.insert(filedata.end(), streamvec.begin(), streamvec.end());
264 remaining -= toreadAmt;
265 currindex = this->fragmap[currindex];
266 //printf("curridx now: %lu\n", currindex);
267 }
268 }
269
270 return filedata;
271}
272
274 using enum Attribute;
275 return LENGTH;
276}
277
278GCF::operator std::string() const {
279 return PackFileReadOnly::operator std::string() +
280 " | Version v" + std::to_string(this->header.gcfversion) +
281 " | AppID " + std::to_string(this->header.appid) +
282 " | App Version v" + std::to_string(this->header.appversion);
283}
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: GCF.h:9
std::vector< Block > blockdata
Definition: GCF.h:142
BlockHeader blockheader
Definition: GCF.h:141
std::vector< ChecksumMapEntry > chksum_map
Definition: GCF.h:148
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:189
DataBlockHeader datablockheader
Definition: GCF.h:147
Attribute getSupportedEntryAttributes() const override
Returns a list of supported entry attributes Mostly for GUI programs that show entries and their meta...
Definition: GCF.cpp:273
std::vector< uint32_t > checksums
Definition: GCF.h:149
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:214
std::vector< uint32_t > fragmap
Definition: GCF.h:143
static std::unique_ptr< PackFile > open(const std::string &path, const EntryCallback &callback=nullptr)
Definition: GCF.cpp:13
EntryCallbackBase< void > EntryCallback
Definition: PackFile.h:30
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 runForAllEntries(const EntryCallback &operation, bool includeUnbaked=true) const
Run a callback for each entry in the pack file.
Definition: PackFile.cpp:499
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 computeAdler32(std::span< const std::byte > buffer)
Definition: Adler32.cpp:39
uint32_t computeCRC32(std::span< const std::byte > buffer)
Definition: CRC32.cpp:7
Definition: LZMA.h:11
Definition: Attribute.h:5
Attribute
Definition: Attribute.h:7
uint32_t count
Definition: GCF.h:29
uint32_t firstblockoffset
Definition: GCF.h:94
std::string filename
Definition: GCF.h:78
DirectoryEntry entry_real
Definition: GCF.h:77
uint32_t nameoffset
Definition: GCF.h:67
uint32_t parentindex
Definition: GCF.h:71