Skip to content

Commit be920de

Browse files
authored
feat: vpk parsed info in demos (#351)
1 parent fca78d4 commit be920de

File tree

4 files changed

+205
-0
lines changed

4 files changed

+205
-0
lines changed

src/Checksum.cpp

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,144 @@ bool AddDemoChecksum(const char *filename) {
235235
static std::thread g_sumthreads[NUM_FILE_SUM_THREADS];
236236
static std::map<std::string, uint32_t> g_filesums[NUM_FILE_SUM_THREADS];
237237

238+
static const uint32_t g_vpkWhitelist[] = {
239+
// Portal 2
240+
498458753, // /portal2_dlc2/pak01_dir.vpk
241+
2331752929, // /portal 2/update/pak01_dir.vpk
242+
2640587196, // /portal2_dlc1/pak01_dir.vpk
243+
4107509135, // /portal2/pak01_dir.vpk
244+
// Mel
245+
1561002964, // /portal stories mel/portal_stories/pak01_dir.vpk
246+
89568034, // /portal stories mel/portal2/pak01_dir.vpk
247+
// Reloaded Current
248+
2391749182, // /portal reloaded/portalreloaded/pak01_dir.vpk
249+
3928273112, // /portal reloaded/update/pak01_dir.vpk
250+
177648256, // /portal reloaded/portal2_dlc2/pak01_dir.vpk
251+
1998161362, // /portal reloaded/portal2_dlc1/pak01_dir.vpk
252+
1409821692, // /portal reloaded/portal2/pak01_dir.vpk
253+
// Reloaded 1.0.0
254+
128738477, // /portal reloaded/portalreloaded/pak01_dir.vpk
255+
// Reloaded 1.1.0 Precoop
256+
1759389097, // /portal reloaded/portalreloaded/pak01_dir.vpk
257+
// Reloaed 1.2.0 Postcoop has none?
258+
// Aptag has none?
259+
};
260+
static const size_t g_vpkWhitelistSize = sizeof(g_vpkWhitelist) / sizeof(g_vpkWhitelist[0]);
261+
262+
struct VpkFileEntry {
263+
std::string internalPath;
264+
uint32_t crc;
265+
};
266+
267+
struct VpkInternalData {
268+
std::string vpkPath;
269+
uint32_t wholeFileCrc;
270+
std::vector<VpkFileEntry> entries;
271+
};
272+
273+
static std::vector<VpkInternalData> g_vpkInternals;
274+
static std::thread g_vpkThread;
275+
276+
static bool parseVpkDirectoryTree(const std::string &vpkPath, VpkInternalData *out) {
277+
FILE *fp = fopen(vpkPath.c_str(), "rb");
278+
if (!fp) return false;
279+
280+
uint32_t signature;
281+
uint32_t version;
282+
uint32_t treeSize;
283+
284+
if (fread(&signature, 4, 1, fp) != 1 ||
285+
fread(&version, 4, 1, fp) != 1 ||
286+
fread(&treeSize, 4, 1, fp) != 1) {
287+
fclose(fp);
288+
return false;
289+
}
290+
291+
if (signature != 0x55AA1234) {
292+
fclose(fp);
293+
return false;
294+
}
295+
296+
if (version == 2) {
297+
// Skip v2 extra header fields
298+
uint32_t dummy[4];
299+
if (fread(dummy, 4, 4, fp) != 4) {
300+
fclose(fp);
301+
return false;
302+
}
303+
} else if (version != 1) {
304+
fclose(fp);
305+
return false;
306+
}
307+
308+
out->vpkPath = vpkPath;
309+
out->entries.clear();
310+
311+
auto readString = [&](std::string &s) -> bool {
312+
s.clear();
313+
int c;
314+
while ((c = fgetc(fp)) != EOF) {
315+
if (c == '\0') return true;
316+
s += (char)c;
317+
}
318+
return false;
319+
};
320+
321+
std::string ext, dirpath, filename;
322+
323+
while (true) {
324+
if (!readString(ext) || ext.empty()) break;
325+
326+
while (true) {
327+
if (!readString(dirpath) || dirpath.empty()) break;
328+
329+
while (true) {
330+
if (!readString(filename) || filename.empty()) break;
331+
332+
uint32_t fileCrc;
333+
uint16_t preloadBytes;
334+
uint16_t archiveIndex;
335+
uint32_t entryOffset;
336+
uint32_t entryLength;
337+
uint16_t terminator;
338+
339+
if (fread(&fileCrc, 4, 1, fp) != 1 ||
340+
fread(&preloadBytes, 2, 1, fp) != 1 ||
341+
fread(&archiveIndex, 2, 1, fp) != 1 ||
342+
fread(&entryOffset, 4, 1, fp) != 1 ||
343+
fread(&entryLength, 4, 1, fp) != 1 ||
344+
fread(&terminator, 2, 1, fp) != 1) {
345+
fclose(fp);
346+
return false;
347+
}
348+
349+
if (preloadBytes > 0) {
350+
if (fseek(fp, preloadBytes, SEEK_CUR) != 0) {
351+
fclose(fp);
352+
return false;
353+
}
354+
}
355+
356+
std::string internalPath;
357+
if (dirpath != " ") {
358+
internalPath = dirpath + "/";
359+
}
360+
internalPath += filename + "." + ext;
361+
362+
out->entries.push_back({internalPath, fileCrc});
363+
}
364+
}
365+
}
366+
367+
fclose(fp);
368+
return true;
369+
}
370+
238371
ON_EVENT(SAR_UNLOAD) {
239372
for (size_t i = 0; i < NUM_FILE_SUM_THREADS; ++i) {
240373
if (g_sumthreads[i].joinable()) g_sumthreads[i].detach();
241374
}
375+
if (g_vpkThread.joinable()) g_vpkThread.detach();
242376
}
243377

244378
static void calcFileSums(std::map<std::string, uint32_t> *out, std::vector<std::string> paths) {
@@ -255,7 +389,34 @@ static void calcFileSums(std::map<std::string, uint32_t> *out, std::vector<std::
255389
}
256390
}
257391

392+
static void calcVpkInternals(std::vector<std::string> vpkPaths) {
393+
for (auto &vpkPath : vpkPaths) {
394+
uint32_t wholeFileCrc = 0;
395+
FILE *fp = fopen(vpkPath.c_str(), "rb");
396+
if (fp) {
397+
fileChecksum(fp, 0, &wholeFileCrc);
398+
fclose(fp);
399+
}
400+
401+
bool whitelisted = false;
402+
for (size_t i = 0; i < g_vpkWhitelistSize; ++i) {
403+
if (g_vpkWhitelist[i] == wholeFileCrc) {
404+
whitelisted = true;
405+
break;
406+
}
407+
}
408+
if (whitelisted) continue;
409+
410+
VpkInternalData vpkData;
411+
vpkData.wholeFileCrc = wholeFileCrc;
412+
if (parseVpkDirectoryTree(vpkPath, &vpkData)) {
413+
g_vpkInternals.push_back(std::move(vpkData));
414+
}
415+
}
416+
}
417+
258418
static void initFileSums() {
419+
std::vector<std::string> vpkDirPaths;
259420
std::vector<std::string> paths;
260421
try {
261422
auto searchpaths = fileSystem->GetSearchPaths();
@@ -285,6 +446,10 @@ static void initFileSums() {
285446
{
286447
paths.push_back(path);
287448
}
449+
450+
if (Utils::EndsWith(path, "_dir.vpk")) {
451+
vpkDirPaths.push_back(path);
452+
}
288453
}
289454
}
290455
}
@@ -297,6 +462,7 @@ static void initFileSums() {
297462
g_sumthreads[i] = std::thread(calcFileSums, &g_filesums[i], std::vector<std::string>(paths.begin() + idx, paths.begin() + end));
298463
idx = end;
299464
}
465+
g_vpkThread = std::thread(calcVpkInternals, std::move(vpkDirPaths));
300466
}
301467

302468
static void addFileChecksum(const char *path, uint32_t sum) {
@@ -311,6 +477,42 @@ static void addFileChecksum(const char *path, uint32_t sum) {
311477
delete[] buf;
312478
}
313479

480+
static void addVpkInternalChecksum(const VpkInternalData &vpk) {
481+
std::vector<uint8_t> data;
482+
data.push_back(0x11);
483+
484+
auto appendU32 = [&](uint32_t val) {
485+
data.push_back((val >> 0) & 0xFF);
486+
data.push_back((val >> 8) & 0xFF);
487+
data.push_back((val >> 16) & 0xFF);
488+
data.push_back((val >> 24) & 0xFF);
489+
};
490+
491+
auto appendStr = [&](const std::string &s) {
492+
data.insert(data.end(), s.begin(), s.end());
493+
data.push_back(0);
494+
};
495+
496+
appendU32(vpk.wholeFileCrc);
497+
appendStr(vpk.vpkPath);
498+
appendU32((uint32_t)vpk.entries.size());
499+
500+
for (auto &entry : vpk.entries) {
501+
appendU32(entry.crc);
502+
appendStr(entry.internalPath);
503+
}
504+
505+
engine->demorecorder->RecordData(data.data(), data.size());
506+
}
507+
508+
void AddDemoVpkChecksums() {
509+
if (g_vpkThread.joinable()) g_vpkThread.join();
510+
511+
for (auto &vpk : g_vpkInternals) {
512+
addVpkInternalChecksum(vpk);
513+
}
514+
}
515+
314516
void AddDemoFileChecksums() {
315517
// make sure all file sums are fully calculated first
316518
for (auto &thrd : g_sumthreads) {

src/Checksum.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
bool AddDemoChecksum(const char *filename);
77
void AddDemoFileChecksums();
8+
void AddDemoVpkChecksums();

src/Modules/EngineDemoPlayer.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ std::string EngineDemoPlayer::GetLevelName() {
171171
// 0x0E: entity slot serial changed
172172
// 0x0F: frametime cap detection
173173
// 0x10: queued commands
174+
// 0x11: VPK internal checksums
174175
void EngineDemoPlayer::CustomDemoData(char *data, size_t length) {
175176
if (data[0] == 0x03 || data[0] == 0x04) { // Entity input data
176177
std::optional<int> slot;

src/Modules/EngineDemoRecorder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ DETOUR(EngineDemoRecorder::SetSignonState, int state) {
173173
RecordQueuedCommands();
174174
engine->ExecuteCommand("echo \"SAR " SAR_VERSION " (Built " SAR_BUILT ")\"", true);
175175
AddDemoFileChecksums();
176+
AddDemoVpkChecksums();
176177
/*
177178
RecordInitialVal("host_timescale");
178179
RecordInitialVal("m_yaw");

0 commit comments

Comments
 (0)