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.
1141 lines
37 KiB
1141 lines
37 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "utlstring.h" |
|
#include "checksum_crc.h" |
|
#include "userid.h" |
|
#include "pure_server.h" |
|
#include "common.h" |
|
#include "tier1/KeyValues.h" |
|
#include "convar.h" |
|
#include "filesystem_engine.h" |
|
#include "server.h" |
|
#include "sv_filter.h" |
|
#include "tier1/UtlSortVector.h" |
|
|
|
// NOTE: This has to be the last file included! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern ConVar sv_pure_consensus; |
|
extern ConVar sv_pure_retiretime; |
|
extern ConVar sv_pure_trace; |
|
|
|
CPureServerWhitelist::CCommand::~CCommand() |
|
{ |
|
} |
|
|
|
|
|
CPureServerWhitelist* CPureServerWhitelist::Create( IFileSystem *pFileSystem ) |
|
{ |
|
CPureServerWhitelist *pRet = new CPureServerWhitelist; |
|
pRet->Init( pFileSystem ); |
|
return pRet; |
|
} |
|
|
|
|
|
CPureServerWhitelist::CPureServerWhitelist() |
|
{ |
|
m_pFileSystem = NULL; |
|
m_LoadCounter = 0; |
|
m_RefCount = 1; |
|
} |
|
|
|
|
|
CPureServerWhitelist::~CPureServerWhitelist() |
|
{ |
|
Term(); |
|
} |
|
|
|
|
|
void CPureServerWhitelist::Init( IFileSystem *pFileSystem ) |
|
{ |
|
Term(); |
|
m_pFileSystem = pFileSystem; |
|
} |
|
|
|
void CPureServerWhitelist::Load( int iPureMode ) |
|
{ |
|
Term(); |
|
|
|
// Not pure at all? |
|
if ( iPureMode < 0 ) |
|
return; |
|
|
|
// Load base trusted keys |
|
{ |
|
KeyValues *kv = new KeyValues( "" ); |
|
bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/trusted_keys_base.txt", "game" ); |
|
if ( bLoaded ) |
|
bLoaded = LoadTrustedKeysFromKeyValues( kv ); |
|
else |
|
Warning( "Error loading cfg/trusted_keys_base.txt\n" ); |
|
kv->deleteThis(); |
|
} |
|
|
|
// sv_pure 0: minimal rules only |
|
if ( iPureMode == 0 ) |
|
{ |
|
KeyValues *kv = new KeyValues( "" ); |
|
bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/pure_server_minimal.txt", "game" ); |
|
if ( bLoaded ) |
|
bLoaded = LoadCommandsFromKeyValues( kv ); |
|
else |
|
Warning( "Error loading cfg/pure_server_minimal.txt\n" ); |
|
kv->deleteThis(); |
|
return; |
|
} |
|
|
|
// Load up full pure rules |
|
{ |
|
KeyValues *kv = new KeyValues( "" ); |
|
bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/pure_server_full.txt", "game" ); |
|
if ( bLoaded ) |
|
bLoaded = LoadCommandsFromKeyValues( kv ); |
|
else |
|
Warning( "Error loading cfg/pure_server_full.txt\n" ); |
|
kv->deleteThis(); |
|
} |
|
|
|
// Now load user customizations |
|
if ( iPureMode == 1 ) |
|
{ |
|
|
|
// Load custom whitelist |
|
KeyValues *kv = new KeyValues( "" ); |
|
bool bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/pure_server_whitelist.txt", "game" ); |
|
if ( !bLoaded ) |
|
// Check the old location |
|
bLoaded = kv->LoadFromFile( g_pFileSystem, "pure_server_whitelist.txt", "game" ); |
|
if ( bLoaded ) |
|
bLoaded = LoadCommandsFromKeyValues( kv ); |
|
else |
|
Msg( "pure_server_whitelist.txt not present; pure server using only base file rules\n" ); |
|
kv->deleteThis(); |
|
|
|
// Load custom trusted keys |
|
kv = new KeyValues( "" ); |
|
bLoaded = kv->LoadFromFile( g_pFileSystem, "cfg/trusted_keys.txt", "game" ); |
|
if ( bLoaded ) |
|
bLoaded = LoadTrustedKeysFromKeyValues( kv ); |
|
else |
|
Msg( "trusted_keys.txt not present; pure server using only base trusted key list\n" ); |
|
kv->deleteThis(); |
|
} |
|
|
|
// Hardcoded rules last |
|
AddHardcodedFileCommands(); |
|
} |
|
|
|
bool operator==( const PureServerPublicKey_t &a, const PureServerPublicKey_t &b ) |
|
{ |
|
return a.Count() == b.Count() && V_memcmp( a.Base(), b.Base(), a.Count() ) == 0; |
|
} |
|
|
|
bool CPureServerWhitelist::CommandDictDifferent( const CUtlDict<CCommand*,int> &a, const CUtlDict<CCommand*,int> &b ) |
|
{ |
|
FOR_EACH_DICT( a, idxA ) |
|
{ |
|
if ( a[idxA]->m_LoadOrder == kLoadOrder_HardcodedOverride ) |
|
continue; |
|
int idxB = b.Find( a.GetElementName( idxA ) ); |
|
if ( !b.IsValidIndex( idxB ) ) |
|
return true; |
|
if ( b[idxB]->m_LoadOrder == kLoadOrder_HardcodedOverride ) |
|
continue; |
|
if ( a[idxA]->m_eFileClass != b[idxB]->m_eFileClass |
|
|| a[idxA]->m_LoadOrder != b[idxB]->m_LoadOrder ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CPureServerWhitelist::operator==( const CPureServerWhitelist &x ) const |
|
{ |
|
|
|
// Compare rule dictionaries |
|
if ( CommandDictDifferent( m_FileCommands, x.m_FileCommands ) |
|
|| CommandDictDifferent( x.m_FileCommands, m_FileCommands ) |
|
|| CommandDictDifferent( m_RecursiveDirCommands, x.m_RecursiveDirCommands ) |
|
|| CommandDictDifferent( x.m_RecursiveDirCommands, m_RecursiveDirCommands ) |
|
|| CommandDictDifferent( m_NonRecursiveDirCommands, x.m_NonRecursiveDirCommands ) |
|
|| CommandDictDifferent( x.m_NonRecursiveDirCommands, m_NonRecursiveDirCommands ) ) |
|
return false; |
|
|
|
// Compare trusted key list |
|
if ( m_vecTrustedKeys.Count() != x.m_vecTrustedKeys.Count() ) |
|
return false; |
|
for ( int i = 0 ; i < m_vecTrustedKeys.Count() ; ++i ) |
|
if ( !( m_vecTrustedKeys[i] == x.m_vecTrustedKeys[i] ) ) |
|
return false; |
|
|
|
// they are the same |
|
return true; |
|
} |
|
|
|
void CPureServerWhitelist::Term() |
|
{ |
|
m_FileCommands.PurgeAndDeleteElements(); |
|
m_RecursiveDirCommands.PurgeAndDeleteElements(); |
|
m_NonRecursiveDirCommands.PurgeAndDeleteElements(); |
|
m_vecTrustedKeys.Purge(); |
|
m_LoadCounter = 0; |
|
} |
|
|
|
|
|
bool CPureServerWhitelist::LoadCommandsFromKeyValues( KeyValues *kv ) |
|
{ |
|
for ( KeyValues *pCurItem = kv->GetFirstValue(); pCurItem; pCurItem = pCurItem->GetNextValue() ) |
|
{ |
|
char szPathName[ MAX_PATH ]; |
|
const char *pKeyValue = pCurItem->GetName(); |
|
const char *pModifiers = pCurItem->GetString(); |
|
if ( !pKeyValue || !pModifiers ) |
|
continue; |
|
|
|
Q_strncpy( szPathName, pKeyValue, sizeof(szPathName) ); |
|
Q_FixSlashes( szPathName ); |
|
const char *pValue = szPathName; |
|
|
|
// Figure out the modifiers. |
|
bool bFromTrustedSource = false, bAllowFromDisk = false, bCheckCRC = false, bAny = false; |
|
CUtlVector<char*> mods; |
|
V_SplitString( pModifiers, "+", mods ); |
|
for ( int i=0; i < mods.Count(); i++ ) |
|
{ |
|
if ( |
|
V_stricmp( mods[i], "from_steam" ) == 0 |
|
|| V_stricmp( mods[i], "trusted_source" ) == 0 |
|
) |
|
bFromTrustedSource = true; |
|
else if ( V_stricmp( mods[i], "allow_from_disk" ) == 0 ) |
|
bAllowFromDisk = true; |
|
else if ( |
|
V_stricmp( mods[i], "check_crc" ) == 0 |
|
|| V_stricmp( mods[i], "check_hash" ) == 0 |
|
) |
|
bCheckCRC = true; |
|
else if ( V_stricmp( mods[i], "any" ) == 0 ) |
|
bAny = true; |
|
else |
|
Warning( "Unknown modifier in whitelist file: %s.\n", mods[i] ); |
|
} |
|
mods.PurgeAndDeleteElementsArray(); |
|
if ( |
|
( bFromTrustedSource && ( bAllowFromDisk || bCheckCRC || bAny ) ) |
|
|| ( bAny && bCheckCRC ) ) |
|
{ |
|
Warning( "Whitelist: incompatible flags used on %s.\n", pValue ); |
|
continue; |
|
} |
|
|
|
EPureServerFileClass eFileClass; |
|
if ( bCheckCRC ) |
|
eFileClass = ePureServerFileClass_CheckHash; |
|
else if ( bFromTrustedSource ) |
|
eFileClass = ePureServerFileClass_AnyTrusted; |
|
else |
|
eFileClass = ePureServerFileClass_Any; |
|
|
|
// Setup a new CCommand to hold this command. |
|
AddFileCommand( pValue, eFileClass, m_LoadCounter++ ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CPureServerWhitelist::AddHardcodedFileCommands() |
|
{ |
|
AddFileCommand( "materials/vgui/replay/thumbnails/...", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride ); |
|
AddFileCommand( "sound/ui/hitsound.wav", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride ); |
|
AddFileCommand( "sound/ui/killsound.wav", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride ); |
|
AddFileCommand( "materials/vgui/logos/...", ePureServerFileClass_Any, kLoadOrder_HardcodedOverride ); |
|
} |
|
|
|
void CPureServerWhitelist::AddFileCommand( const char *pszFilePath, EPureServerFileClass eFileClass, unsigned short nLoadOrder ) |
|
{ |
|
|
|
CPureServerWhitelist::CCommand *pCommand = new CPureServerWhitelist::CCommand; |
|
pCommand->m_LoadOrder = nLoadOrder; |
|
pCommand->m_eFileClass = eFileClass; |
|
|
|
// Figure out if they're referencing a file, a recursive directory, or a nonrecursive directory. |
|
CUtlDict<CCommand*,int> *pList; |
|
const char *pEndPart = V_UnqualifiedFileName( pszFilePath ); |
|
if ( Q_stricmp( pEndPart, "..." ) == 0 ) |
|
pList = &m_RecursiveDirCommands; |
|
else if ( Q_stricmp( pEndPart, "*.*" ) == 0 ) |
|
pList = &m_NonRecursiveDirCommands; |
|
else |
|
pList = &m_FileCommands; |
|
|
|
// If it's a directory command, get rid of the *.* or ... |
|
char filePath[MAX_PATH]; |
|
if ( pList == &m_RecursiveDirCommands || pList == &m_NonRecursiveDirCommands ) |
|
V_ExtractFilePath( pszFilePath, filePath, sizeof( filePath ) ); |
|
else |
|
V_strncpy( filePath, pszFilePath, sizeof( filePath ) ); |
|
|
|
V_FixSlashes( filePath ); |
|
|
|
int idxExisting = pList->Find( filePath ); |
|
if ( idxExisting != pList->InvalidIndex() ) |
|
{ |
|
delete pList->Element( idxExisting ); |
|
pList->RemoveAt( idxExisting ); |
|
} |
|
pList->Insert( filePath, pCommand ); |
|
} |
|
|
|
bool CPureServerWhitelist::LoadTrustedKeysFromKeyValues( KeyValues *kv ) |
|
{ |
|
for ( KeyValues *pCurItem = kv->GetFirstTrueSubKey(); pCurItem; pCurItem = pCurItem->GetNextTrueSubKey() ) |
|
{ |
|
if ( V_stricmp( pCurItem->GetName(), "public_key" ) != 0 ) |
|
{ |
|
Warning( "Trusted key list has unexpected block '%s'; expected only 'public_key' blocks\n", pCurItem->GetName() ); |
|
continue; |
|
} |
|
|
|
const char *pszType = pCurItem->GetString( "type", "(none)" ); |
|
if ( V_stricmp( pszType, "rsa" ) != 0 ) |
|
{ |
|
Warning( "Trusted key type '%s' not supported.\n", pszType ); |
|
continue; |
|
} |
|
|
|
const char *pszKeyData = pCurItem->GetString( "rsa_public_key", "" ); |
|
if ( *pszKeyData == '\0' ) |
|
{ |
|
Warning( "trusted key is missing 'rsa_public_key' data; ignored\n" ); |
|
continue; |
|
} |
|
|
|
PureServerPublicKey_t &key = m_vecTrustedKeys[ m_vecTrustedKeys.AddToTail() ]; |
|
int nKeyDataLen = V_strlen( pszKeyData ); |
|
key.SetSize( nKeyDataLen / 2 ); |
|
// Aaaannnnnnnnddddd V_hextobinary has no return code. |
|
// Because nobody could *ever* possible attempt to parse bad data. It could never possibly happen. |
|
V_hextobinary( pszKeyData, nKeyDataLen, key.Base(), key.Count() ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CPureServerWhitelist::UpdateCommandStats( CUtlDict<CPureServerWhitelist::CCommand*,int> &commands, int *pHighest, int *pLongestPathName ) |
|
{ |
|
for ( int i=commands.First(); i != commands.InvalidIndex(); i=commands.Next( i ) ) |
|
{ |
|
*pHighest = max( *pHighest, (int)commands[i]->m_LoadOrder ); |
|
|
|
int len = V_strlen( commands.GetElementName( i ) ); |
|
*pLongestPathName = max( *pLongestPathName, len ); |
|
} |
|
} |
|
|
|
void CPureServerWhitelist::PrintCommand( const char *pFileSpec, const char *pExt, int maxPathnameLen, CPureServerWhitelist::CCommand *pCommand ) |
|
{ |
|
// Get rid of the trailing slash if there is one. |
|
char tempFileSpec[MAX_PATH]; |
|
V_strncpy( tempFileSpec, pFileSpec, sizeof( tempFileSpec ) ); |
|
int len = V_strlen( tempFileSpec ); |
|
if ( len > 0 && (tempFileSpec[len-1] == '/' || tempFileSpec[len-1] == '\\') ) |
|
tempFileSpec[len-1] = 0; |
|
|
|
CUtlString buf; |
|
if ( pExt ) |
|
buf.Format( "%s%c%s", tempFileSpec, CORRECT_PATH_SEPARATOR, pExt ); |
|
else |
|
buf.Format( "%s", tempFileSpec ); |
|
|
|
len = V_strlen( pFileSpec ); |
|
for ( int i=len; i < maxPathnameLen+6; i++ ) |
|
{ |
|
buf += " "; |
|
} |
|
|
|
buf += "\t"; |
|
switch ( pCommand->m_eFileClass ) |
|
{ |
|
default: |
|
buf += va( "(bogus value %d)", (int)pCommand->m_eFileClass ); |
|
Assert( false ); |
|
break; |
|
|
|
case ePureServerFileClass_Any: |
|
buf += "any"; |
|
break; |
|
|
|
case ePureServerFileClass_AnyTrusted: |
|
buf += "trusted_source"; |
|
break; |
|
|
|
case ePureServerFileClass_CheckHash: |
|
buf += "check_crc"; |
|
break; |
|
} |
|
|
|
Msg( "%s\n", buf.String() ); |
|
} |
|
|
|
|
|
int CPureServerWhitelist::FindCommandByLoadOrder( CUtlDict<CPureServerWhitelist::CCommand*,int> &commands, int iLoadOrder ) |
|
{ |
|
for ( int i=commands.First(); i != commands.InvalidIndex(); i=commands.Next( i ) ) |
|
{ |
|
if ( commands[i]->m_LoadOrder == iLoadOrder ) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
void CPureServerWhitelist::PrintWhitelistContents() |
|
{ |
|
int highestLoadOrder = 0, longestPathName = 0; |
|
UpdateCommandStats( m_FileCommands, &highestLoadOrder, &longestPathName ); |
|
UpdateCommandStats( m_RecursiveDirCommands, &highestLoadOrder, &longestPathName ); |
|
UpdateCommandStats( m_NonRecursiveDirCommands, &highestLoadOrder, &longestPathName ); |
|
|
|
for ( int iLoadOrder=0; iLoadOrder <= highestLoadOrder; iLoadOrder++ ) |
|
{ |
|
// Check regular file commands. |
|
int iCommand = FindCommandByLoadOrder( m_FileCommands, iLoadOrder ); |
|
if ( iCommand != -1 ) |
|
{ |
|
PrintCommand( m_FileCommands.GetElementName( iCommand ), NULL, longestPathName, m_FileCommands[iCommand] ); |
|
} |
|
else |
|
{ |
|
// Check recursive commands. |
|
iCommand = FindCommandByLoadOrder( m_RecursiveDirCommands, iLoadOrder ); |
|
if ( iCommand != -1 ) |
|
{ |
|
PrintCommand( m_RecursiveDirCommands.GetElementName( iCommand ), "...", longestPathName, m_RecursiveDirCommands[iCommand] ); |
|
} |
|
else |
|
{ |
|
// Check *.* commands. |
|
iCommand = FindCommandByLoadOrder( m_NonRecursiveDirCommands, iLoadOrder ); |
|
if ( iCommand != -1 ) |
|
{ |
|
PrintCommand( m_NonRecursiveDirCommands.GetElementName( iCommand ), "*.*", longestPathName, m_NonRecursiveDirCommands[iCommand] ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
void CPureServerWhitelist::Encode( CUtlBuffer &buf ) |
|
{ |
|
// Put dummy version number |
|
buf.PutUnsignedInt( 0xffff ); |
|
|
|
// Encode rules |
|
EncodeCommandList( m_FileCommands, buf ); |
|
EncodeCommandList( m_RecursiveDirCommands, buf ); |
|
EncodeCommandList( m_NonRecursiveDirCommands, buf ); |
|
|
|
// Encode trusted keys |
|
buf.PutUnsignedInt( m_vecTrustedKeys.Count() ); |
|
FOR_EACH_VEC( m_vecTrustedKeys, i ) |
|
{ |
|
uint32 nKeySize = m_vecTrustedKeys[i].Count(); |
|
buf.PutUnsignedInt( nKeySize ); |
|
buf.Put( m_vecTrustedKeys[i].Base(), nKeySize ); |
|
} |
|
} |
|
|
|
void CPureServerWhitelist::EncodeCommandList( CUtlDict<CPureServerWhitelist::CCommand*,int> &theList, CUtlBuffer &buf ) |
|
{ |
|
// Count how many we're really going to write |
|
int nCount = 0; |
|
for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) ) |
|
{ |
|
if ( theList[i]->m_LoadOrder != kLoadOrder_HardcodedOverride ) |
|
++nCount; |
|
} |
|
buf.PutInt( nCount ); |
|
|
|
// Write them |
|
for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) ) |
|
{ |
|
CPureServerWhitelist::CCommand *pCommand = theList[i]; |
|
if ( pCommand->m_LoadOrder == kLoadOrder_HardcodedOverride ) |
|
continue; |
|
|
|
unsigned char val = (unsigned char)pCommand->m_eFileClass; |
|
|
|
buf.PutUnsignedChar( val ); |
|
buf.PutUnsignedShort( pCommand->m_LoadOrder ); |
|
buf.PutString( theList.GetElementName( i ) ); |
|
} |
|
} |
|
|
|
|
|
void CPureServerWhitelist::Decode( CUtlBuffer &buf ) |
|
{ |
|
Term(); |
|
|
|
uint32 nVersionTag = *(uint32 *)buf.PeekGet(); |
|
uint32 nFormatVersion = 0; |
|
if ( nVersionTag == 0xffff ) |
|
{ |
|
buf.GetUnsignedInt(); |
|
nFormatVersion = 1; |
|
} |
|
else |
|
{ |
|
// Talking to legacy server -- load up default rules, |
|
// the rest of his list are supposed to be exceptions to |
|
// the base |
|
Load( 2 ); |
|
} |
|
DecodeCommandList( m_FileCommands, buf, nFormatVersion ); |
|
DecodeCommandList( m_RecursiveDirCommands, buf, nFormatVersion ); |
|
DecodeCommandList( m_NonRecursiveDirCommands, buf, nFormatVersion ); |
|
|
|
// Hardcoded |
|
AddHardcodedFileCommands(); |
|
|
|
if ( nFormatVersion >= 1 ) |
|
{ |
|
uint32 nKeyCount = buf.GetUnsignedInt(); |
|
m_vecTrustedKeys.SetCount( nKeyCount ); |
|
FOR_EACH_VEC( m_vecTrustedKeys, i ) |
|
{ |
|
uint32 nKeySize = buf.GetUnsignedInt(); |
|
m_vecTrustedKeys[i].SetCount( nKeySize ); |
|
buf.Get( m_vecTrustedKeys[i].Base(), nKeySize ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CPureServerWhitelist::CacheFileCRCs() |
|
{ |
|
InternalCacheFileCRCs( m_FileCommands, k_eCacheCRCType_SingleFile ); |
|
InternalCacheFileCRCs( m_NonRecursiveDirCommands, k_eCacheCRCType_Directory ); |
|
InternalCacheFileCRCs( m_RecursiveDirCommands, k_eCacheCRCType_Directory_Recursive ); |
|
} |
|
|
|
|
|
// !SV_PURE FIXME! Do we need this? |
|
void CPureServerWhitelist::InternalCacheFileCRCs( CUtlDict<CCommand*,int> &theList, ECacheCRCType eType ) |
|
{ |
|
// for ( int i=theList.First(); i != theList.InvalidIndex(); i = theList.Next( i ) ) |
|
// { |
|
// CCommand *pCommand = theList[i]; |
|
// if ( pCommand->m_bCheckCRC ) |
|
// { |
|
// const char *pPathname = theList.GetElementName( i ); |
|
// m_pFileSystem->CacheFileCRCs( pPathname, eType, &m_ForceMatchList ); |
|
// } |
|
// } |
|
} |
|
|
|
|
|
void CPureServerWhitelist::DecodeCommandList( CUtlDict<CPureServerWhitelist::CCommand*,int> &theList, CUtlBuffer &buf, uint32 nFormatVersion ) |
|
{ |
|
int nCommands = buf.GetInt(); |
|
|
|
for ( int i=0; i < nCommands; i++ ) |
|
{ |
|
CPureServerWhitelist::CCommand *pCommand = new CPureServerWhitelist::CCommand; |
|
|
|
unsigned char val = buf.GetUnsignedChar(); |
|
unsigned short nLoadOrder = buf.GetUnsignedShort(); |
|
|
|
if ( nFormatVersion == 0 ) |
|
{ |
|
pCommand->m_eFileClass = ( val & 1 ) ? ePureServerFileClass_Any : ePureServerFileClass_AnyTrusted; |
|
pCommand->m_LoadOrder = nLoadOrder + m_LoadCounter; |
|
} |
|
else |
|
{ |
|
pCommand->m_eFileClass = (EPureServerFileClass)val; |
|
pCommand->m_LoadOrder = nLoadOrder; |
|
} |
|
|
|
char str[MAX_PATH]; |
|
buf.GetString( str ); |
|
V_FixSlashes( str ); |
|
|
|
theList.Insert( str, pCommand ); |
|
} |
|
} |
|
|
|
|
|
CPureServerWhitelist::CCommand* CPureServerWhitelist::GetBestEntry( const char *pFilename ) |
|
{ |
|
// NOTE: Since this is a user-specified file, we don't have the added complexity of path IDs in here. |
|
// So when the filesystem asks if a file is in the whitelist, we just ignore the path ID. |
|
|
|
// Make sure we have a relative pathname with fixed slashes.. |
|
char relativeFilename[MAX_PATH]; |
|
V_strncpy( relativeFilename, pFilename, sizeof( relativeFilename ) ); |
|
|
|
// Convert the path to relative if necessary. |
|
if ( !V_IsAbsolutePath( relativeFilename ) || m_pFileSystem->FullPathToRelativePath( pFilename, relativeFilename, sizeof( relativeFilename ) ) ) |
|
{ |
|
V_FixSlashes( relativeFilename ); |
|
|
|
// Get the directory this thing is in. |
|
char relativeDir[MAX_PATH]; |
|
if ( !V_ExtractFilePath( relativeFilename, relativeDir, sizeof( relativeDir ) ) ) |
|
relativeDir[0] = 0; |
|
|
|
|
|
// Check each of our dictionaries to see if there is an entry for this thing. |
|
CCommand *pBestEntry = NULL; |
|
|
|
pBestEntry = CheckEntry( m_FileCommands, relativeFilename, pBestEntry ); |
|
if ( relativeDir[0] != 0 ) |
|
{ |
|
pBestEntry = CheckEntry( m_NonRecursiveDirCommands, relativeDir, pBestEntry ); |
|
|
|
while ( relativeDir[0] != 0 ) |
|
{ |
|
// Check for this directory. |
|
pBestEntry = CheckEntry( m_RecursiveDirCommands, relativeDir, pBestEntry ); |
|
if ( !V_StripLastDir( relativeDir, sizeof( relativeDir ) ) ) |
|
break; |
|
} |
|
} |
|
|
|
return pBestEntry; |
|
} |
|
|
|
// Either we couldn't find an entry, or they specified an absolute path that we could not convert to a relative path. |
|
return NULL; |
|
} |
|
|
|
|
|
CPureServerWhitelist::CCommand* CPureServerWhitelist::CheckEntry( |
|
CUtlDict<CPureServerWhitelist::CCommand*,int> &dict, |
|
const char *pEntryName, |
|
CPureServerWhitelist::CCommand *pBestEntry ) |
|
{ |
|
int i = dict.Find( pEntryName ); |
|
if ( i != dict.InvalidIndex() && (!pBestEntry || dict[i]->m_LoadOrder > pBestEntry->m_LoadOrder) ) |
|
pBestEntry = dict[i]; |
|
|
|
return pBestEntry; |
|
} |
|
|
|
|
|
void CPureServerWhitelist::AddRef() |
|
{ |
|
ThreadInterlockedIncrement( &m_RefCount ); |
|
} |
|
|
|
void CPureServerWhitelist::Release() |
|
{ |
|
if ( ThreadInterlockedDecrement( &m_RefCount ) <= 0 ) |
|
delete this; |
|
} |
|
|
|
int CPureServerWhitelist::GetTrustedKeyCount() const |
|
{ |
|
return m_vecTrustedKeys.Count(); |
|
} |
|
|
|
const byte *CPureServerWhitelist::GetTrustedKey( int iKeyIndex, int *nKeySize ) const |
|
{ |
|
Assert( nKeySize != NULL ); |
|
if ( !m_vecTrustedKeys.IsValidIndex( iKeyIndex ) ) |
|
{ |
|
*nKeySize = 0; |
|
return NULL; |
|
} |
|
|
|
*nKeySize = m_vecTrustedKeys[iKeyIndex].Count(); |
|
return m_vecTrustedKeys[iKeyIndex].Base(); |
|
} |
|
|
|
|
|
EPureServerFileClass CPureServerWhitelist::GetFileClass( const char *pszFilename ) |
|
{ |
|
CCommand *pCommand = GetBestEntry( pszFilename ); |
|
if ( pCommand ) |
|
return pCommand->m_eFileClass; |
|
|
|
// Default action is to be permissive. (The default whitelist protects certain directories and files at a root level.) |
|
return ePureServerFileClass_Any; |
|
} |
|
|
|
void CPureFileTracker::AddUserReportedFileHash( int idxFile, FileHash_t *pFileHash, USERID_t userID, bool bAddMasterRecord ) |
|
{ |
|
UserReportedFileHash_t userFileHash; |
|
userFileHash.m_idxFile = idxFile; |
|
userFileHash.m_userID = userID; |
|
userFileHash.m_FileHash = *pFileHash; |
|
int idxUserReported = m_treeUserReportedFileHash.Find( userFileHash ); |
|
if ( idxUserReported == m_treeUserReportedFileHash.InvalidIndex() ) |
|
{ |
|
idxUserReported = m_treeUserReportedFileHash.Insert( userFileHash ); |
|
if ( bAddMasterRecord ) |
|
{ |
|
// count the number of matches for this idxFile |
|
// if it exceeds > 5 then make a master record |
|
int idxFirst = idxUserReported; |
|
int idxLast = idxUserReported; |
|
int ctMatches = 1; |
|
int ctTotalFiles = 1; |
|
// first go forward |
|
int idx = m_treeUserReportedFileHash.NextInorder( idxUserReported ); |
|
while ( idx != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxUserReported].m_idxFile ) |
|
{ |
|
if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash ) |
|
ctMatches++; |
|
ctTotalFiles++; |
|
idxLast = idx; |
|
idx = m_treeUserReportedFileHash.NextInorder( idx ); |
|
} |
|
// then backwards |
|
idx = m_treeUserReportedFileHash.PrevInorder( idxUserReported ); |
|
while ( idx != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxUserReported].m_idxFile ) |
|
{ |
|
if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash ) |
|
ctMatches++; |
|
ctTotalFiles++; |
|
idxFirst = idx; |
|
idx = m_treeUserReportedFileHash.PrevInorder( idx ); |
|
} |
|
// if ctTotalFiles >> ctMatches then that means clients are reading different bits from the file. |
|
// in order to get this right we need to ask them to read the entire thing |
|
if ( ctMatches >= sv_pure_consensus.GetInt() ) |
|
{ |
|
MasterFileHash_t masterFileHashNew; |
|
masterFileHashNew.m_idxFile = m_treeUserReportedFileHash[idxUserReported].m_idxFile; |
|
masterFileHashNew.m_cMatches = ctMatches; |
|
masterFileHashNew.m_FileHash = m_treeUserReportedFileHash[idxUserReported].m_FileHash; |
|
m_treeMasterFileHashes.Insert( masterFileHashNew ); |
|
// remove all the individual records that matched the new master, we don't need them anymore |
|
int idxRemove = idxFirst; |
|
while ( idxRemove != m_treeUserReportedFileHash.InvalidIndex() ) |
|
{ |
|
int idxNext = m_treeUserReportedFileHash.NextInorder( idxRemove ); |
|
if ( m_treeUserReportedFileHash[idxRemove].m_FileHash == m_treeUserReportedFileHash[idxUserReported].m_FileHash ) |
|
m_treeUserReportedFileHash.RemoveAt( idxRemove ); |
|
if ( idxRemove == idxLast ) |
|
break; |
|
idxRemove = idxNext; |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
m_treeUserReportedFileHash[idxUserReported].m_FileHash = *pFileHash; |
|
} |
|
// we dont have enough data to decide if you match or not yet - so we call it a match |
|
} |
|
|
|
|
|
void FileRenderHelper( USERID_t userID, const char *pchMessage, const char *pchPath, const char *pchFileName, FileHash_t *pFileHash, int nFileFraction, FileHash_t *pFileHashLocal ) |
|
{ |
|
char rgch[256]; |
|
char hex[ 34 ]; |
|
Q_memset( hex, 0, sizeof( hex ) ); |
|
Q_binarytohex( (const byte *)&pFileHash->m_md5contents.bits, sizeof( pFileHash->m_md5contents.bits ), hex, sizeof( hex ) ); |
|
|
|
char hex2[ 34 ]; |
|
Q_memset( hex2, 0, sizeof( hex2 ) ); |
|
if ( pFileHashLocal ) |
|
Q_binarytohex( (const byte *)&pFileHashLocal->m_md5contents.bits, sizeof( pFileHashLocal->m_md5contents.bits ), hex2, sizeof( hex2 ) ); |
|
|
|
if ( pFileHash->m_PackFileID ) |
|
{ |
|
Q_snprintf( rgch, 256, "Pure server: file: %s\\%s ( %d %d %8.8x %6.6x ) %s : %s : %s\n", |
|
pchPath, pchFileName, |
|
pFileHash->m_PackFileID, pFileHash->m_nPackFileNumber, nFileFraction, pFileHash->m_cbFileLen, |
|
pchMessage, |
|
hex, hex2 ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( rgch, 256, "Pure server: file: %s\\%s ( %d %d %x ) %s : %s : %s\n", |
|
pchPath, pchFileName, |
|
pFileHash->m_eFileHashType, pFileHash->m_cbFileLen, pFileHash->m_eFileHashType ? pFileHash->m_crcIOSequence : 0, |
|
pchMessage, |
|
hex, hex2 ); |
|
} |
|
if ( userID.idtype != 0 ) |
|
Msg( "[%s] %s\n", GetUserIDString(userID), rgch ); |
|
else |
|
Msg( "%s", rgch ); |
|
|
|
} |
|
|
|
|
|
bool CPureFileTracker::DoesFileMatch( const char *pPathID, const char *pRelativeFilename, int nFileFraction, FileHash_t *pFileHash, USERID_t userID ) |
|
{ |
|
// // if the server has been idle for more than 15 minutes, discard all this data |
|
// const float flRetireTime = sv_pure_retiretime.GetFloat(); |
|
// float flCurTime = Plat_FloatTime(); |
|
// if ( ( flCurTime - m_flLastFileReceivedTime ) > flRetireTime ) |
|
// { |
|
// m_treeMasterFileHashes.RemoveAll(); |
|
// m_treeUserReportedFileHash.RemoveAll(); |
|
// m_treeMasterFileHashes.RemoveAll(); |
|
// } |
|
// m_flLastFileReceivedTime = flCurTime; |
|
// |
|
// // The clients must send us all files. We decide if it is whitelisted or not |
|
// // That way the clients can not hide modified files in a whitelisted directory |
|
// if ( pFileHash->m_PackFileID == 0 && |
|
// sv.GetPureServerWhitelist()->GetFileClass( pRelativeFilename ) != ePureServerFileClass_CheckHash ) |
|
// { |
|
// |
|
// if ( sv_pure_trace.GetInt() == 4 ) |
|
// { |
|
// char warningStr[1024] = {0}; |
|
// V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s ignored by whitelist.", pPathID, pRelativeFilename ); |
|
// Msg( "[%s] %s\n", GetUserIDString(userID), warningStr ); |
|
// } |
|
// |
|
// return true; |
|
// } |
|
// |
|
// char rgchFilenameFixed[MAX_PATH]; |
|
// Q_strncpy( rgchFilenameFixed, pRelativeFilename, sizeof( rgchFilenameFixed ) ); |
|
// Q_FixSlashes( rgchFilenameFixed ); |
|
// |
|
// // first look up the file and see if we have ever seen it before |
|
// CRC32_t crcFilename; |
|
// CRC32_Init( &crcFilename ); |
|
// CRC32_ProcessBuffer( &crcFilename, rgchFilenameFixed, Q_strlen( rgchFilenameFixed ) ); |
|
// CRC32_ProcessBuffer( &crcFilename, pPathID, Q_strlen( pPathID ) ); |
|
// CRC32_Final( &crcFilename ); |
|
// UserReportedFile_t ufile; |
|
// ufile.m_crcIdentifier = crcFilename; |
|
// ufile.m_filename = rgchFilenameFixed; |
|
// ufile.m_path = pPathID; |
|
// ufile.m_nFileFraction = nFileFraction; |
|
// int idxFile = m_treeAllReportedFiles.Find( ufile ); |
|
// if ( idxFile == m_treeAllReportedFiles.InvalidIndex() ) |
|
// { |
|
// idxFile = m_treeAllReportedFiles.Insert( ufile ); |
|
// } |
|
// else |
|
// { |
|
// m_cMatchedFile++; |
|
// } |
|
// // then check if we have a master CRC for the file |
|
// MasterFileHash_t masterFileHash; |
|
// masterFileHash.m_idxFile = idxFile; |
|
// int idxMaster = m_treeMasterFileHashes.Find( masterFileHash ); |
|
// // dont do anything with this yet |
|
// |
|
// // check to see if we have loaded the file locally and can match it |
|
// FileHash_t filehashLocal; |
|
// EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal ); |
|
// if ( eStatus == k_eFileCRCStatus_FileInVPK) |
|
// { |
|
// // you managed to load a file outside a VPK that the server has in the VPK |
|
// // this is possible if the user explodes the VPKs into individual files and then deletes the VPKs |
|
// FileRenderHelper( userID, "file should be in VPK", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL ); |
|
// return false; |
|
// } |
|
// // if the user sent us a full file hash, but we dont have one, hash it now |
|
// if ( pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile && |
|
// ( eStatus != k_eFileCRCStatus_GotCRC || filehashLocal.m_eFileHashType != FileHash_t::k_EFileHashTypeEntireFile ) ) |
|
// { |
|
// // lets actually read the file so we get a complete file hash |
|
// FileHandle_t f = g_pFileSystem->Open( rgchFilenameFixed, "rb", pPathID); |
|
// // try to load the file and really compute the hash - should only have to do this once ever |
|
// if ( f ) |
|
// { |
|
// // load file into a null-terminated buffer |
|
// int fileSize = g_pFileSystem->Size( f ); |
|
// unsigned bufSize = g_pFileSystem->GetOptimalReadSize( f, fileSize ); |
|
// |
|
// char *buffer = (char*)g_pFileSystem->AllocOptimalReadBuffer( f, bufSize ); |
|
// Assert( buffer ); |
|
// |
|
// // read into local buffer |
|
// bool bRetOK = ( g_pFileSystem->ReadEx( buffer, bufSize, fileSize, f ) != 0 ); |
|
// bRetOK; |
|
// g_pFileSystem->FreeOptimalReadBuffer( buffer ); |
|
// |
|
// g_pFileSystem->Close( f ); // close file after reading |
|
// |
|
// eStatus = g_pFileSystem->CheckCachedFileHash( pPathID, rgchFilenameFixed, nFileFraction, &filehashLocal ); |
|
// } |
|
// else |
|
// { |
|
// // what should we do if we couldn't open the file? should probably kick |
|
// FileRenderHelper( userID, "could not open file to hash ( benign for now )", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL ); |
|
// } |
|
// } |
|
// if ( eStatus == k_eFileCRCStatus_GotCRC ) |
|
// { |
|
// if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile && |
|
// pFileHash->m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile ) |
|
// { |
|
// if ( filehashLocal == *pFileHash ) |
|
// { |
|
// m_cMatchedFileFullHash++; |
|
// return true; |
|
// } |
|
// else |
|
// { |
|
// // don't need to check anything else |
|
// // did not match - record so that we have a record of the file that did not match ( just for reporting ) |
|
// AddUserReportedFileHash( idxFile, pFileHash, userID, false ); |
|
// FileRenderHelper( userID, "file does not match", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, &filehashLocal ); |
|
// return false; |
|
// } |
|
// } |
|
// } |
|
// |
|
// // if this is a VPK file, we have completely cataloged all the VPK files, so no suprises are allowed |
|
// if ( pFileHash->m_PackFileID ) |
|
// { |
|
// AddUserReportedFileHash( idxFile, pFileHash, userID, false ); |
|
// FileRenderHelper( userID, "unrecognized vpk file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL ); |
|
// return false; |
|
// } |
|
// |
|
// |
|
// // now lets see if we have a master file hash for this |
|
// if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() ) |
|
// { |
|
// m_cMatchedMasterFile++; |
|
// |
|
// FileHash_t *pFileHashLocal = &m_treeMasterFileHashes[idxMaster].m_FileHash; |
|
// if ( *pFileHashLocal == *pFileHash ) |
|
// { |
|
// m_cMatchedMasterFileHash++; |
|
// return true; |
|
// } |
|
// else |
|
// { |
|
// // did not match - record so that we have a record of the file that did not match ( just for reporting ) |
|
// AddUserReportedFileHash( idxFile, pFileHash, userID, false ); |
|
// // and then return failure |
|
// FileRenderHelper( userID, "file does not match server master file", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, pFileHashLocal ); |
|
// return false; |
|
// } |
|
// } |
|
// |
|
// // no master record, accumulate individual record so we can get a consensus |
|
// if ( sv_pure_trace.GetInt() == 3 ) |
|
// { |
|
// FileRenderHelper( userID, "server does not have hash for this file. Waiting for consensus", pPathID, rgchFilenameFixed, pFileHash, nFileFraction, NULL ); |
|
// } |
|
// |
|
// AddUserReportedFileHash( idxFile, pFileHash, userID, true ); |
|
|
|
// we dont have enough data to decide if you match or not yet - so we call it a match |
|
return true; |
|
} |
|
|
|
|
|
struct FindFileIndex_t |
|
{ |
|
int idxFindFile; |
|
}; |
|
|
|
class CStupidLess |
|
{ |
|
public: |
|
bool Less( const FindFileIndex_t &src1, const FindFileIndex_t &src2, void *pCtx ) |
|
{ |
|
if ( src1.idxFindFile < src2.idxFindFile ) |
|
return true; |
|
|
|
return false; |
|
} |
|
}; |
|
|
|
int CPureFileTracker::ListUserFiles( bool bListAll, const char *pchFilenameFind ) |
|
{ |
|
CUtlSortVector< FindFileIndex_t, CStupidLess > m_vecReportedFiles; |
|
int idxFindFile = m_treeAllReportedFiles.FirstInorder(); |
|
while ( idxFindFile != m_treeAllReportedFiles.InvalidIndex() ) |
|
{ |
|
UserReportedFile_t &ufile = m_treeAllReportedFiles[idxFindFile]; |
|
if ( pchFilenameFind && Q_stristr( ufile.m_filename, pchFilenameFind ) ) |
|
{ |
|
FileHash_t filehashLocal; |
|
EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( ufile.m_path.String(), ufile.m_filename.String(), 0, &filehashLocal ); |
|
|
|
if ( eStatus == k_eFileCRCStatus_GotCRC ) |
|
{ |
|
USERID_t useridFake; |
|
useridFake.idtype = IDTYPE_STEAM; |
|
FileRenderHelper( useridFake, "Found: ",ufile.m_path.String(),ufile.m_filename.String(), &filehashLocal, 0, NULL ); |
|
FindFileIndex_t ffi; |
|
ffi.idxFindFile = idxFindFile; |
|
m_vecReportedFiles.Insert( ffi ); |
|
} |
|
else |
|
{ |
|
Msg( "File not found %s %s %x\n", ufile.m_filename.String(), ufile.m_path.String(), idxFindFile ); |
|
} |
|
} |
|
idxFindFile = m_treeAllReportedFiles.NextInorder( idxFindFile ); |
|
} |
|
|
|
|
|
int cTotalFiles = 0; |
|
int cTotalMatches = 0; |
|
int idx = m_treeUserReportedFileHash.FirstInorder(); |
|
while ( idx != m_treeUserReportedFileHash.InvalidIndex() ) |
|
{ |
|
UserReportedFileHash_t &file = m_treeUserReportedFileHash[idx]; |
|
|
|
int idxNext = m_treeUserReportedFileHash.NextInorder( idx ); |
|
int ctMatches = 1; |
|
int ctFiles = 1; |
|
// check this against all others for the same file |
|
while ( idxNext != m_treeUserReportedFileHash.InvalidIndex() && m_treeUserReportedFileHash[idx].m_idxFile == m_treeUserReportedFileHash[idxNext].m_idxFile ) |
|
{ |
|
if ( m_treeUserReportedFileHash[idx].m_FileHash == m_treeUserReportedFileHash[idxNext].m_FileHash ) |
|
{ |
|
ctMatches++; |
|
cTotalMatches++; |
|
} |
|
ctFiles++; |
|
idxNext = m_treeUserReportedFileHash.NextInorder( idxNext ); |
|
} |
|
idx = m_treeUserReportedFileHash.NextInorder( idx ); |
|
cTotalFiles++; |
|
|
|
// do we have a master for this one? |
|
MasterFileHash_t masterFileHashFind; |
|
masterFileHashFind.m_idxFile = file.m_idxFile; |
|
int idxMaster = m_treeMasterFileHashes.Find( masterFileHashFind ); |
|
|
|
UserReportedFile_t &ufile = m_treeAllReportedFiles[file.m_idxFile]; |
|
|
|
bool bOutput = false; |
|
if ( Q_stristr( ufile.m_filename.String(), "bin\\pak01" )!=NULL || Q_stristr( ufile.m_filename.String(), ".vpk" )!=NULL ) |
|
bOutput = true; |
|
else |
|
{ |
|
FileHash_t filehashLocal; |
|
EFileCRCStatus eStatus = g_pFileSystem->CheckCachedFileHash( ufile.m_path.String(), ufile.m_filename.String(), 0, &filehashLocal ); |
|
if ( eStatus == k_eFileCRCStatus_GotCRC ) |
|
{ |
|
if ( filehashLocal.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile && |
|
file.m_FileHash.m_eFileHashType == FileHash_t::k_EFileHashTypeEntireFile && |
|
filehashLocal != file.m_FileHash ) |
|
{ |
|
bOutput = true; |
|
} |
|
} |
|
} |
|
|
|
FindFileIndex_t ffi; |
|
ffi.idxFindFile = file.m_idxFile; |
|
|
|
if ( ctMatches != ctFiles || idxMaster != m_treeMasterFileHashes.InvalidIndex() || bListAll || ( pchFilenameFind && m_vecReportedFiles.Find( ffi ) != -1 ) || bOutput ) |
|
{ |
|
char rgch[256]; |
|
Q_snprintf( rgch, 256, "reports=%d matches=%d Hash details:", ctFiles, ctMatches ); |
|
FileHash_t *pFileHashMaster = NULL; |
|
if ( idxMaster != m_treeMasterFileHashes.InvalidIndex() ) |
|
pFileHashMaster = &m_treeMasterFileHashes[idxMaster].m_FileHash; |
|
FileRenderHelper( file.m_userID, rgch, ufile.m_path.String(), ufile.m_filename.String(), &file.m_FileHash, 0, pFileHashMaster ); |
|
} |
|
} |
|
Msg( "Total user files %d %d %d \n", m_treeUserReportedFileHash.Count(), cTotalFiles, cTotalMatches ); |
|
Msg( "Total files %d, total with authoritative hashes %d \n", m_treeAllReportedFiles.Count(), m_treeMasterFileHashes.Count() ); |
|
Msg( "Matching files %d %d %d \n", m_cMatchedFile, m_cMatchedMasterFile, m_cMatchedMasterFileHash ); |
|
|
|
return 0; |
|
} |
|
|
|
int CPureFileTracker::ListAllTrackedFiles( bool bListAll, const char *pchFilenameFind, int nFileFractionMin, int nFileFractionMax ) |
|
{ |
|
g_pFileSystem->MarkAllCRCsUnverified(); |
|
|
|
int cTotal = 0; |
|
int cTotalMatch = 0; |
|
int count = 0; |
|
do |
|
{ |
|
CUnverifiedFileHash rgUnverifiedFiles[1]; |
|
count = g_pFileSystem->GetUnverifiedFileHashes( rgUnverifiedFiles, ARRAYSIZE( rgUnverifiedFiles ) ); |
|
|
|
if ( count && ( bListAll || ( pchFilenameFind && Q_stristr( rgUnverifiedFiles[0].m_Filename, pchFilenameFind ) && rgUnverifiedFiles[0].m_nFileFraction >= nFileFractionMin && rgUnverifiedFiles[0].m_nFileFraction <= nFileFractionMax ) ) ) |
|
{ |
|
USERID_t useridFake; |
|
useridFake.idtype = IDTYPE_STEAM; |
|
FileRenderHelper( useridFake, "", rgUnverifiedFiles[0].m_PathID, rgUnverifiedFiles[0].m_Filename, &rgUnverifiedFiles[0].m_FileHash, rgUnverifiedFiles[0].m_nFileFraction, NULL ); |
|
if ( rgUnverifiedFiles[0].m_FileHash.m_PackFileID ) |
|
{ |
|
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 ); |
|
} |
|
cTotalMatch++; |
|
} |
|
if ( count ) |
|
cTotal++; |
|
} while ( count ); |
|
|
|
Msg( "Total files %d Matching files %d \n", cTotal, cTotalMatch ); |
|
|
|
return 0; |
|
} |
|
|
|
|
|
CPureFileTracker g_PureFileTracker; |
|
|
|
//#define DEBUG_PURE_SERVER |
|
#ifdef DEBUG_PURE_SERVER |
|
void CC_ListPureServerFiles(const CCommand &args) |
|
{ |
|
if ( !sv.IsDedicated() ) |
|
return; |
|
g_PureFileTracker.ListUserFiles( args.ArgC() > 1 && (atoi(args[1]) > 0), NULL ); |
|
} |
|
|
|
static ConCommand svpurelistuserfiles("sv_pure_listuserfiles", CC_ListPureServerFiles, "ListPureServerFiles"); |
|
|
|
|
|
void CC_PureServerFindFile(const CCommand &args) |
|
{ |
|
if ( !sv.IsDedicated() ) |
|
return; |
|
g_PureFileTracker.ListUserFiles( false, args[1] ); |
|
} |
|
|
|
static ConCommand svpurefinduserfiles("sv_pure_finduserfiles", CC_PureServerFindFile, "ListPureServerFiles"); |
|
|
|
void CC_PureServerListTrackedFiles(const CCommand &args) |
|
{ |
|
// BUGBUG! Because this code is in engine instead of server, it exists in the client - ugh! |
|
// Remove this command from client before shipping for realz. |
|
//if ( !sv.IsDedicated() ) |
|
// return; |
|
int nFileFractionMin = args.ArgC() >= 3 ? Q_atoi(args[2]) : 0; |
|
int nFileFractionMax = args.ArgC() >= 4 ? Q_atoi(args[3]) : nFileFractionMin; |
|
if ( nFileFractionMax < 0 ) |
|
nFileFractionMax = 0x7FFFFFFF; |
|
g_PureFileTracker.ListAllTrackedFiles( args.ArgC() <= 1, args.ArgC() >= 2 ? args[1] : NULL, nFileFractionMin, nFileFractionMax ); |
|
} |
|
|
|
static ConCommand svpurelistfiles("sv_pure_listfiles", CC_PureServerListTrackedFiles, "ListPureServerFiles"); |
|
|
|
void CC_PureServerCheckVPKFiles(const CCommand &args) |
|
{ |
|
if ( sv.IsDedicated() ) |
|
Plat_BeginWatchdogTimer( 5 * 60 ); // reset watchdog timer to allow 5 minutes for the VPK check |
|
g_pFileSystem->CacheAllVPKFileHashes( false, true ); |
|
if ( sv.IsDedicated() ) |
|
Plat_EndWatchdogTimer(); |
|
} |
|
|
|
static ConCommand svpurecheckvpks("sv_pure_checkvpk", CC_PureServerCheckVPKFiles, "CheckPureServerVPKFiles"); |
|
|
|
#endif
|
|
|