Counter Strike : Global Offensive Source Code
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.

977 lines
31 KiB

  1. //========= Copyright � 1996-2005, 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 <utlsortvector.h>
  20. extern ConVar sv_pure_consensus;
  21. extern ConVar sv_pure_retiretime;
  22. extern ConVar sv_pure_trace;
  23. // NOTE: This has to be the last file included!
  24. #include "tier0/memdbgon.h"
  25. static char *g_SvPure2_ProtectedDirs[] =
  26. {
  27. "sound",
  28. "models",
  29. "materials"
  30. };
  31. static bool IsProtectedBySvPure2( const char *pFilename )
  32. {
  33. for ( int i=0; i < ARRAYSIZE( g_SvPure2_ProtectedDirs ); i++ )
  34. {
  35. const char *pProtectedDir = g_SvPure2_ProtectedDirs[i];
  36. int len = V_strlen( pProtectedDir );
  37. if ( V_strlen( pFilename ) < len+1 )
  38. return false;
  39. char tempStr[512];
  40. Assert( len < ARRAYSIZE( tempStr ) );
  41. memcpy( tempStr, pFilename, len );
  42. tempStr[len] = 0;
  43. if ( V_stricmp( tempStr, pProtectedDir ) == 0 )
  44. {
  45. if ( pFilename[len] == '/' || pFilename[len] == '\\' )
  46. return true;
  47. }
  48. }
  49. return false;
  50. }
  51. CPureServerWhitelist::CCommand::CCommand()
  52. {
  53. }
  54. CPureServerWhitelist::CCommand::~CCommand()
  55. {
  56. }
  57. CPureServerWhitelist* CPureServerWhitelist::Create( IFileSystem *pFileSystem )
  58. {
  59. CPureServerWhitelist *pRet = new CPureServerWhitelist;
  60. pRet->Init( pFileSystem );
  61. return pRet;
  62. }
  63. CPureServerWhitelist::CPureServerWhitelist()
  64. {
  65. m_pFileSystem = NULL;
  66. m_LoadCounter = 0;
  67. m_AllowFromDiskList.m_pWhitelist = this;
  68. m_ForceMatchList.m_pWhitelist = this;
  69. m_RefCount = 1;
  70. m_bFullyPureMode = false;
  71. }
  72. CPureServerWhitelist::~CPureServerWhitelist()
  73. {
  74. Term();
  75. }
  76. void CPureServerWhitelist::Init( IFileSystem *pFileSystem )
  77. {
  78. Term();
  79. m_pFileSystem = pFileSystem;
  80. }
  81. void CPureServerWhitelist::Term()
  82. {
  83. m_FileCommands.PurgeAndDeleteElements();
  84. m_RecursiveDirCommands.PurgeAndDeleteElements();
  85. m_NonRecursiveDirCommands.PurgeAndDeleteElements();
  86. m_pFileSystem = NULL;
  87. m_LoadCounter = 0;
  88. }
  89. bool CPureServerWhitelist::LoadFromKeyValues( KeyValues *kv )
  90. {
  91. for ( KeyValues *pCurItem = kv->GetFirstValue(); pCurItem; pCurItem = pCurItem->GetNextValue() )
  92. {
  93. char szPathName[ MAX_PATH ];
  94. const char *pKeyValue = pCurItem->GetName();
  95. const char *pModifiers = pCurItem->GetString();
  96. if ( !pKeyValue || !pModifiers )
  97. continue;
  98. Q_strncpy( szPathName, pKeyValue, sizeof(szPathName) );
  99. Q_FixSlashes( szPathName );
  100. const char *pValue = szPathName;
  101. // Figure out the modifiers.
  102. bool bFromSteam = false, bAllowFromDisk = false, bCheckCRC = false;
  103. CSplitString mods( pModifiers, "+" );
  104. for ( int i=0; i < mods.Count(); i++ )
  105. {
  106. if ( V_stricmp( mods[i], "from_steam" ) == 0 )
  107. bFromSteam = true;
  108. else if ( V_stricmp( mods[i], "allow_from_disk" ) == 0 )
  109. bAllowFromDisk = true;
  110. else if ( V_stricmp( mods[i], "check_crc" ) == 0 )
  111. bCheckCRC = true;
  112. else
  113. Warning( "Unknown modifier in whitelist file: %s.\n", mods[i] );
  114. }
  115. // we don't have to purge really; and if we do, we CAN'T delete elements
  116. if ( bFromSteam && (bAllowFromDisk || bCheckCRC) )
  117. {
  118. bAllowFromDisk = bCheckCRC = false;
  119. Warning( "Whitelist: from_steam not compatible with other modifiers (used on %s).\n", pValue );
  120. Warning( " Other markers removed.\n" );
  121. }
  122. // Setup a new CCommand to hold this command.
  123. CPureServerWhitelist::CCommand *pCommand = new CPureServerWhitelist::CCommand;
  124. pCommand->m_LoadOrder = m_LoadCounter++;
  125. pCommand->m_bAllowFromDisk = bAllowFromDisk;
  126. pCommand->m_bCheckCRC = bCheckCRC;
  127. // Figure out if they're referencing a file, a recursive directory, or a nonrecursive directory.
  128. CUtlDict<CCommand*,int> *pList;
  129. const char *pEndPart = V_UnqualifiedFileName( pValue );
  130. if ( Q_stricmp( pEndPart, "..." ) == 0 )
  131. pList = &m_RecursiveDirCommands;
  132. else if ( Q_stricmp( pEndPart, "*.*" ) == 0 )
  133. pList = &m_NonRecursiveDirCommands;
  134. else
  135. pList = &m_FileCommands;
  136. // If it's a directory command, get rid of the *.* or ...
  137. char filePath[MAX_PATH];
  138. if ( pList == &m_RecursiveDirCommands || pList == &m_NonRecursiveDirCommands )
  139. V_ExtractFilePath( pValue, filePath, sizeof( filePath ) );
  140. else
  141. V_strncpy( filePath, pValue, sizeof( filePath ) );
  142. V_FixSlashes( filePath );
  143. // Add the command to the appropriate list.
  144. if ( pList->Find( filePath ) == pList->InvalidIndex() )
  145. {
  146. pList->Insert( filePath, pCommand );
  147. }
  148. else
  149. {
  150. Error( "Pure server whitelist entry '%s' is a duplicate.\n", filePath );
  151. }
  152. }
  153. return true;
  154. }
  155. void CPureServerWhitelist::EnableFullyPureMode()
  156. {
  157. // In this mode, all files must come from Steam.
  158. m_FileCommands.PurgeAndDeleteElements();
  159. m_RecursiveDirCommands.PurgeAndDeleteElements();
  160. m_NonRecursiveDirCommands.PurgeAndDeleteElements();
  161. m_bFullyPureMode = true;
  162. }
  163. bool CPureServerWhitelist::IsInFullyPureMode() const
  164. {
  165. return m_bFullyPureMode;
  166. }
  167. void CPureServerWhitelist::UpdateCommandStats( CUtlDict<CPureServerWhitelist::CCommand*,int> &commands, int *pHighest, int *pLongestPathName )
  168. {
  169. for ( int i=commands.First(); i != commands.InvalidIndex(); i=commands.Next( i ) )
  170. {
  171. *pHighest = MAX( *pHighest, commands[i]->m_LoadOrder );
  172. int len = V_strlen( commands.GetElementName( i ) );
  173. *pLongestPathName = MAX( *pLongestPathName, len );
  174. }
  175. }
  176. void CPureServerWhitelist::PrintCommand( const char *pFileSpec, const char *pExt, int maxPathnameLen, CPureServerWhitelist::CCommand *pCommand )
  177. {
  178. // Get rid of the trailing slash if there is one.
  179. char tempFileSpec[MAX_PATH];
  180. V_strncpy( tempFileSpec, pFileSpec, sizeof( tempFileSpec ) );
  181. int len = V_strlen( tempFileSpec );
  182. if ( len > 0 && (tempFileSpec[len-1] == '/' || tempFileSpec[len-1] == '\\') )
  183. tempFileSpec[len-1] = 0;
  184. if ( pExt )
  185. Msg( "%s%c%s", tempFileSpec, CORRECT_PATH_SEPARATOR, pExt );
  186. else
  187. Msg( "%s", tempFileSpec );
  188. len = V_strlen( pFileSpec );
  189. for ( int i=len; i < maxPathnameLen+6; i++ )
  190. {
  191. Msg( " " );
  192. }
  193. Msg( "\t" );
  194. if ( pCommand->m_bCheckCRC )
  195. Msg( "check_crc" );
  196. else
  197. Msg( "allow_from_disk" );
  198. Msg( "\n" );
  199. }
  200. int CPureServerWhitelist::FindCommandByLoadOrder( CUtlDict<CPureServerWhitelist::CCommand*,int> &commands, int iLoadOrder )
  201. {
  202. for ( int i=commands.First(); i != commands.InvalidIndex(); i=commands.Next( i ) )
  203. {
  204. if ( commands[i]->m_LoadOrder == iLoadOrder )
  205. return i;
  206. }
  207. return -1;
  208. }
  209. void CPureServerWhitelist::PrintWhitelistContents()
  210. {
  211. int highestLoadOrder = 0, longestPathName = 0;
  212. UpdateCommandStats( m_FileCommands, &highestLoadOrder, &longestPathName );
  213. UpdateCommandStats( m_RecursiveDirCommands, &highestLoadOrder, &longestPathName );
  214. UpdateCommandStats( m_NonRecursiveDirCommands, &highestLoadOrder, &longestPathName );
  215. for ( int iLoadOrder=0; iLoadOrder <= highestLoadOrder; iLoadOrder++ )
  216. {
  217. // Check regular file commands.
  218. int iCommand = FindCommandByLoadOrder( m_FileCommands, iLoadOrder );
  219. if ( iCommand != -1 )
  220. {
  221. PrintCommand( m_FileCommands.GetElementName( iCommand ), NULL, longestPathName, m_FileCommands[iCommand] );
  222. }
  223. else
  224. {
  225. // Check recursive commands.
  226. iCommand = FindCommandByLoadOrder( m_RecursiveDirCommands, iLoadOrder );
  227. if ( iCommand != -1 )
  228. {
  229. PrintCommand( m_RecursiveDirCommands.GetElementName( iCommand ), "...", longestPathName, m_RecursiveDirCommands[iCommand] );
  230. }
  231. else
  232. {
  233. // Check *.* commands.
  234. iCommand = FindCommandByLoadOrder( m_NonRecursiveDirCommands, iLoadOrder );
  235. if ( iCommand != -1 )
  236. {
  237. PrintCommand( m_NonRecursiveDirCommands.GetElementName( iCommand ), "*.*", longestPathName, m_NonRecursiveDirCommands[iCommand] );
  238. }
  239. }
  240. }
  241. }
  242. }
  243. void CPureServerWhitelist::Encode( CUtlBuffer &buf )
  244. {
  245. EncodeCommandList( m_FileCommands, buf );
  246. EncodeCommandList( m_RecursiveDirCommands, buf );
  247. EncodeCommandList( m_NonRecursiveDirCommands, buf );
  248. buf.PutChar( (char)m_bFullyPureMode );
  249. }
  250. void CPureServerWhitelist::EncodeCommandList( CUtlDict<CPureServerWhitelist::CCommand*,int> &theList, CUtlBuffer &buf )
  251. {
  252. buf.PutInt( theList.Count() );
  253. for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) )
  254. {
  255. CPureServerWhitelist::CCommand *pCommand = theList[i];
  256. unsigned char val = 0;
  257. if ( pCommand->m_bAllowFromDisk )
  258. val |= 0x01;
  259. if ( pCommand->m_bCheckCRC )
  260. val |= 0x02;
  261. buf.PutUnsignedChar( val );
  262. buf.PutUnsignedShort( pCommand->m_LoadOrder );
  263. buf.PutString( theList.GetElementName( i ) );
  264. }
  265. }
  266. void CPureServerWhitelist::Decode( CUtlBuffer &buf )
  267. {
  268. DecodeCommandList( m_FileCommands, buf );
  269. DecodeCommandList( m_RecursiveDirCommands, buf );
  270. DecodeCommandList( m_NonRecursiveDirCommands, buf );
  271. if ( buf.GetBytesRemaining() >= 1 )
  272. {
  273. m_bFullyPureMode = (buf.GetChar() != 0);
  274. }
  275. else
  276. {
  277. m_bFullyPureMode = false;
  278. }
  279. }
  280. void CPureServerWhitelist::CacheFileCRCs()
  281. {
  282. InternalCacheFileCRCs( m_FileCommands, k_eCacheCRCType_SingleFile );
  283. InternalCacheFileCRCs( m_NonRecursiveDirCommands, k_eCacheCRCType_Directory );
  284. InternalCacheFileCRCs( m_RecursiveDirCommands, k_eCacheCRCType_Directory_Recursive );
  285. }
  286. void CPureServerWhitelist::InternalCacheFileCRCs( CUtlDict<CCommand*,int> &theList, ECacheCRCType eType )
  287. {
  288. for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) )
  289. {
  290. CCommand *pCommand = theList[i];
  291. if ( pCommand->m_bCheckCRC )
  292. {
  293. const char *pPathname = theList.GetElementName( i );
  294. m_pFileSystem->CacheFileCRCs( pPathname, eType, &m_ForceMatchList );
  295. }
  296. }
  297. }
  298. void CPureServerWhitelist::DecodeCommandList( CUtlDict<CPureServerWhitelist::CCommand*,int> &theList, CUtlBuffer &buf )
  299. {
  300. int nCommands = buf.GetInt();
  301. for ( int i=0; i < nCommands; i++ )
  302. {
  303. CPureServerWhitelist::CCommand *pCommand = new CPureServerWhitelist::CCommand;
  304. unsigned char val = buf.GetUnsignedChar();
  305. pCommand->m_bAllowFromDisk = (( val & 0x01 ) != 0);
  306. pCommand->m_bCheckCRC = (( val & 0x02 ) != 0);
  307. pCommand->m_LoadOrder = buf.GetUnsignedShort();
  308. char str[MAX_PATH];
  309. buf.GetString( str, sizeof( str )-1 );
  310. V_FixSlashes( str );
  311. theList.Insert( str, pCommand );
  312. }
  313. }
  314. CPureServerWhitelist::CCommand* CPureServerWhitelist::GetBestEntry( const char *pFilename )
  315. {
  316. // NOTE: Since this is a user-specified file, we don't have the added complexity of path IDs in here.
  317. // So when the filesystem asks if a file is in the whitelist, we just ignore the path ID.
  318. // Make sure we have a relative pathname with fixed slashes..
  319. char relativeFilename[MAX_PATH];
  320. V_strncpy( relativeFilename, pFilename, sizeof( relativeFilename ) );
  321. // Convert the path to relative if necessary.
  322. if ( !V_IsAbsolutePath( relativeFilename ) || m_pFileSystem->FullPathToRelativePath( pFilename, relativeFilename, sizeof( relativeFilename ) ) )
  323. {
  324. V_FixSlashes( relativeFilename );
  325. // Get the directory this thing is in.
  326. char relativeDir[MAX_PATH];
  327. if ( !V_ExtractFilePath( relativeFilename, relativeDir, sizeof( relativeDir ) ) )
  328. relativeDir[0] = 0;
  329. // Check each of our dictionaries to see if there is an entry for this thing.
  330. CCommand *pBestEntry = NULL;
  331. pBestEntry = CheckEntry( m_FileCommands, relativeFilename, pBestEntry );
  332. if ( relativeDir[0] != 0 )
  333. {
  334. pBestEntry = CheckEntry( m_NonRecursiveDirCommands, relativeDir, pBestEntry );
  335. while ( relativeDir[0] != 0 )
  336. {
  337. // Check for this directory.
  338. pBestEntry = CheckEntry( m_RecursiveDirCommands, relativeDir, pBestEntry );
  339. if ( !V_StripLastDir( relativeDir, sizeof( relativeDir ) ) )
  340. break;
  341. }
  342. }
  343. return pBestEntry;
  344. }
  345. // Either we couldn't find an entry, or they specified an absolute path that we could not convert to a relative path.
  346. return NULL;
  347. }
  348. CPureServerWhitelist::CCommand* CPureServerWhitelist::CheckEntry(
  349. CUtlDict<CPureServerWhitelist::CCommand*,int> &dict,
  350. const char *pEntryName,
  351. CPureServerWhitelist::CCommand *pBestEntry )
  352. {
  353. int i = dict.Find( pEntryName );
  354. if ( i != dict.InvalidIndex() && (!pBestEntry || dict[i]->m_LoadOrder > pBestEntry->m_LoadOrder) )
  355. pBestEntry = dict[i];
  356. return pBestEntry;
  357. }
  358. void CPureServerWhitelist::Release()
  359. {
  360. if ( --m_RefCount <= 0 )
  361. delete this;
  362. }
  363. IFileList* CPureServerWhitelist::GetAllowFromDiskList()
  364. {
  365. ++m_RefCount;
  366. return &m_AllowFromDiskList;
  367. }
  368. IFileList* CPureServerWhitelist::GetForceMatchList()
  369. {
  370. ++m_RefCount;
  371. return &m_ForceMatchList;
  372. }
  373. // --------------------------------------------------------------------------------------------------- //
  374. // CAllowFromDiskList/CForceMatchList implementation.
  375. // --------------------------------------------------------------------------------------------------- //
  376. bool CPureServerWhitelist::CAllowFromDiskList::IsFileInList( const char *pFilename )
  377. {
  378. // In "fully pure" mode, all files must come from disk.
  379. if ( m_pWhitelist->m_bFullyPureMode )
  380. {
  381. // Only protect maps, models, and sounds.
  382. if ( IsProtectedBySvPure2( pFilename ) )
  383. return false;
  384. else
  385. return true;
  386. }
  387. CCommand *pCommand = m_pWhitelist->GetBestEntry( pFilename );
  388. if ( pCommand )
  389. return pCommand->m_bAllowFromDisk;
  390. else
  391. return true; // All files are allowed to come from disk by default.
  392. }
  393. void CPureServerWhitelist::CAllowFromDiskList::Release()
  394. {
  395. m_pWhitelist->Release();
  396. }
  397. bool CPureServerWhitelist::CForceMatchList::IsFileInList( const char *pFilename )
  398. {
  399. // In "fully pure" mode, all files must match the server files
  400. if ( m_pWhitelist->m_bFullyPureMode )
  401. return true;
  402. CCommand *pCommand = m_pWhitelist->GetBestEntry( pFilename );
  403. if ( pCommand )
  404. return pCommand->m_bCheckCRC;
  405. else
  406. return false; // By default, no files require the CRC check.
  407. }
  408. void CPureServerWhitelist::CForceMatchList::Release()
  409. {
  410. m_pWhitelist->Release();
  411. }
  412. void CPureFileTracker::AddUserReportedFileHash( int idxFile, FileHash_t *pFileHash, USERID_t userID, bool bAddMasterRecord )
  413. {
  414. UserReportedFileHash_t userFileHash;
  415. userFileHash.m_idxFile = idxFile;
  416. userFileHash.m_userID = userID;
  417. userFileHash.m_FileHash = *pFileHash;
  418. int idxUserReported = m_treeUserReportedFileHash.Find( userFileHash );
  419. if ( idxUserReported == m_treeUserReportedFileHash.InvalidIndex() )
  420. {
  421. idxUserReported = m_treeUserReportedFileHash.Insert( userFileHash );
  422. if ( bAddMasterRecord )
  423. {
  424. // count the number of matches for this idxFile
  425. // if it exceeds > 5 then make a master record
  426. int idxFirst = idxUserReported;
  427. int idxLast = idxUserReported;
  428. int ctMatches = 1;
  429. int ctTotalFiles = 1;
  430. // first go forward
  431. int idx = m_treeUserReportedFileHash.NextInorder( idxUserReported );
  432. while ( idx != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxUserReported].m_idxFile )
  433. {
  434. if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash )
  435. ctMatches++;
  436. ctTotalFiles++;
  437. idxLast = idx;
  438. idx = m_treeUserReportedFileHash.NextInorder( idx );
  439. }
  440. // then backwards
  441. idx = m_treeUserReportedFileHash.PrevInorder( idxUserReported );
  442. while ( idx != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxUserReported].m_idxFile )
  443. {
  444. if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash )
  445. ctMatches++;
  446. ctTotalFiles++;
  447. idxFirst = idx;
  448. idx = m_treeUserReportedFileHash.PrevInorder( idx );
  449. }
  450. // if ctTotalFiles >> ctMatches then that means clients are reading different bits from the file.
  451. // in order to get this right we need to ask them to read the entire thing
  452. if ( ctMatches >= sv_pure_consensus.GetInt() )
  453. {
  454. MasterFileHash_t masterFileHashNew;
  455. masterFileHashNew.m_idxFile = m_treeUserReportedFileHash[idxUserReported].m_idxFile;
  456. masterFileHashNew.m_cMatches = ctMatches;
  457. masterFileHashNew.m_FileHash = m_treeUserReportedFileHash[idxUserReported].m_FileHash;
  458. m_treeMasterFileHashes.Insert( masterFileHashNew );
  459. // remove all the individual records that matched the new master, we don't need them anymore
  460. int idxRemove = idxFirst;
  461. while ( idxRemove != m_treeUserReportedFileHash.InvalidIndex() )
  462. {
  463. int idxNext = m_treeUserReportedFileHash.NextInorder( idxRemove );
  464. if ( m_treeUserReportedFileHash[idxRemove].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash )
  465. m_treeUserReportedFileHash.RemoveAt( idxRemove );
  466. if ( idxRemove == idxLast )
  467. break;
  468. idxRemove = idxNext;
  469. }
  470. }
  471. }
  472. }
  473. else
  474. {
  475. m_treeUserReportedFileHash[idxUserReported].m_FileHash = *pFileHash;
  476. }
  477. // we dont have enough data to decide if you match or not yet - so we call it a match
  478. }
  479. void FileRenderHelper( USERID_t userID, const char *pchMessage, const char *pchPath, const char *pchFileName, FileHash_t *pFileHash, int nFileFraction, FileHash_t *pFileHashLocal )
  480. {
  481. char rgch[256];
  482. char hex[ 34 ];
  483. Q_memset( hex, 0, sizeof( hex ) );
  484. Q_binarytohex( (const byte *)&pFileHash->m_md5contents.bits, sizeof( pFileHash->m_md5contents.bits ), hex, sizeof( hex ) );
  485. char hex2[ 34 ];
  486. Q_memset( hex2, 0, sizeof( hex2 ) );
  487. if ( pFileHashLocal )
  488. Q_binarytohex( (const byte *)&pFileHashLocal->m_md5contents.bits, sizeof( pFileHashLocal->m_md5contents.bits ), hex2, sizeof( hex2 ) );
  489. if ( pFileHash->m_PackFileID )
  490. {
  491. Q_snprintf( rgch, 256, "Pure server: file: %s\\%s ( %d %d %8.8x %6.6x ) %s : %s : %s\n",
  492. pchPath, pchFileName,
  493. pFileHash->m_PackFileID, pFileHash->m_nPackFileNumber, nFileFraction, pFileHash->m_cbFileLen,
  494. pchMessage,
  495. hex, hex2 );
  496. }
  497. else
  498. {
  499. Q_snprintf( rgch, 256, "Pure server: file: %s\\%s ( %d %d %lx ) %s : %s : %s\n",
  500. pchPath, pchFileName,
  501. pFileHash->m_eFileHashType, pFileHash->m_cbFileLen, pFileHash->m_eFileHashType ? pFileHash->m_crcIOSequence : 0,
  502. pchMessage,
  503. hex, hex2 );
  504. }
  505. if ( userID.idtype != 0 )
  506. Msg( "[%s] %s\n", GetUserIDString(userID), rgch );
  507. else
  508. Msg( "%s", rgch );
  509. }
  510. bool CPureFileTracker::DoesFileMatch( const char *pPathID, const char *pRelativeFilename, int nFileFraction, FileHash_t *pFileHash, USERID_t userID )
  511. {
  512. // if the server has been idle for more than 15 minutes, discard all this data
  513. const float flRetireTime = sv_pure_retiretime.GetFloat();
  514. float flCurTime = Plat_FloatTime();
  515. if ( ( flCurTime - m_flLastFileReceivedTime ) > flRetireTime )
  516. {
  517. m_treeMasterFileHashes.RemoveAll();
  518. m_treeUserReportedFileHash.RemoveAll();
  519. m_treeMasterFileHashes.RemoveAll();
  520. }
  521. m_flLastFileReceivedTime = flCurTime;
  522. // The clients must send us all files. We decide if it is whitelisted or not
  523. // That way the clients can not hide modified files in a whitelisted directory
  524. if ( pFileHash->m_PackFileID == 0 &&
  525. !sv.GetPureServerWhitelist()->GetForceMatchList()->IsFileInList( pRelativeFilename ) )
  526. {
  527. if ( sv_pure_trace.GetInt() == 4 )
  528. {
  529. char warningStr[1024] = {0};
  530. V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s ignored by whitelist.", pPathID, pRelativeFilename );
  531. Msg( "[%s] %s\n", GetUserIDString(userID), warningStr );
  532. }
  533. return true;
  534. }
  535. char rgchFilenameFixed[MAX_PATH];
  536. Q_strncpy( rgchFilenameFixed, pRelativeFilename, sizeof( rgchFilenameFixed ) );
  537. Q_FixSlashes( rgchFilenameFixed );
  538. // first look up the file and see if we have ever seen it before
  539. CRC32_t crcFilename;
  540. CRC32_Init( &crcFilename );
  541. CRC32_ProcessBuffer( &crcFilename, rgchFilenameFixed, Q_strlen( rgchFilenameFixed ) );
  542. CRC32_ProcessBuffer( &crcFilename, pPathID, Q_strlen( pPathID ) );
  543. CRC32_Final( &crcFilename );
  544. UserReportedFile_t ufile;
  545. ufile.m_crcIdentifier = crcFilename;
  546. ufile.m_filename = rgchFilenameFixed;
  547. ufile.m_path = pPathID;
  548. ufile.m_nFileFraction = nFileFraction;
  549. int idxFile = m_treeAllReportedFiles.Find( ufile );
  550. if ( idxFile == m_treeAllReportedFiles.InvalidIndex() )
  551. {
  552. idxFile = m_treeAllReportedFiles.Insert( ufile );
  553. }
  554. else
  555. {
  556. m_cMatchedFile++;
  557. }
  558. // then check if we have a master CRC for the file
  559. MasterFileHash_t masterFileHash;
  560. masterFileHash.m_idxFile = idxFile;
  561. int idxMaster = m_treeMasterFileHashes.Find( masterFileHash );
  562. // dont do anything with this yet
  563. // check to see if we have loaded the file locally and can match it
  564. FileHash_t filehashLocal;
  565. EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal );
  566. if ( eStatus == k_eFileCRCStatus_FileInVPK)
  567. {
  568. // you managed to load a file outside a VPK that the server has in the VPK
  569. // this is possible if the user explodes the VPKs into individual files and then deletes the VPKs
  570. FileRenderHelper( userID, "file should be in VPK", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  571. return false;
  572. }
  573. // if the user sent us a full file hash, but we dont have one, hash it now
  574. if ( pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  575. ( eStatus != k_eFileCRCStatus_GotCRC || filehashLocal.m_eFileHashType != FileHash_t::k_EFileHashTypeEntireFile ) )
  576. {
  577. // lets actually read the file so we get a complete file hash
  578. FileHandle_t f = g_pFileSystem->Open( rgchFilenameFixed, "rb", pPathID);
  579. // try to load the file and really compute the hash - should only have to do this once ever
  580. if ( f )
  581. {
  582. // load file into a null-terminated buffer
  583. int fileSize = g_pFileSystem->Size( f );
  584. unsigned bufSize = g_pFileSystem->GetOptimalReadSize( f, fileSize );
  585. char *buffer = (char*)g_pFileSystem->AllocOptimalReadBuffer( f, bufSize );
  586. Assert( buffer );
  587. // read into local buffer
  588. bool bRetOK = ( g_pFileSystem->ReadEx( buffer, bufSize, fileSize, f ) != 0 );
  589. bRetOK;
  590. g_pFileSystem->FreeOptimalReadBuffer( buffer );
  591. g_pFileSystem->Close( f ); // close file after reading
  592. eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal );
  593. }
  594. else
  595. {
  596. // what should we do if we couldn't open the file? should probably kick
  597. FileRenderHelper( userID, "could not open file to hash ( benign for now )", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  598. }
  599. }
  600. if ( eStatus == k_eFileCRCStatus_GotCRC )
  601. {
  602. if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  603. pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile )
  604. {
  605. if ( filehashLocal == *pFileHash )
  606. {
  607. m_cMatchedFileFullHash++;
  608. return true;
  609. }
  610. else
  611. {
  612. // don't need to check anything else
  613. // did not match - record so that we have a record of the file that did not match ( just for reporting )
  614. AddUserReportedFileHash( idxFile, pFileHash, userID, false );
  615. FileRenderHelper( userID, "file does not match", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, &filehashLocal );
  616. return false;
  617. }
  618. }
  619. }
  620. // if this is a VPK file, we have completely cataloged all the VPK files, so no suprises are allowed
  621. if ( pFileHash->m_PackFileID )
  622. {
  623. AddUserReportedFileHash( idxFile, pFileHash, userID, false );
  624. FileRenderHelper( userID, "unrecognized vpk file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  625. return false;
  626. }
  627. // now lets see if we have a master file hash for this
  628. if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() )
  629. {
  630. m_cMatchedMasterFile++;
  631. FileHash_t *pFileHashLocal = &m_treeMasterFileHashes[idxMaster].m_FileHash;
  632. if ( *pFileHashLocal == *pFileHash )
  633. {
  634. m_cMatchedMasterFileHash++;
  635. return true;
  636. }
  637. else
  638. {
  639. // did not match - record so that we have a record of the file that did not match ( just for reporting )
  640. AddUserReportedFileHash( idxFile, pFileHash, userID, false );
  641. // and then return failure
  642. FileRenderHelper( userID, "file does not match server master file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, pFileHashLocal );
  643. return false;
  644. }
  645. }
  646. // no master record, accumulate individual record so we can get a consensus
  647. if ( sv_pure_trace.GetInt() == 3 )
  648. {
  649. FileRenderHelper( userID, "server does not have hash for this file. Waiting for consensus", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL );
  650. }
  651. AddUserReportedFileHash( idxFile, pFileHash, userID, true );
  652. // we dont have enough data to decide if you match or not yet - so we call it a match
  653. return true;
  654. }
  655. struct FindFileIndex_t
  656. {
  657. int idxFindFile;
  658. };
  659. class CStupidLess
  660. {
  661. public:
  662. bool Less( const FindFileIndex_t &src1, const FindFileIndex_t &src2, void *pCtx )
  663. {
  664. if ( src1.idxFindFile < src2.idxFindFile )
  665. return true;
  666. return false;
  667. }
  668. };
  669. int CPureFileTracker::ListUserFiles( bool bListAll, const char *pchFilenameFind )
  670. {
  671. CUtlSortVector< FindFileIndex_t, CStupidLess > m_vecReportedFiles;
  672. int idxFindFile = m_treeAllReportedFiles.FirstInorder();
  673. while ( idxFindFile != m_treeAllReportedFiles.InvalidIndex() )
  674. {
  675. UserReportedFile_t &ufile = m_treeAllReportedFiles[idxFindFile];
  676. if ( pchFilenameFind && Q_stristr( ufile.m_filename, pchFilenameFind ) )
  677. {
  678. FileHash_t filehashLocal;
  679. EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( ufile.m_path.String(), ufile.m_filename.String(), 0, &filehashLocal );
  680. if ( eStatus == k_eFileCRCStatus_GotCRC )
  681. {
  682. USERID_t useridFake = { 0, 0 };
  683. FileRenderHelper( useridFake, "Found: ",ufile.m_path.String(),ufile.m_filename.String(), &filehashLocal, 0, NULL );
  684. FindFileIndex_t ffi;
  685. ffi.idxFindFile = idxFindFile;
  686. m_vecReportedFiles.Insert( ffi );
  687. }
  688. else
  689. {
  690. Msg( "File not found %s %s %x\n", ufile.m_filename.String(), ufile.m_path.String(), idxFindFile );
  691. }
  692. }
  693. idxFindFile = m_treeAllReportedFiles.NextInorder( idxFindFile );
  694. }
  695. int cTotalFiles = 0;
  696. int cTotalMatches = 0;
  697. int idx = m_treeUserReportedFileHash.FirstInorder();
  698. while ( idx != m_treeUserReportedFileHash.InvalidIndex() )
  699. {
  700. UserReportedFileHash_t &file = m_treeUserReportedFileHash[idx];
  701. int idxNext = m_treeUserReportedFileHash.NextInorder( idx );
  702. int ctMatches = 1;
  703. int ctFiles = 1;
  704. // check this against all others for the same file
  705. while ( idxNext != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxNext].m_idxFile )
  706. {
  707. if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxNext].m_FileHash )
  708. {
  709. ctMatches++;
  710. cTotalMatches++;
  711. }
  712. ctFiles++;
  713. idxNext = m_treeUserReportedFileHash.NextInorder( idxNext );
  714. }
  715. idx = m_treeUserReportedFileHash.NextInorder( idx );
  716. cTotalFiles++;
  717. // do we have a master for this one?
  718. MasterFileHash_t masterFileHashFind;
  719. masterFileHashFind.m_idxFile = file.m_idxFile;
  720. int idxMaster = m_treeMasterFileHashes.Find( masterFileHashFind );
  721. UserReportedFile_t &ufile = m_treeAllReportedFiles[file.m_idxFile];
  722. bool bOutput = false;
  723. if ( Q_stristr( ufile.m_filename.String(), "bin\\pak01" )!=NULL || Q_stristr( ufile.m_filename.String(), ".vpk" )!=NULL )
  724. bOutput = true;
  725. else
  726. {
  727. FileHash_t filehashLocal;
  728. EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( ufile.m_path.String(), ufile.m_filename.String(), 0, &filehashLocal );
  729. if ( eStatus == k_eFileCRCStatus_GotCRC )
  730. {
  731. if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  732. file.m_FileHash.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile &&
  733. filehashLocal != file.m_FileHash )
  734. {
  735. bOutput = true;
  736. }
  737. }
  738. }
  739. FindFileIndex_t ffi;
  740. ffi.idxFindFile = file.m_idxFile;
  741. if ( ctMatches != ctFiles || idxMaster != m_treeMasterFileHashes.InvalidIndex() || bListAll || ( pchFilenameFind && m_vecReportedFiles.Find( ffi ) != -1 ) || bOutput )
  742. {
  743. char rgch[256];
  744. V_sprintf_safe( rgch, "reports=%d matches=%d Hash details:", ctFiles, ctMatches );
  745. FileHash_t *pFileHashMaster = NULL;
  746. if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() )
  747. pFileHashMaster = &m_treeMasterFileHashes[idxMaster].m_FileHash;
  748. FileRenderHelper( file.m_userID, rgch, ufile.m_path.String(), ufile.m_filename.String(), &file.m_FileHash, 0, pFileHashMaster );
  749. }
  750. }
  751. Msg( "Total user files %d %d %d \n", m_treeUserReportedFileHash.Count(), cTotalFiles, cTotalMatches );
  752. Msg( "Total files %d, total with authoritative hashes %d \n", m_treeAllReportedFiles.Count(), m_treeMasterFileHashes.Count() );
  753. Msg( "Matching files %d %d %d \n", m_cMatchedFile, m_cMatchedMasterFile, m_cMatchedMasterFileHash );
  754. return 0;
  755. }
  756. int CPureFileTracker::ListAllTrackedFiles( bool bListAll, const char *pchFilenameFind, int nFileFractionMin, int nFileFractionMax )
  757. {
  758. g_pFileSystem->MarkAllCRCsUnverified();
  759. int cTotal = 0;
  760. int cTotalMatch = 0;
  761. int count = 0;
  762. do
  763. {
  764. CUnverifiedFileHash rgUnverifiedFiles[1];
  765. count = g_pFileSystem->GetUnverifiedFileHashes( rgUnverifiedFiles, ARRAYSIZE( rgUnverifiedFiles ) );
  766. if ( count && ( bListAll || ( pchFilenameFind && Q_stristr( rgUnverifiedFiles[0].m_Filename, pchFilenameFind ) && rgUnverifiedFiles[0].m_nFileFraction >= nFileFractionMin && rgUnverifiedFiles[0].m_nFileFraction <= nFileFractionMax ) ) )
  767. {
  768. USERID_t useridFake = { 0, 0 };
  769. FileRenderHelper( useridFake, "", rgUnverifiedFiles[0].m_PathID, rgUnverifiedFiles[0].m_Filename, &rgUnverifiedFiles[0].m_FileHash, rgUnverifiedFiles[0].m_nFileFraction, NULL );
  770. if ( rgUnverifiedFiles[0].m_FileHash.m_PackFileID )
  771. {
  772. 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 );
  773. }
  774. cTotalMatch++;
  775. }
  776. if ( count )
  777. cTotal++;
  778. } while ( count );
  779. Msg( "Total files %d Matching files %d \n", cTotal, cTotalMatch );
  780. return 0;
  781. }
  782. CPureFileTracker g_PureFileTracker;
  783. #define DEBUG_PURE_SERVER
  784. #ifdef DEBUG_PURE_SERVER
  785. void CC_ListPureServerFiles(const CCommand &args)
  786. {
  787. if ( !sv.IsDedicated() )
  788. return;
  789. g_PureFileTracker.ListUserFiles( args.ArgC() > 1 && (atoi(args[1]) > 0), NULL );
  790. }
  791. static ConCommand svpurelistuserfiles("sv_pure_listuserfiles", CC_ListPureServerFiles, "ListPureServerFiles");
  792. void CC_PureServerFindFile(const CCommand &args)
  793. {
  794. if ( !sv.IsDedicated() )
  795. return;
  796. g_PureFileTracker.ListUserFiles( false, args[1] );
  797. }
  798. static ConCommand svpurefinduserfiles("sv_pure_finduserfiles", CC_PureServerFindFile, "ListPureServerFiles");
  799. void CC_PureServerListTrackedFiles(const CCommand &args)
  800. {
  801. // BUGBUG! Because this code is in engine instead of server, it exists in the client - ugh!
  802. // Remove this command from client before shipping for realz.
  803. //if ( !sv.IsDedicated() )
  804. // return;
  805. int nFileFractionMin = args.ArgC() >= 3 ? Q_atoi(args[2]) : 0;
  806. int nFileFractionMax = args.ArgC() >= 4 ? Q_atoi(args[3]) : nFileFractionMin;
  807. if ( nFileFractionMax < 0 )
  808. nFileFractionMax = 0x7FFFFFFF;
  809. g_PureFileTracker.ListAllTrackedFiles( args.ArgC() <= 1, args.ArgC() >= 2 ? args[1] : NULL, nFileFractionMin, nFileFractionMax );
  810. }
  811. static ConCommand svpurelistfiles("sv_pure_listfiles", CC_PureServerListTrackedFiles, "ListPureServerFiles");
  812. void CC_PureServerCheckVPKFiles(const CCommand &args)
  813. {
  814. if ( sv.IsDedicated() )
  815. BeginWatchdogTimer( 5 * 60 ); // reset watchdog timer to allow 5 minutes for the VPK check
  816. g_pFileSystem->CacheAllVPKFileHashes( false, true );
  817. if ( sv.IsDedicated() )
  818. EndWatchdogTimer();
  819. }
  820. static ConCommand svpurecheckvpks("sv_pure_checkvpk", CC_PureServerCheckVPKFiles, "CheckPureServerVPKFiles");
  821. #endif