@@ -235,10 +235,144 @@ bool AddDemoChecksum(const char *filename) {
235235static std::thread g_sumthreads[NUM_FILE_SUM_THREADS];
236236static 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+
238371ON_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
244378static 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+
258418static 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
302468static 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+
314516void AddDemoFileChecksums () {
315517 // make sure all file sums are fully calculated first
316518 for (auto &thrd : g_sumthreads) {
0 commit comments