Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1145 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //=============================================================================//
  9. #include "utlstring.h"
  10. #include "checksum_crc.h"
  11. #include "userid.h"
  12. #include "pure_server.h"
  13. #include "common.h"
  14. #include "tier1/KeyValues.h"
  15. #include "convar.h"
  16. #include "filesystem_engine.h"
  17. #include "server.h"
  18. #include "sv_filter.h"
  19. #include "tier1/UtlSortVector.h"
  20. // NOTE: This has to be the last file included!
  21. #include "tier0/memdbgon.h"
  22. extern ConVar sv_pure_consensus;
  23. extern ConVar sv_pure_retiretime;
  24. extern ConVar sv_pure_trace;
  25. CPureServerWhitelist::CCommand::CCommand()
  26. {
  27. }
  28. CPureServerWhitelist::CCommand::~CCommand()
  29. {
  30. }
  31. CPureServerWhitelist* CPureServerWhitelist::Create( IFileSystem *pFileSystem )
  32. {
  33. CPureServerWhitelist *pRet = new CPureServerWhitelist;
  34. pRet->Init( pFileSystem );
  35. return pRet;
  36. }
  37. CPureServerWhitelist::CPureServerWhitelist()
  38. {
  39. m_pFileSystem = NULL;
  40. m_LoadCounter = 0;
  41. m_RefCount = 1;
  42. }
  43. CPureServerWhitelist::~CPureServerWhitelist()
  44. {
  45. Term();
  46. }
  47. void CPureServerWhitelist::Init( IFileSystem *pFileSystem )
  48. {
  49. Term();
  50. m_pFileSystem = pFileSystem;
  51. }
  52. void CPureServerWhitelist::Load( int iPureMode )
  53. {
  54. Term();
  55. // Not pure at all?
  56. if ( iPureMode < 0 )
  57. return;
  58. // Load base trusted keys
  59. {
  60. KeyValues *kv = new KeyValues( "" );
  61. bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/trusted_keys_base.txt", "game" );
  62. if ( bLoaded )
  63. bLoaded = LoadTrustedKeysFromKeyValues( kv );
  64. else
  65. Warning( "Error loading cfg/trusted_keys_base.txt\n" );
  66. kv->deleteThis();
  67. }
  68. // sv_pure 0: minimal rules only
  69. if ( iPureMode == 0 )
  70. {
  71. KeyValues *kv = new KeyValues( "" );
  72. bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/pure_server_minimal.txt", "game" );
  73. if ( bLoaded )
  74. bLoaded = LoadCommandsFromKeyValues( kv );
  75. else
  76. Warning( "Error loading cfg/pure_server_minimal.txt\n" );
  77. kv->deleteThis();
  78. return;
  79. }
  80. // Load up full pure rules
  81. {
  82. KeyValues *kv = new KeyValues( "" );
  83. bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/pure_server_full.txt", "game" );
  84. if ( bLoaded )
  85. bLoaded = LoadCommandsFromKeyValues( kv );
  86. else
  87. Warning( "Error loading cfg/pure_server_full.txt\n" );
  88. kv->deleteThis();
  89. }
  90. // Now load user customizations
  91. if ( iPureMode == 1 )
  92. {
  93. // Load custom whitelist
  94. KeyValues *kv = new KeyValues( "" );
  95. bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/pure_server_whitelist.txt", "game" );
  96. if ( !bLoaded )
  97. // Check the old location
  98. bLoaded = kv->LoadFromFile( g_pFileSystem, "pure_server_whitelist.txt", "game" );
  99. if ( bLoaded )
  100. bLoaded = LoadCommandsFromKeyValues( kv );
  101. else
  102. Msg( "pure_server_whitelist.txt not present; pure server using only base file rules\n" );
  103. kv->deleteThis();
  104. // Load custom trusted keys
  105. kv = new KeyValues( "" );
  106. bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/trusted_keys.txt", "game" );
  107. if ( bLoaded )
  108. bLoaded = LoadTrustedKeysFromKeyValues( kv );
  109. else
  110. Msg( "trusted_keys.txt not present; pure server using only base trusted key list\n" );
  111. kv->deleteThis();
  112. }
  113. // Hardcoded rules last
  114. AddHardcodedFileCommands();
  115. }
  116. bool operator==( const PureServerPublicKey_t &a, const PureServerPublicKey_t &b )
  117. {
  118. return a.Count() == b.Count() && V_memcmp( a.Base(), b.Base(), a.Count() ) == 0;
  119. }
  120. bool CPureServerWhitelist::CommandDictDifferent( const CUtlDict<CCommand*,int> &a, const CUtlDict<CCommand*,int> &b )
  121. {
  122. FOR_EACH_DICT( a, idxA )
  123. {
  124. if ( a[idxA]->m_LoadOrder == kLoadOrder_HardcodedOverride )
  125. continue;
  126. int idxB = b.Find( a.GetElementName( idxA ) );
  127. if ( !b.IsValidIndex( idxB ) )
  128. return true;
  129. if ( b[idxB]->m_LoadOrder == kLoadOrder_HardcodedOverride )
  130. continue;
  131. if ( a[idxA]->m_eFileClass != b[idxB]->m_eFileClass
  132. || a[idxA]->m_LoadOrder != b[idxB]->m_LoadOrder )
  133. return true;
  134. }
  135. return false;
  136. }
  137. bool CPureServerWhitelist::operator==( const CPureServerWhitelist &x ) const
  138. {
  139. // Compare rule dictionaries
  140. if ( CommandDictDifferent( m_FileCommands, x.m_FileCommands )
  141. || CommandDictDifferent( x.m_FileCommands, m_FileCommands )
  142. || CommandDictDifferent( m_RecursiveDirCommands, x.m_RecursiveDirCommands )
  143. || CommandDictDifferent( x.m_RecursiveDirCommands, m_RecursiveDirCommands )
  144. || CommandDictDifferent( m_NonRecursiveDirCommands, x.m_NonRecursiveDirCommands )
  145. || CommandDictDifferent( x.m_NonRecursiveDirCommands, m_NonRecursiveDirCommands ) )
  146. return false;
  147. // Compare trusted key list
  148. if ( m_vecTrustedKeys.Count() != x.m_vecTrustedKeys.Count() )
  149. return false;
  150. for ( int i = 0 ; i < m_vecTrustedKeys.Count() ; ++i )
  151. if ( !( m_vecTrustedKeys[i] == x.m_vecTrustedKeys[i] ) )
  152. return false;
  153. // they are the same
  154. return true;
  155. }
  156. void CPureServerWhitelist::Term()
  157. {
  158. m_FileCommands.PurgeAndDeleteElements();
  159. m_RecursiveDirCommands.PurgeAndDeleteElements();
  160. m_NonRecursiveDirCommands.PurgeAndDeleteElements();
  161. m_vecTrustedKeys.Purge();
  162. m_LoadCounter = 0;
  163. }
  164. bool CPureServerWhitelist::LoadCommandsFromKeyValues( KeyValues *kv )
  165. {
  166. for ( KeyValues *pCurItem = kv->GetFirstValue(); pCurItem; pCurItem = pCurItem->GetNextValue() )
  167. {
  168. char szPathName[ MAX_PATH ];
  169. const char *pKeyValue = pCurItem->GetName();
  170. const char *pModifiers = pCurItem->GetString();
  171. if ( !pKeyValue || !pModifiers )
  172. continue;
  173. Q_strncpy( szPathName, pKeyValue, sizeof(szPathName) );
  174. Q_FixSlashes( szPathName );
  175. const char *pValue = szPathName;
  176. // Figure out the modifiers.
  177. bool bFromTrustedSource = false, bAllowFromDisk = false, bCheckCRC = false, bAny = false;
  178. CUtlVector<char*> mods;
  179. V_SplitString( pModifiers, "+", mods );
  180. for ( int i=0; i < mods.Count(); i++ )
  181. {
  182. if (
  183. V_stricmp( mods[i], "from_steam" ) == 0
  184. || V_stricmp( mods[i], "trusted_source" ) == 0
  185. )
  186. bFromTrustedSource = true;
  187. else if ( V_stricmp( mods[i], "allow_from_disk" ) == 0 )
  188. bAllowFromDisk = true;
  189. else if (
  190. V_stricmp( mods[i], "check_crc" ) == 0
  191. || V_stricmp( mods[i], "check_hash" ) == 0
  192. )
  193. bCheckCRC = true;
  194. else if ( V_stricmp( mods[i], "any" ) == 0 )
  195. bAny = true;
  196. else
  197. Warning( "Unknown modifier in whitelist file: %s.\n", mods[i] );
  198. }
  199. mods.PurgeAndDeleteElements();
  200. if (
  201. ( bFromTrustedSource && ( bAllowFromDisk || bCheckCRC || bAny ) )
  202. || ( bAny && bCheckCRC ) )
  203. {
  204. Warning( "Whitelist: incompatible flags used on %s.\n", pValue );
  205. continue;
  206. }
  207. EPureServerFileClass eFileClass;
  208. if ( bCheckCRC )
  209. eFileClass = ePureServerFileClass_CheckHash;
  210. else if ( bFromTrustedSource )
  211. eFileClass = ePureServerFileClass_AnyTrusted;
  212. else
  213. eFileClass = ePureServerFileClass_Any;
  214. // Setup a new CCommand to hold this command.
  215. AddFileCommand( pValue, eFileClass, m_LoadCounter++ );
  216. }
  217. return true;
  218. }
  219. void CPureServerWhitelist::AddHardcodedFileCommands()
  220. {
  221. AddFileCommand( "materials/vgui/replay/thumbnails/...", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride );
  222. AddFileCommand( "sound/ui/hitsound.wav", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride );
  223. AddFileCommand( "sound/ui/killsound.wav", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride );
  224. AddFileCommand( "materials/vgui/logos/...", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride );
  225. }
  226. void CPureServerWhitelist::AddFileCommand( const char *pszFilePath, EPureServerFileClass eFileClass, unsigned short nLoadOrder )
  227. {
  228. CPureServerWhitelist::CCommand *pCommand = new CPureServerWhitelist::CCommand;
  229. pCommand->m_LoadOrder = nLoadOrder;
  230. pCommand->m_eFileClass = eFileClass;
  231. // Figure out if they're referencing a file, a recursive directory, or a nonrecursive directory.
  232. CUtlDict<CCommand*,int> *pList;
  233. const char *pEndPart = V_UnqualifiedFileName( pszFilePath );
  234. if ( Q_stricmp( pEndPart, "..." ) == 0 )
  235. pList = &m_RecursiveDirCommands;
  236. else if ( Q_stricmp( pEndPart, "*.*" ) == 0 )
  237. pList = &m_NonRecursiveDirCommands;
  238. else
  239. pList = &m_FileCommands;
  240. // If it's a directory command, get rid of the *.* or ...
  241. char filePath[MAX_PATH];
  242. if ( pList == &m_RecursiveDirCommands || pList == &m_NonRecursiveDirCommands )
  243. V_ExtractFilePath( pszFilePath, filePath, sizeof( filePath ) );
  244. else
  245. V_strncpy( filePath, pszFilePath, sizeof( filePath ) );
  246. V_FixSlashes( filePath );
  247. int idxExisting = pList->Find( filePath );
  248. if ( idxExisting != pList->InvalidIndex() )
  249. {
  250. delete pList->Element( idxExisting );
  251. pList->RemoveAt( idxExisting );
  252. }
  253. pList->Insert( filePath, pCommand );
  254. }
  255. bool CPureServerWhitelist::LoadTrustedKeysFromKeyValues( KeyValues *kv )
  256. {
  257. for ( KeyValues *pCurItem = kv->GetFirstTrueSubKey(); pCurItem; pCurItem = pCurItem->GetNextTrueSubKey() )
  258. {
  259. if ( V_stricmp( pCurItem->GetName(), "public_key" ) != 0 )
  260. {
  261. Warning( "Trusted key list has unexpected block '%s'; expected only 'public_key' blocks\n", pCurItem->GetName() );
  262. continue;
  263. }
  264. const char *pszType = pCurItem->GetString( "type", "(none)" );
  265. if ( V_stricmp( pszType, "rsa" ) != 0 )
  266. {
  267. Warning( "Trusted key type '%s' not supported.\n", pszType );
  268. continue;
  269. }
  270. const char *pszKeyData = pCurItem->GetString( "rsa_public_key", "" );
  271. if ( *pszKeyData == '\0' )
  272. {
  273. Warning( "trusted key is missing 'rsa_public_key' data; ignored\n" );
  274. continue;
  275. }
  276. PureServerPublicKey_t &key = m_vecTrustedKeys[ m_vecTrustedKeys.AddToTail() ];
  277. int nKeyDataLen = V_strlen( pszKeyData );
  278. key.SetSize( nKeyDataLen / 2 );
  279. // Aaaannnnnnnnddddd V_hextobinary has no return code.
  280. // Because nobody could *ever* possible attempt to parse bad data. It could never possibly happen.
  281. V_hextobinary( pszKeyData, nKeyDataLen, key.Base(), key.Count() );
  282. }
  283. return true;
  284. }
  285. void CPureServerWhitelist::UpdateCommandStats( CUtlDict<CPureServerWhitelist::CCommand*,int> &commands, int *pHighest, int *pLongestPathName )
  286. {
  287. for ( int i=commands.First(); i != commands.InvalidIndex(); i=commands.Next( i ) )
  288. {
  289. *pHighest = max( *pHighest, (int)commands[i]->m_LoadOrder );
  290. int len = V_strlen( commands.GetElementName( i ) );
  291. *pLongestPathName = max( *pLongestPathName, len );
  292. }
  293. }
  294. void CPureServerWhitelist::PrintCommand( const char *pFileSpec, const char *pExt, int maxPathnameLen, CPureServerWhitelist::CCommand *pCommand )
  295. {
  296. // Get rid of the trailing slash if there is one.
  297. char tempFileSpec[MAX_PATH];
  298. V_strncpy( tempFileSpec, pFileSpec, sizeof( tempFileSpec ) );
  299. int len = V_strlen( tempFileSpec );
  300. if ( len > 0 && (tempFileSpec[len-1] == '/' || tempFileSpec[len-1] == '\\') )
  301. tempFileSpec[len-1] = 0;
  302. CUtlString buf;
  303. if ( pExt )
  304. buf.Format( "%s%c%s", tempFileSpec, CORRECT_PATH_SEPARATOR, pExt );
  305. else
  306. buf.Format( "%s", tempFileSpec );
  307. len = V_strlen( pFileSpec );
  308. for ( int i=len; i < maxPathnameLen+6; i++ )
  309. {
  310. buf += " ";
  311. }
  312. buf += "\t";
  313. switch ( pCommand->m_eFileClass )
  314. {
  315. default:
  316. buf += va( "(bogus value %d)", (int)pCommand->m_eFileClass );
  317. Assert( false );
  318. break;
  319. case ePureServerFileClass_Any:
  320. buf += "any";
  321. break;
  322. case ePureServerFileClass_AnyTrusted:
  323. buf += "trusted_source";
  324. break;
  325. case ePureServerFileClass_CheckHash:
  326. buf += "check_crc";
  327. break;
  328. }
  329. Msg( "%s\n", buf.String() );
  330. }
  331. int CPureServerWhitelist::FindCommandByLoadOrder( CUtlDict<CPureServerWhitelist::CCommand*,int> &commands, int iLoadOrder )
  332. {
  333. for ( int i=commands.First(); i != commands.InvalidIndex(); i=commands.Next( i ) )
  334. {
  335. if ( commands[i]->m_LoadOrder == iLoadOrder )
  336. return i;
  337. }
  338. return -1;
  339. }
  340. void CPureServerWhitelist::PrintWhitelistContents()
  341. {
  342. int highestLoadOrder = 0, longestPathName = 0;
  343. UpdateCommandStats( m_FileCommands, &highestLoadOrder, &longestPathName );
  344. UpdateCommandStats( m_RecursiveDirCommands, &highestLoadOrder, &longestPathName );
  345. UpdateCommandStats( m_NonRecursiveDirCommands, &highestLoadOrder, &longestPathName );
  346. for ( int iLoadOrder=0; iLoadOrder <= highestLoadOrder; iLoadOrder++ )
  347. {
  348. // Check regular file commands.
  349. int iCommand = FindCommandByLoadOrder( m_FileCommands, iLoadOrder );
  350. if ( iCommand != -1 )
  351. {
  352. PrintCommand( m_FileCommands.GetElementName( iCommand ), NULL, longestPathName, m_FileCommands[iCommand] );
  353. }
  354. else
  355. {
  356. // Check recursive commands.
  357. iCommand = FindCommandByLoadOrder( m_RecursiveDirCommands, iLoadOrder );
  358. if ( iCommand != -1 )
  359. {
  360. PrintCommand( m_RecursiveDirCommands.GetElementName( iCommand ), "...", longestPathName, m_RecursiveDirCommands[iCommand] );
  361. }
  362. else
  363. {
  364. // Check *.* commands.
  365. iCommand = FindCommandByLoadOrder( m_NonRecursiveDirCommands, iLoadOrder );
  366. if ( iCommand != -1 )
  367. {
  368. PrintCommand( m_NonRecursiveDirCommands.GetElementName( iCommand ), "*.*", longestPathName, m_NonRecursiveDirCommands[iCommand] );
  369. }
  370. }
  371. }
  372. }
  373. }
  374. void CPureServerWhitelist::Encode( CUtlBuffer &buf )
  375. {
  376. // Put dummy version number
  377. buf.PutUnsignedInt( 0xffff );
  378. // Encode rules
  379. EncodeCommandList( m_FileCommands, buf );
  380. EncodeCommandList( m_RecursiveDirCommands, buf );
  381. EncodeCommandList( m_NonRecursiveDirCommands, buf );
  382. // Encode trusted keys
  383. buf.PutUnsignedInt( m_vecTrustedKeys.Count() );
  384. FOR_EACH_VEC( m_vecTrustedKeys, i )
  385. {
  386. uint32 nKeySize = m_vecTrustedKeys[i].Count();
  387. buf.PutUnsignedInt( nKeySize );
  388. buf.Put( m_vecTrustedKeys[i].Base(), nKeySize );
  389. }
  390. }
  391. void CPureServerWhitelist::EncodeCommandList( CUtlDict<CPureServerWhitelist::CCommand*,int> &theList, CUtlBuffer &buf )
  392. {
  393. // Count how many we're really going to write
  394. int nCount = 0;
  395. for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) )
  396. {
  397. if ( theList[i]->m_LoadOrder != kLoadOrder_HardcodedOverride )
  398. ++nCount;
  399. }
  400. buf.PutInt( nCount );
  401. // Write them
  402. for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) )
  403. {
  404. CPureServerWhitelist::CCommand *pCommand = theList[i];
  405. if ( pCommand->m_LoadOrder == kLoadOrder_HardcodedOverride )
  406. continue;
  407. unsigned char val = (unsigned char)pCommand->m_eFileClass;
  408. buf.PutUnsignedChar( val );
  409. buf.PutUnsignedShort( pCommand->m_LoadOrder );
  410. buf.PutString( theList.GetElementName( i ) );
  411. }
  412. }
  413. void CPureServerWhitelist::Decode( CUtlBuffer &buf )
  414. {
  415. Term();
  416. uint32 nVersionTag = *(uint32 *)buf.PeekGet();
  417. uint32 nFormatVersion = 0;
  418. if ( nVersionTag == 0xffff )
  419. {
  420. buf.GetUnsignedInt();
  421. nFormatVersion = 1;
  422. }
  423. else
  424. {
  425. // Talking to legacy server -- load up default rules,
  426. // the rest of his list are supposed to be exceptions to
  427. // the base
  428. Load( 2 );
  429. }
  430. DecodeCommandList( m_FileCommands, buf, nFormatVersion );
  431. DecodeCommandList( m_RecursiveDirCommands, buf, nFormatVersion );
  432. DecodeCommandList( m_NonRecursiveDirCommands, buf, nFormatVersion );
  433. // Hardcoded
  434. AddHardcodedFileCommands();
  435. if ( nFormatVersion >= 1 )
  436. {
  437. uint32 nKeyCount = buf.GetUnsignedInt();
  438. m_vecTrustedKeys.SetCount( nKeyCount );
  439. FOR_EACH_VEC( m_vecTrustedKeys, i )
  440. {
  441. uint32 nKeySize = buf.GetUnsignedInt();
  442. m_vecTrustedKeys[i].SetCount( nKeySize );
  443. buf.Get( m_vecTrustedKeys[i].Base(), nKeySize );
  444. }
  445. }
  446. }
  447. void CPureServerWhitelist::CacheFileCRCs()
  448. {
  449. InternalCacheFileCRCs( m_FileCommands, k_eCacheCRCType_SingleFile );
  450. InternalCacheFileCRCs( m_NonRecursiveDirCommands, k_eCacheCRCType_Directory );
  451. InternalCacheFileCRCs( m_RecursiveDirCommands, k_eCacheCRCType_Directory_Recursive );
  452. }
  453. // !SV_PURE FIXME! Do we need this?
  454. void CPureServerWhitelist::InternalCacheFileCRCs( CUtlDict<CCommand*,int> &theList, ECacheCRCType eType )
  455. {
  456. // for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) )
  457. // {
  458. // CCommand *pCommand = theList[i];
  459. // if ( pCommand->m_bCheckCRC )
  460. // {
  461. // const char *pPathname = theList.GetElementName( i );
  462. // m_pFileSystem->CacheFileCRCs( pPathname, eType, &m_ForceMatchList );
  463. // }
  464. // }
  465. }
  466. void CPureServerWhitelist::DecodeCommandList( CUtlDict<CPureServerWhitelist::CCommand*,int> &theList, CUtlBuffer &buf, uint32 nFormatVersion )
  467. {
  468. int nCommands = buf.GetInt();
  469. for ( int i=0; i < nCommands; i++ )
  470. {
  471. CPureServerWhitelist::CCommand *pCommand = new CPureServerWhitelist::CCommand;
  472. unsigned char val = buf.GetUnsignedChar();
  473. unsigned short nLoadOrder = buf.GetUnsignedShort();
  474. if ( nFormatVersion == 0 )
  475. {
  476. pCommand->m_eFileClass = ( val & 1 ) ? ePureServerFileClass_Any : ePureServerFileClass_AnyTrusted;
  477. pCommand->m_LoadOrder = nLoadOrder + m_LoadCounter;
  478. }
  479. else
  480. {
  481. pCommand->m_eFileClass = (EPureServerFileClass)val;
  482. pCommand->m_LoadOrder = nLoadOrder;
  483. }
  484. char str[MAX_PATH];
  485. buf.GetString( str );
  486. V_FixSlashes( str );
  487. theList.Insert( str, pCommand );
  488. }
  489. }
  490. CPureServerWhitelist::CCommand* CPureServerWhitelist::GetBestEntry( const char *pFilename )
  491. {
  492. // NOTE: Since this is a user-specified file, we don't have the added complexity of path IDs in here.
  493. // So when the filesystem asks if a file is in the whitelist, we just ignore the path ID.
  494. // Make sure we have a relative pathname with fixed slashes..
  495. char relativeFilename[MAX_PATH];
  496. V_strncpy( relativeFilename, pFilename, sizeof( relativeFilename ) );
  497. // Convert the path to relative if necessary.
  498. if ( !V_IsAbsolutePath( relativeFilename ) || m_pFileSystem->FullPathToRelativePath( pFilename, relativeFilename, sizeof( relativeFilename ) ) )
  499. {
  500. V_FixSlashes( relativeFilename );
  501. // Get the directory this thing is in.
  502. char relativeDir[MAX_PATH];
  503. if ( !V_ExtractFilePath( relativeFilename, relativeDir, sizeof( relativeDir ) ) )
  504. relativeDir[0] = 0;
  505. // Check each of our dictionaries to see if there is an entry for this thing.
  506. CCommand *pBestEntry = NULL;
  507. pBestEntry = CheckEntry( m_FileCommands, relativeFilename, pBestEntry );
  508. if ( relativeDir[0] != 0 )
  509. {
  510. pBestEntry = CheckEntry( m_NonRecursiveDirCommands, relativeDir, pBestEntry );
  511. while ( relativeDir[0] != 0 )
  512. {
  513. // Check for this directory.
  514. pBestEntry = CheckEntry( m_RecursiveDirCommands, relativeDir, pBestEntry );
  515. if ( !V_StripLastDir( relativeDir, sizeof( relativeDir ) ) )
  516. break;
  517. }
  518. }
  519. return pBestEntry;
  520. }
  521. // Either we couldn't find an entry, or they specified an absolute path that we could not convert to a relative path.
  522. return NULL;
  523. }
  524. CPureServerWhitelist::CCommand* CPureServerWhitelist::CheckEntry(
  525. CUtlDict<CPureServerWhitelist::CCommand*,int> &dict,
  526. const char *pEntryName,
  527. CPureServerWhitelist::CCommand *pBestEntry )
  528. {
  529. int i = dict.Find( pEntryName );
  530. if ( i != dict.InvalidIndex() && (!pBestEntry || dict[i]->m_LoadOrder > pBestEntry->m_LoadOrder) )
  531. pBestEntry = dict[i];
  532. return pBestEntry;
  533. }
  534. void CPureServerWhitelist::AddRef()
  535. {
  536. ThreadInterlockedIncrement( &m_RefCount );
  537. }
  538. void CPureServerWhitelist::Release()
  539. {
  540. if ( ThreadInterlockedDecrement( &m_RefCount ) <= 0 )
  541. delete this;
  542. }
  543. int CPureServerWhitelist::GetTrustedKeyCount() const
  544. {
  545. return m_vecTrustedKeys.Count();
  546. }
  547. const byte *CPureServerWhitelist::GetTrustedKey( int iKeyIndex, int *nKeySize ) const
  548. {
  549. Assert( nKeySize != NULL );
  550. if ( !m_vecTrustedKeys.IsValidIndex( iKeyIndex ) )
  551. {
  552. *nKeySize = 0;
  553. return NULL;
  554. }
  555. *nKeySize = m_vecTrustedKeys[iKeyIndex].Count();
  556. return m_vecTrustedKeys[iKeyIndex].Base();
  557. }
  558. EPureServerFileClass CPureServerWhitelist::GetFileClass( const char *pszFilename )
  559. {
  560. CCommand *pCommand = GetBestEntry( pszFilename );
  561. if ( pCommand )
  562. return pCommand->m_eFileClass;
  563. // Default action is to be permissive. (The default whitelist protects certain directories and files at a root level.)
  564. return ePureServerFileClass_Any;
  565. }
  566. void CPureFileTracker::AddUserReportedFileHash( int idxFile, FileHash_t *pFileHash, USERID_t userID, bool bAddMasterRecord )
  567. {
  568. UserReportedFileHash_t userFileHash;
  569. userFileHash.m_idxFile = idxFile;
  570. userFileHash.m_userID = userID;
  571. userFileHash.m_FileHash = *pFileHash;
  572. int idxUserReported = m_treeUserReportedFileHash.Find( userFileHash );
  573. if ( idxUserReported == m_treeUserReportedFileHash.InvalidIndex() )
  574. {
  575. idxUserReported = m_treeUserReportedFileHash.Insert( userFileHash );
  576. if ( bAddMasterRecord )
  577. {
  578. // count the number of matches for this idxFile
  579. // if it exceeds > 5 then make a master record
  580. int idxFirst = idxUserReported;
  581. int idxLast = idxUserReported;
  582. int ctMatches = 1;
  583. int ctTotalFiles = 1;
  584. // first go forward
  585. int idx = m_treeUserReportedFileHash.NextInorder( idxUserReported );
  586. while ( idx != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxUserReported].m_idxFile )
  587. {
  588. if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash )
  589. ctMatches++;
  590. ctTotalFiles++;
  591. idxLast = idx;
  592. idx = m_treeUserReportedFileHash.NextInorder( idx );
  593. }
  594. // then backwards
  595. idx = m_treeUserReportedFileHash.PrevInorder( idxUserReported );
  596. while ( idx != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxUserReported].m_idxFile )
  597. {
  598. if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash )
  599. ctMatches++;
  600. ctTotalFiles++;
  601. idxFirst = idx;
  602. idx = m_treeUserReportedFileHash.PrevInorder( idx );
  603. }
  604. // if ctTotalFiles >> ctMatches then that means clients are reading different bits from the file.
  605. // in order to get this right we need to ask them to read the entire thing
  606. if ( ctMatches >= sv_pure_consensus.GetInt() )
  607. {
  608. MasterFileHash_t masterFileHashNew;
  609. masterFileHashNew.m_idxFile = m_treeUserReportedFileHash[idxUserReported].m_idxFile;
  610. masterFileHashNew.m_cMatches = ctMatches;
  611. masterFileHashNew.m_FileHash = m_treeUserReportedFileHash[idxUserReported].m_FileHash;
  612. m_treeMasterFileHashes.Insert( masterFileHashNew );
  613. // remove all the individual records that matched the new master, we don't need them anymore
  614. int idxRemove = idxFirst;
  615. while ( idxRemove != m_treeUserReportedFileHash.InvalidIndex() )
  616. {
  617. int idxNext = m_treeUserReportedFileHash.NextInorder( idxRemove );
  618. if ( m_treeUserReportedFileHash[idxRemove].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash )
  619. m_treeUserReportedFileHash.RemoveAt( idxRemove );
  620. if ( idxRemove == idxLast )
  621. break;
  622. idxRemove = idxNext;
  623. }
  624. }
  625. }
  626. }
  627. else
  628. {
  629. m_treeUserReportedFileHash[idxUserReported].m_FileHash = *pFileHash;
  630. }
  631. // we dont have enough data to decide if you match or not yet - so we call it a match
  632. }
  633. void FileRenderHelper( USERID_t userID, const char *pchMessage, const char *pchPath, const char *pchFileName, FileHash_t *pFileHash, int nFileFraction, FileHash_t *pFileHashLocal )
  634. {
  635. char rgch[256];
  636. char hex[ 34 ];
  637. Q_memset( hex, 0, sizeof( hex ) );
  638. Q_binarytohex( (const byte *)&pFileHash->m_md5contents.bits, sizeof( pFileHash->m_md5contents.bits ), hex, sizeof( hex ) );
  639. char hex2[ 34 ];
  640. Q_memset( hex2, 0, sizeof( hex2 ) );
  641. if ( pFileHashLocal )
  642. Q_binarytohex( (const byte *)&pFileHashLocal->m_md5contents.bits, sizeof( pFileHashLocal->m_md5contents.bits ), hex2, sizeof( hex2 ) );
  643. if ( pFileHash->m_PackFileID )
  644. {
  645. Q_snprintf( rgch, 256, "Pure server: file: %s\\%s ( %d %d %8.8x %6.6x ) %s : %s : %s\n",
  646. pchPath, pchFileName,
  647. pFileHash->m_PackFileID, pFileHash->m_nPackFileNumber, nFileFraction, pFileHash->m_cbFileLen,
  648. pchMessage,
  649. hex, hex2 );
  650. }
  651. else
  652. {
  653. Q_snprintf( rgch, 256, "Pure server: file: %s\\%s ( %d %d %x ) %s : %s : %s\n",
  654. pchPath, pchFileName,
  655. pFileHash->m_eFileHashType, pFileHash->m_cbFileLen, pFileHash->m_eFileHashType ? pFileHash->m_crcIOSequence : 0,
  656. pchMessage,
  657. hex, hex2 );
  658. }
  659. if ( userID.idtype != 0 )
  660. Msg( "[%s] %s\n", GetUserIDString(userID), rgch );
  661. else
  662. Msg( "%s", rgch );
  663. }
  664. bool CPureFileTracker::DoesFileMatch( const char *pPathID, const char *pRelativeFilename, int nFileFraction, FileHash_t *pFileHash, USERID_t userID )
  665. {
  666. // // if the server has been idle for more than 15 minutes, discard all this data
  667. // const float flRetireTime = sv_pure_retiretime.GetFloat();
  668. // float flCurTime = Plat_FloatTime();
  669. // if ( ( flCurTime - m_flLastFileReceivedTime ) > flRetireTime )
  670. // {
  671. // m_treeMasterFileHashes.RemoveAll();
  672. // m_treeUserReportedFileHash.RemoveAll();
  673. // m_treeMasterFileHashes.RemoveAll();
  674. // }
  675. // m_flLastFileReceivedTime = flCurTime;
  676. //
  677. // // The clients must send us all files. We decide if it is whitelisted or not
  678. // // That way the clients can not hide modified files in a whitelisted directory
  679. // if ( pFileHash->m_PackFileID == 0 &&
  680. // sv.GetPureServerWhitelist()->GetFileClass( pRelativeFilename ) != ePureServerFileClass_CheckHash )
  681. // {
  682. //
  683. // if ( sv_pure_trace.GetInt() == 4 )
  684. // {
  685. // char warningStr[1024] = {0};
  686. // V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s ignored by whitelist.", pPathID, pRelativeFilename );
  687. // Msg( "[%s] %s\n", GetUserIDString(userID), warningStr );
  688. // }
  689. //
  690. // return true;
  691. // }
  692. //
  693. // char rgchFilenameFixed[MAX_PATH];
  694. // Q_strncpy( rgchFilenameFixed, pRelativeFilename, sizeof( rgchFilenameFixed ) );
  695. // Q_FixSlashes( rgchFilenameFixed );
  696. //
  697. // // first look up the file and see if we have ever seen it before
  698. // CRC32_t crcFilename;
  699. // CRC32_Init( &crcFilename );
  700. // CRC32_ProcessBuffer( &crcFilename, rgchFilenameFixed, Q_strlen( rgchFilenameFixed ) );
  701. // CRC32_ProcessBuffer( &crcFilename, pPathID, Q_strlen( pPathID ) );
  702. // CRC32_Final( &crcFilename );
  703. // UserReportedFile_t ufile;
  704. // ufile.m_crcIdentifier = crcFilename;
  705. // ufile.m_filename = rgchFilenameFixed;
  706. // ufile.m_path = pPathID;
  707. // ufile.m_nFileFraction = nFileFraction;
  708. // int idxFile = m_treeAllReportedFiles.Find( ufile );
  709. // if ( idxFile == m_treeAllReportedFiles.InvalidIndex() )
  710. // {
  711. // idxFile = m_treeAllReportedFiles.Insert( ufile );
  712. // }
  713. // else
  714. // {
  715. // m_cMatchedFile++;
  716. // }
  717. // // then check if we have a master CRC for the file
  718. // MasterFileHash_t masterFileHash;
  719. // masterFileHash.m_idxFile = idxFile;
  720. // int idxMaster = m_treeMasterFileHashes.Find( masterFileHash );
  721. // // dont do anything with this yet
  722. //
  723. // // check to see if we have loaded the file locally and can match it
  724. // FileHash_t filehashLocal;
  725. // EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal );
  726. // if ( eStatus == k_eFileCRCStatus_FileInVPK)
  727. // {
  728. // // you managed to load a file outside a VPK that the server has in the VPK
  729. // // this is possible if the user explodes the VPKs into individual files and then deletes the VPKs
  730. // FileRenderHelper( userID, "file should be in VPK", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  731. // return false;
  732. // }
  733. // // if the user sent us a full file hash, but we dont have one, hash it now
  734. // if ( pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  735. // ( eStatus != k_eFileCRCStatus_GotCRC || filehashLocal.m_eFileHashType != FileHash_t::k_EFileHashTypeEntireFile ) )
  736. // {
  737. // // lets actually read the file so we get a complete file hash
  738. // FileHandle_t f = g_pFileSystem->Open( rgchFilenameFixed, "rb", pPathID);
  739. // // try to load the file and really compute the hash - should only have to do this once ever
  740. // if ( f )
  741. // {
  742. // // load file into a null-terminated buffer
  743. // int fileSize = g_pFileSystem->Size( f );
  744. // unsigned bufSize = g_pFileSystem->GetOptimalReadSize( f, fileSize );
  745. //
  746. // char *buffer = (char*)g_pFileSystem->AllocOptimalReadBuffer( f, bufSize );
  747. // Assert( buffer );
  748. //
  749. // // read into local buffer
  750. // bool bRetOK = ( g_pFileSystem->ReadEx( buffer, bufSize, fileSize, f ) != 0 );
  751. // bRetOK;
  752. // g_pFileSystem->FreeOptimalReadBuffer( buffer );
  753. //
  754. // g_pFileSystem->Close( f ); // close file after reading
  755. //
  756. // eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal );
  757. // }
  758. // else
  759. // {
  760. // // what should we do if we couldn't open the file? should probably kick
  761. // FileRenderHelper( userID, "could not open file to hash ( benign for now )", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  762. // }
  763. // }
  764. // if ( eStatus == k_eFileCRCStatus_GotCRC )
  765. // {
  766. // if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  767. // pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile )
  768. // {
  769. // if ( filehashLocal == *pFileHash )
  770. // {
  771. // m_cMatchedFileFullHash++;
  772. // return true;
  773. // }
  774. // else
  775. // {
  776. // // don't need to check anything else
  777. // // did not match - record so that we have a record of the file that did not match ( just for reporting )
  778. // AddUserReportedFileHash( idxFile, pFileHash, userID, false );
  779. // FileRenderHelper( userID, "file does not match", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, &filehashLocal );
  780. // return false;
  781. // }
  782. // }
  783. // }
  784. //
  785. // // if this is a VPK file, we have completely cataloged all the VPK files, so no suprises are allowed
  786. // if ( pFileHash->m_PackFileID )
  787. // {
  788. // AddUserReportedFileHash( idxFile, pFileHash, userID, false );
  789. // FileRenderHelper( userID, "unrecognized vpk file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  790. // return false;
  791. // }
  792. //
  793. //
  794. // // now lets see if we have a master file hash for this
  795. // if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() )
  796. // {
  797. // m_cMatchedMasterFile++;
  798. //
  799. // FileHash_t *pFileHashLocal = &m_treeMasterFileHashes[idxMaster].m_FileHash;
  800. // if ( *pFileHashLocal == *pFileHash )
  801. // {
  802. // m_cMatchedMasterFileHash++;
  803. // return true;
  804. // }
  805. // else
  806. // {
  807. // // did not match - record so that we have a record of the file that did not match ( just for reporting )
  808. // AddUserReportedFileHash( idxFile, pFileHash, userID, false );
  809. // // and then return failure
  810. // FileRenderHelper( userID, "file does not match server master file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, pFileHashLocal );
  811. // return false;
  812. // }
  813. // }
  814. //
  815. // // no master record, accumulate individual record so we can get a consensus
  816. // if ( sv_pure_trace.GetInt() == 3 )
  817. // {
  818. // FileRenderHelper( userID, "server does not have hash for this file. Waiting for consensus", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  819. // }
  820. //
  821. // AddUserReportedFileHash( idxFile, pFileHash, userID, true );
  822. // we dont have enough data to decide if you match or not yet - so we call it a match
  823. return true;
  824. }
  825. struct FindFileIndex_t
  826. {
  827. int idxFindFile;
  828. };
  829. class CStupidLess
  830. {
  831. public:
  832. bool Less( const FindFileIndex_t &src1, const FindFileIndex_t &src2, void *pCtx )
  833. {
  834. if ( src1.idxFindFile < src2.idxFindFile )
  835. return true;
  836. return false;
  837. }
  838. };
  839. int CPureFileTracker::ListUserFiles( bool bListAll, const char *pchFilenameFind )
  840. {
  841. CUtlSortVector< FindFileIndex_t, CStupidLess > m_vecReportedFiles;
  842. int idxFindFile = m_treeAllReportedFiles.FirstInorder();
  843. while ( idxFindFile != m_treeAllReportedFiles.InvalidIndex() )
  844. {
  845. UserReportedFile_t &ufile = m_treeAllReportedFiles[idxFindFile];
  846. if ( pchFilenameFind && Q_stristr( ufile.m_filename, pchFilenameFind ) )
  847. {
  848. FileHash_t filehashLocal;
  849. EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( ufile.m_path.String(), ufile.m_filename.String(), 0, &filehashLocal );
  850. if ( eStatus == k_eFileCRCStatus_GotCRC )
  851. {
  852. USERID_t useridFake;
  853. useridFake.idtype = IDTYPE_STEAM;
  854. FileRenderHelper( useridFake, "Found: ",ufile.m_path.String(),ufile.m_filename.String(), &filehashLocal, 0, NULL );
  855. FindFileIndex_t ffi;
  856. ffi.idxFindFile = idxFindFile;
  857. m_vecReportedFiles.Insert( ffi );
  858. }
  859. else
  860. {
  861. Msg( "File not found %s %s %x\n", ufile.m_filename.String(), ufile.m_path.String(), idxFindFile );
  862. }
  863. }
  864. idxFindFile = m_treeAllReportedFiles.NextInorder( idxFindFile );
  865. }
  866. int cTotalFiles = 0;
  867. int cTotalMatches = 0;
  868. int idx = m_treeUserReportedFileHash.FirstInorder();
  869. while ( idx != m_treeUserReportedFileHash.InvalidIndex() )
  870. {
  871. UserReportedFileHash_t &file = m_treeUserReportedFileHash[idx];
  872. int idxNext = m_treeUserReportedFileHash.NextInorder( idx );
  873. int ctMatches = 1;
  874. int ctFiles = 1;
  875. // check this against all others for the same file
  876. while ( idxNext != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxNext].m_idxFile )
  877. {
  878. if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxNext].m_FileHash )
  879. {
  880. ctMatches++;
  881. cTotalMatches++;
  882. }
  883. ctFiles++;
  884. idxNext = m_treeUserReportedFileHash.NextInorder( idxNext );
  885. }
  886. idx = m_treeUserReportedFileHash.NextInorder( idx );
  887. cTotalFiles++;
  888. // do we have a master for this one?
  889. MasterFileHash_t masterFileHashFind;
  890. masterFileHashFind.m_idxFile = file.m_idxFile;
  891. int idxMaster = m_treeMasterFileHashes.Find( masterFileHashFind );
  892. UserReportedFile_t &ufile = m_treeAllReportedFiles[file.m_idxFile];
  893. bool bOutput = false;
  894. if ( Q_stristr( ufile.m_filename.String(), "bin\\pak01" )!=NULL || Q_stristr( ufile.m_filename.String(), ".vpk" )!=NULL )
  895. bOutput = true;
  896. else
  897. {
  898. FileHash_t filehashLocal;
  899. EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( ufile.m_path.String(), ufile.m_filename.String(), 0, &filehashLocal );
  900. if ( eStatus == k_eFileCRCStatus_GotCRC )
  901. {
  902. if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  903. file.m_FileHash.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  904. filehashLocal != file.m_FileHash )
  905. {
  906. bOutput = true;
  907. }
  908. }
  909. }
  910. FindFileIndex_t ffi;
  911. ffi.idxFindFile = file.m_idxFile;
  912. if ( ctMatches != ctFiles || idxMaster != m_treeMasterFileHashes.InvalidIndex() || bListAll || ( pchFilenameFind && m_vecReportedFiles.Find( ffi ) != -1 ) || bOutput )
  913. {
  914. char rgch[256];
  915. Q_snprintf( rgch, 256, "reports=%d matches=%d Hash details:", ctFiles, ctMatches );
  916. FileHash_t *pFileHashMaster = NULL;
  917. if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() )
  918. pFileHashMaster = &m_treeMasterFileHashes[idxMaster].m_FileHash;
  919. FileRenderHelper( file.m_userID, rgch, ufile.m_path.String(), ufile.m_filename.String(), &file.m_FileHash, 0, pFileHashMaster );
  920. }
  921. }
  922. Msg( "Total user files %d %d %d \n", m_treeUserReportedFileHash.Count(), cTotalFiles, cTotalMatches );
  923. Msg( "Total files %d, total with authoritative hashes %d \n", m_treeAllReportedFiles.Count(), m_treeMasterFileHashes.Count() );
  924. Msg( "Matching files %d %d %d \n", m_cMatchedFile, m_cMatchedMasterFile, m_cMatchedMasterFileHash );
  925. return 0;
  926. }
  927. int CPureFileTracker::ListAllTrackedFiles( bool bListAll, const char *pchFilenameFind, int nFileFractionMin, int nFileFractionMax )
  928. {
  929. g_pFileSystem->MarkAllCRCsUnverified();
  930. int cTotal = 0;
  931. int cTotalMatch = 0;
  932. int count = 0;
  933. do
  934. {
  935. CUnverifiedFileHash rgUnverifiedFiles[1];
  936. count = g_pFileSystem->GetUnverifiedFileHashes( rgUnverifiedFiles, ARRAYSIZE( rgUnverifiedFiles ) );
  937. if ( count && ( bListAll || ( pchFilenameFind && Q_stristr( rgUnverifiedFiles[0].m_Filename, pchFilenameFind ) && rgUnverifiedFiles[0].m_nFileFraction >= nFileFractionMin && rgUnverifiedFiles[0].m_nFileFraction <= nFileFractionMax ) ) )
  938. {
  939. USERID_t useridFake;
  940. useridFake.idtype = IDTYPE_STEAM;
  941. FileRenderHelper( useridFake, "", rgUnverifiedFiles[0].m_PathID, rgUnverifiedFiles[0].m_Filename, &rgUnverifiedFiles[0].m_FileHash, rgUnverifiedFiles[0].m_nFileFraction, NULL );
  942. if ( rgUnverifiedFiles[0].m_FileHash.m_PackFileID )
  943. {
  944. g_pFileSystem->CheckVPKFileHash( rgUnverifiedFiles[0].m_FileHash.m_PackFileID, rgUnverifiedFiles[0].m_FileHash.m_nPackFileNumber, rgUnverifiedFiles[0].m_nFileFraction, rgUnverifiedFiles[0].m_FileHash.m_md5contents );
  945. }
  946. cTotalMatch++;
  947. }
  948. if ( count )
  949. cTotal++;
  950. } while ( count );
  951. Msg( "Total files %d Matching files %d \n", cTotal, cTotalMatch );
  952. return 0;
  953. }
  954. CPureFileTracker g_PureFileTracker;
  955. //#define DEBUG_PURE_SERVER
  956. #ifdef DEBUG_PURE_SERVER
  957. void CC_ListPureServerFiles(const CCommand &args)
  958. {
  959. if ( !sv.IsDedicated() )
  960. return;
  961. g_PureFileTracker.ListUserFiles( args.ArgC() > 1 && (atoi(args[1]) > 0), NULL );
  962. }
  963. static ConCommand svpurelistuserfiles("sv_pure_listuserfiles", CC_ListPureServerFiles, "ListPureServerFiles");
  964. void CC_PureServerFindFile(const CCommand &args)
  965. {
  966. if ( !sv.IsDedicated() )
  967. return;
  968. g_PureFileTracker.ListUserFiles( false, args[1] );
  969. }
  970. static ConCommand svpurefinduserfiles("sv_pure_finduserfiles", CC_PureServerFindFile, "ListPureServerFiles");
  971. void CC_PureServerListTrackedFiles(const CCommand &args)
  972. {
  973. // BUGBUG! Because this code is in engine instead of server, it exists in the client - ugh!
  974. // Remove this command from client before shipping for realz.
  975. //if ( !sv.IsDedicated() )
  976. // return;
  977. int nFileFractionMin = args.ArgC() >= 3 ? Q_atoi(args[2]) : 0;
  978. int nFileFractionMax = args.ArgC() >= 4 ? Q_atoi(args[3]) : nFileFractionMin;
  979. if ( nFileFractionMax < 0 )
  980. nFileFractionMax = 0x7FFFFFFF;
  981. g_PureFileTracker.ListAllTrackedFiles( args.ArgC() <= 1, args.ArgC() >= 2 ? args[1] : NULL, nFileFractionMin, nFileFractionMax );
  982. }
  983. static ConCommand svpurelistfiles("sv_pure_listfiles", CC_PureServerListTrackedFiles, "ListPureServerFiles");
  984. void CC_PureServerCheckVPKFiles(const CCommand &args)
  985. {
  986. if ( sv.IsDedicated() )
  987. Plat_BeginWatchdogTimer( 5 * 60 ); // reset watchdog timer to allow 5 minutes for the VPK check
  988. g_pFileSystem->CacheAllVPKFileHashes( false, true );
  989. if ( sv.IsDedicated() )
  990. Plat_EndWatchdogTimer();
  991. }
  992. static ConCommand svpurecheckvpks("sv_pure_checkvpk", CC_PureServerCheckVPKFiles, "CheckPureServerVPKFiles");
  993. #endif