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.

1766 lines
43 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: unusedcontent.cpp : Defines the entry point for the console application.
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include <stdio.h>
  8. #include <windows.h>
  9. #include <io.h>
  10. #include <sys/stat.h>
  11. #include "tier0/dbg.h"
  12. #pragma warning( disable : 4018 )
  13. #include "utlrbtree.h"
  14. #include "utlvector.h"
  15. #include "utldict.h"
  16. #include "filesystem.h"
  17. #include "FileSystem_Tools.h"
  18. #include "FileSystem_Helpers.h"
  19. #include "KeyValues.h"
  20. #include "cmdlib.h"
  21. #include "scriplib.h"
  22. #include "tier0/icommandline.h"
  23. #include "tier1/fmtstr.h"
  24. bool uselogfile = false;
  25. bool spewdeletions = false;
  26. bool showreferencedfiles = false;
  27. bool immediatedelete = false;
  28. bool printwhitelist = false;
  29. bool showmapfileusage = false;
  30. static char modname[MAX_PATH];
  31. static char g_szReslistDir[ MAX_PATH ] = "reslists/";
  32. namespace UnusedContent
  33. {
  34. class CCleanupUtlSymbolTable;
  35. //-----------------------------------------------------------------------------
  36. // forward declarations
  37. //-----------------------------------------------------------------------------
  38. class CUtlSymbolTable;
  39. //-----------------------------------------------------------------------------
  40. // This is a symbol, which is a easier way of dealing with strings.
  41. //-----------------------------------------------------------------------------
  42. typedef unsigned int UtlSymId_t;
  43. #define UC_UTL_INVAL_SYMBOL ((UnusedContent::UtlSymId_t)~0)
  44. class CUtlSymbol
  45. {
  46. public:
  47. // constructor, destructor
  48. CUtlSymbol() : m_Id(UTL_INVAL_SYMBOL) {}
  49. CUtlSymbol( UtlSymId_t id ) : m_Id(id) {}
  50. CUtlSymbol( char const* pStr );
  51. CUtlSymbol( CUtlSymbol const& sym ) : m_Id(sym.m_Id) {}
  52. // operator=
  53. CUtlSymbol& operator=( CUtlSymbol const& src ) { m_Id = src.m_Id; return *this; }
  54. // operator==
  55. bool operator==( CUtlSymbol const& src ) const { return m_Id == src.m_Id; }
  56. bool operator==( char const* pStr ) const;
  57. // Is valid?
  58. bool IsValid() const { return m_Id != UTL_INVAL_SYMBOL; }
  59. // Gets at the symbol
  60. operator UtlSymId_t const() const { return m_Id; }
  61. // Gets the string associated with the symbol
  62. char const* String( ) const;
  63. // Modules can choose to disable the static symbol table so to prevent accidental use of them.
  64. static void DisableStaticSymbolTable();
  65. protected:
  66. UtlSymId_t m_Id;
  67. // Initializes the symbol table
  68. static void Initialize();
  69. // returns the current symbol table
  70. static CUtlSymbolTable* CurrTable();
  71. // The standard global symbol table
  72. static CUtlSymbolTable* s_pSymbolTable;
  73. static bool s_bAllowStaticSymbolTable;
  74. friend class UnusedContent::CCleanupUtlSymbolTable;
  75. };
  76. //-----------------------------------------------------------------------------
  77. // CUtlSymbolTable:
  78. // description:
  79. // This class defines a symbol table, which allows us to perform mappings
  80. // of strings to symbols and back. The symbol class itself contains
  81. // a static version of this class for creating global strings, but this
  82. // class can also be instanced to create local symbol tables.
  83. //-----------------------------------------------------------------------------
  84. class CUtlSymbolTable
  85. {
  86. public:
  87. // constructor, destructor
  88. CUtlSymbolTable( int growSize = 0, int initSize = 32, bool caseInsensitive = false );
  89. ~CUtlSymbolTable();
  90. // Finds and/or creates a symbol based on the string
  91. CUtlSymbol AddString( char const* pString );
  92. // Finds the symbol for pString
  93. CUtlSymbol Find( char const* pString );
  94. // Look up the string associated with a particular symbol
  95. char const* String( CUtlSymbol id ) const;
  96. // Remove all symbols in the table.
  97. void RemoveAll();
  98. int GetNumStrings( void ) const
  99. {
  100. return m_Lookup.Count();
  101. }
  102. protected:
  103. class CStringPoolIndex
  104. {
  105. public:
  106. inline CStringPoolIndex()
  107. {
  108. }
  109. inline CStringPoolIndex( unsigned int iPool, unsigned int iOffset )
  110. {
  111. m_iPool = iPool;
  112. m_iOffset = iOffset;
  113. }
  114. inline bool operator==( const CStringPoolIndex &other ) const
  115. {
  116. return m_iPool == other.m_iPool && m_iOffset == other.m_iOffset;
  117. }
  118. unsigned int m_iPool; // Index into m_StringPools.
  119. unsigned int m_iOffset; // Index into the string pool.
  120. };
  121. // Stores the symbol lookup
  122. CUtlRBTree<CStringPoolIndex, unsigned int> m_Lookup;
  123. typedef struct
  124. {
  125. int m_TotalLen; // How large is
  126. int m_SpaceUsed;
  127. char m_Data[1];
  128. } StringPool_t;
  129. // stores the string data
  130. CUtlVector<StringPool_t*> m_StringPools;
  131. private:
  132. int FindPoolWithSpace( int len ) const;
  133. const char* StringFromIndex( const CStringPoolIndex &index ) const;
  134. // Less function, for sorting strings
  135. static bool SymLess( CStringPoolIndex const& i1, CStringPoolIndex const& i2 );
  136. // case insensitive less function
  137. static bool SymLessi( CStringPoolIndex const& i1, CStringPoolIndex const& i2 );
  138. };
  139. //=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
  140. //
  141. // The copyright to the contents herein is the property of Valve, L.L.C.
  142. // The contents may be used and/or copied only with the written permission of
  143. // Valve, L.L.C., or in accordance with the terms and conditions stipulated in
  144. // the agreement/contract under which the contents have been supplied.
  145. //
  146. // Purpose: Defines a symbol table
  147. //
  148. // $Header: $
  149. // $NoKeywords: $
  150. //=============================================================================
  151. #pragma warning (disable:4514)
  152. #define INVALID_STRING_INDEX CStringPoolIndex( 0xFFFF, 0xFFFF )
  153. #define MIN_STRING_POOL_SIZE 2048
  154. //-----------------------------------------------------------------------------
  155. // globals
  156. //-----------------------------------------------------------------------------
  157. CUtlSymbolTable* CUtlSymbol::s_pSymbolTable = 0;
  158. bool CUtlSymbol::s_bAllowStaticSymbolTable = true;
  159. //-----------------------------------------------------------------------------
  160. // symbol methods
  161. //-----------------------------------------------------------------------------
  162. void CUtlSymbol::Initialize()
  163. {
  164. // If this assert fails, then the module that this call is in has chosen to disallow
  165. // use of the static symbol table. Usually, it's to prevent confusion because it's easy
  166. // to accidentally use the global symbol table when you really want to use a specific one.
  167. Assert( s_bAllowStaticSymbolTable );
  168. // necessary to allow us to create global symbols
  169. static bool symbolsInitialized = false;
  170. if (!symbolsInitialized)
  171. {
  172. s_pSymbolTable = new CUtlSymbolTable;
  173. symbolsInitialized = true;
  174. }
  175. }
  176. //-----------------------------------------------------------------------------
  177. // Purpose: Singleton to delete table on exit from module
  178. //-----------------------------------------------------------------------------
  179. class CCleanupUtlSymbolTable
  180. {
  181. public:
  182. ~CCleanupUtlSymbolTable()
  183. {
  184. delete CUtlSymbol::s_pSymbolTable;
  185. CUtlSymbol::s_pSymbolTable = NULL;
  186. }
  187. };
  188. static CCleanupUtlSymbolTable g_CleanupSymbolTable;
  189. CUtlSymbolTable* CUtlSymbol::CurrTable()
  190. {
  191. Initialize();
  192. return s_pSymbolTable;
  193. }
  194. //-----------------------------------------------------------------------------
  195. // string->symbol->string
  196. //-----------------------------------------------------------------------------
  197. CUtlSymbol::CUtlSymbol( char const* pStr )
  198. {
  199. m_Id = CurrTable()->AddString( pStr );
  200. }
  201. char const* CUtlSymbol::String( ) const
  202. {
  203. return CurrTable()->String(m_Id);
  204. }
  205. void CUtlSymbol::DisableStaticSymbolTable()
  206. {
  207. s_bAllowStaticSymbolTable = false;
  208. }
  209. //-----------------------------------------------------------------------------
  210. // checks if the symbol matches a string
  211. //-----------------------------------------------------------------------------
  212. bool CUtlSymbol::operator==( char const* pStr ) const
  213. {
  214. if (m_Id == UTL_INVAL_SYMBOL)
  215. return false;
  216. return strcmp( String(), pStr ) == 0;
  217. }
  218. //-----------------------------------------------------------------------------
  219. // symbol table stuff
  220. //-----------------------------------------------------------------------------
  221. struct LessCtx_t
  222. {
  223. char const* m_pUserString;
  224. CUtlSymbolTable* m_pTable;
  225. LessCtx_t( ) : m_pUserString(0), m_pTable(0) {}
  226. };
  227. static LessCtx_t g_LessCtx;
  228. inline const char* CUtlSymbolTable::StringFromIndex( const CStringPoolIndex &index ) const
  229. {
  230. Assert( index.m_iPool < m_StringPools.Count() );
  231. Assert( index.m_iOffset < m_StringPools[index.m_iPool]->m_TotalLen );
  232. return &m_StringPools[index.m_iPool]->m_Data[index.m_iOffset];
  233. }
  234. bool CUtlSymbolTable::SymLess( CStringPoolIndex const& i1, CStringPoolIndex const& i2 )
  235. {
  236. char const* str1 = (i1 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString :
  237. g_LessCtx.m_pTable->StringFromIndex( i1 );
  238. char const* str2 = (i2 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString :
  239. g_LessCtx.m_pTable->StringFromIndex( i2 );
  240. return strcmp( str1, str2 ) < 0;
  241. }
  242. bool CUtlSymbolTable::SymLessi( CStringPoolIndex const& i1, CStringPoolIndex const& i2 )
  243. {
  244. char const* str1 = (i1 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString :
  245. g_LessCtx.m_pTable->StringFromIndex( i1 );
  246. char const* str2 = (i2 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString :
  247. g_LessCtx.m_pTable->StringFromIndex( i2 );
  248. return strcmpi( str1, str2 ) < 0;
  249. }
  250. //-----------------------------------------------------------------------------
  251. // constructor, destructor
  252. //-----------------------------------------------------------------------------
  253. CUtlSymbolTable::CUtlSymbolTable( int growSize, int initSize, bool caseInsensitive ) :
  254. m_Lookup( growSize, initSize, caseInsensitive ? SymLessi : SymLess ), m_StringPools( 8 )
  255. {
  256. }
  257. CUtlSymbolTable::~CUtlSymbolTable()
  258. {
  259. }
  260. CUtlSymbol CUtlSymbolTable::Find( char const* pString )
  261. {
  262. if (!pString)
  263. return CUtlSymbol();
  264. // Store a special context used to help with insertion
  265. g_LessCtx.m_pUserString = pString;
  266. g_LessCtx.m_pTable = this;
  267. // Passing this special invalid symbol makes the comparison function
  268. // use the string passed in the context
  269. UtlSymId_t idx = m_Lookup.Find( INVALID_STRING_INDEX );
  270. return CUtlSymbol( idx );
  271. }
  272. int CUtlSymbolTable::FindPoolWithSpace( int len ) const
  273. {
  274. for ( int i=0; i < m_StringPools.Count(); i++ )
  275. {
  276. StringPool_t *pPool = m_StringPools[i];
  277. if ( (pPool->m_TotalLen - pPool->m_SpaceUsed) >= len )
  278. {
  279. return i;
  280. }
  281. }
  282. return -1;
  283. }
  284. //-----------------------------------------------------------------------------
  285. // Finds and/or creates a symbol based on the string
  286. //-----------------------------------------------------------------------------
  287. CUtlSymbol CUtlSymbolTable::AddString( char const* pString )
  288. {
  289. if (!pString)
  290. return CUtlSymbol( UTL_INVAL_SYMBOL );
  291. CUtlSymbol id = Find( pString );
  292. if (id.IsValid())
  293. return id;
  294. int len = strlen(pString) + 1;
  295. // Find a pool with space for this string, or allocate a new one.
  296. int iPool = FindPoolWithSpace( len );
  297. if ( iPool == -1 )
  298. {
  299. // Add a new pool.
  300. int newPoolSize = max( len, MIN_STRING_POOL_SIZE );
  301. StringPool_t *pPool = (StringPool_t*)malloc( sizeof( StringPool_t ) + newPoolSize - 1 );
  302. pPool->m_TotalLen = newPoolSize;
  303. pPool->m_SpaceUsed = 0;
  304. iPool = m_StringPools.AddToTail( pPool );
  305. }
  306. // Copy the string in.
  307. StringPool_t *pPool = m_StringPools[iPool];
  308. Assert( pPool->m_SpaceUsed < 0xFFFF ); // This should never happen, because if we had a string > 64k, it
  309. // would have been given its entire own pool.
  310. unsigned int iStringOffset = pPool->m_SpaceUsed;
  311. memcpy( &pPool->m_Data[pPool->m_SpaceUsed], pString, len );
  312. pPool->m_SpaceUsed += len;
  313. // didn't find, insert the string into the vector.
  314. CStringPoolIndex index;
  315. index.m_iPool = iPool;
  316. index.m_iOffset = iStringOffset;
  317. UtlSymId_t idx = m_Lookup.Insert( index );
  318. return CUtlSymbol( idx );
  319. }
  320. //-----------------------------------------------------------------------------
  321. // Look up the string associated with a particular symbol
  322. //-----------------------------------------------------------------------------
  323. char const* CUtlSymbolTable::String( CUtlSymbol id ) const
  324. {
  325. if (!id.IsValid())
  326. return "";
  327. Assert( m_Lookup.IsValidIndex((UtlSymId_t)id) );
  328. return StringFromIndex( m_Lookup[id] );
  329. }
  330. //-----------------------------------------------------------------------------
  331. // Remove all symbols in the table.
  332. //-----------------------------------------------------------------------------
  333. void CUtlSymbolTable::RemoveAll()
  334. {
  335. m_Lookup.RemoveAll();
  336. for ( int i=0; i < m_StringPools.Count(); i++ )
  337. free( m_StringPools[i] );
  338. m_StringPools.RemoveAll();
  339. }
  340. } // Namespace UnusedContent
  341. struct AnalysisData
  342. {
  343. UnusedContent::CUtlSymbolTable symbols;
  344. };
  345. char *directories_to_check[] =
  346. {
  347. "",
  348. "bin",
  349. "maps",
  350. "materials",
  351. "models",
  352. "scenes",
  353. "scripts",
  354. "sound",
  355. "hl2",
  356. };
  357. char *directories_to_ignore[] = // don't include these dirs in the others list
  358. {
  359. "reslists",
  360. "reslists_temp",
  361. "logs",
  362. "media",
  363. "downloads",
  364. "save",
  365. "screenshots",
  366. "testscripts",
  367. "logos"
  368. };
  369. enum
  370. {
  371. REFERENCED_NO = 0,
  372. REFERENCED_WHITELIST,
  373. REFERENCED_GAME
  374. };
  375. struct FileEntry
  376. {
  377. FileEntry()
  378. {
  379. sym = UC_UTL_INVAL_SYMBOL;
  380. size = 0;
  381. referenced = REFERENCED_NO;
  382. }
  383. UnusedContent::CUtlSymbol sym;
  384. unsigned int size;
  385. int referenced;
  386. };
  387. struct ReferencedFile
  388. {
  389. ReferencedFile()
  390. {
  391. sym = UC_UTL_INVAL_SYMBOL;
  392. }
  393. ReferencedFile( const ReferencedFile& src )
  394. {
  395. sym = src.sym;
  396. maplist.RemoveAll();
  397. int c = src.maplist.Count();
  398. for ( int i = 0; i < c; ++i )
  399. {
  400. maplist.AddToTail( src.maplist[ i ] );
  401. }
  402. }
  403. ReferencedFile & operator =( const ReferencedFile& src )
  404. {
  405. if ( this == &src )
  406. return *this;
  407. sym = src.sym;
  408. maplist.RemoveAll();
  409. int c = src.maplist.Count();
  410. for ( int i = 0; i < c; ++i )
  411. {
  412. maplist.AddToTail( src.maplist[ i ] );
  413. }
  414. return *this;
  415. }
  416. UnusedContent::CUtlSymbol sym;
  417. CUtlVector< UnusedContent::CUtlSymbol > maplist;
  418. };
  419. static AnalysisData g_Analysis;
  420. IFileSystem *filesystem = NULL;
  421. static CUniformRandomStream g_Random;
  422. IUniformRandomStream *random = &g_Random;
  423. static bool spewed = false;
  424. SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg )
  425. {
  426. spewed = true;
  427. printf( "%s", pMsg );
  428. OutputDebugString( pMsg );
  429. if ( type == SPEW_ERROR )
  430. {
  431. printf( "\n" );
  432. OutputDebugString( "\n" );
  433. exit(-1);
  434. }
  435. return SPEW_CONTINUE;
  436. }
  437. char *va( const char *fmt, ... )
  438. {
  439. static char string[ 8192 ];
  440. va_list va;
  441. va_start( va, fmt );
  442. vsprintf( string, fmt, va );
  443. va_end( va );
  444. return string;
  445. }
  446. //-----------------------------------------------------------------------------
  447. // Purpose:
  448. // Input : depth -
  449. // *fmt -
  450. // ... -
  451. //-----------------------------------------------------------------------------
  452. void vprint( int depth, const char *fmt, ... )
  453. {
  454. char string[ 8192 ];
  455. va_list va;
  456. va_start( va, fmt );
  457. vsprintf( string, fmt, va );
  458. va_end( va );
  459. FILE *fp = NULL;
  460. if ( uselogfile )
  461. {
  462. fp = fopen( "log.txt", "ab" );
  463. }
  464. while ( depth-- > 0 )
  465. {
  466. printf( " " );
  467. OutputDebugString( " " );
  468. if ( fp )
  469. {
  470. fprintf( fp, " " );
  471. }
  472. }
  473. ::printf( "%s", string );
  474. OutputDebugString( string );
  475. if ( fp )
  476. {
  477. char *p = string;
  478. while ( *p )
  479. {
  480. if ( *p == '\n' )
  481. {
  482. fputc( '\r', fp );
  483. }
  484. fputc( *p, fp );
  485. p++;
  486. }
  487. fclose( fp );
  488. }
  489. }
  490. void logprint( char const *logfile, const char *fmt, ... )
  491. {
  492. char string[ 8192 ];
  493. va_list va;
  494. va_start( va, fmt );
  495. vsprintf( string, fmt, va );
  496. va_end( va );
  497. FILE *fp = NULL;
  498. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.Find( logfile );
  499. static CUtlRBTree< UnusedContent::CUtlSymbol, int > previousfiles( 0, 0, DefLessFunc( UnusedContent::CUtlSymbol ) );
  500. if ( previousfiles.Find( sym ) == previousfiles.InvalidIndex() )
  501. {
  502. previousfiles.Insert( sym );
  503. fp = fopen( logfile, "wb" );
  504. }
  505. else
  506. {
  507. fp = fopen( logfile, "ab" );
  508. }
  509. if ( fp )
  510. {
  511. char *p = string;
  512. while ( *p )
  513. {
  514. if ( *p == '\n' )
  515. {
  516. fputc( '\r', fp );
  517. }
  518. fputc( *p, fp );
  519. p++;
  520. }
  521. fclose( fp );
  522. }
  523. }
  524. void Con_Printf( const char *fmt, ... )
  525. {
  526. va_list args;
  527. static char output[1024];
  528. va_start( args, fmt );
  529. vprintf( fmt, args );
  530. vsprintf( output, fmt, args );
  531. vprint( 0, output );
  532. }
  533. bool ShouldCheckDir( char const *dirname );
  534. bool ShouldIgnoreDir( const char *dirname );
  535. void BuildFileList_R( int depth, CUtlVector< FileEntry >& files, CUtlVector< FileEntry > * otherfiles, char const *dir, char const *wild, int skipchars )
  536. {
  537. WIN32_FIND_DATA wfd;
  538. char directory[ 256 ];
  539. char filename[ 256 ];
  540. HANDLE ff;
  541. bool canrecurse = true;
  542. if ( !Q_stricmp( wild, "..." ) )
  543. {
  544. canrecurse = true;
  545. sprintf( directory, "%s%s%s", dir[0] == '\\' ? dir + 1 : dir, dir[0] != 0 ? "\\" : "", "*.*" );
  546. }
  547. else
  548. {
  549. sprintf( directory, "%s%s%s", dir, dir[0] != 0 ? "\\" : "", wild );
  550. }
  551. int dirlen = Q_strlen( dir );
  552. if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE )
  553. return;
  554. do
  555. {
  556. if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
  557. {
  558. bool useOtherFiles = false;
  559. if ( wfd.cFileName[ 0 ] == '.' )
  560. continue;
  561. if ( depth == 0 && !ShouldCheckDir( wfd.cFileName ) && otherfiles )
  562. {
  563. if ( !ShouldIgnoreDir( wfd.cFileName ) )
  564. {
  565. useOtherFiles = true;
  566. }
  567. }
  568. if ( !canrecurse )
  569. continue;
  570. // Recurse down directory
  571. if ( dir[0] )
  572. {
  573. sprintf( filename, "%s\\%s", dir, wfd.cFileName );
  574. }
  575. else
  576. {
  577. sprintf( filename, "%s", wfd.cFileName );
  578. }
  579. BuildFileList_R( depth + 1, useOtherFiles ? *otherfiles: files, NULL, filename, wild, skipchars );
  580. }
  581. else
  582. {
  583. if (!stricmp(wfd.cFileName, "vssver.scc"))
  584. continue;
  585. char filename[ MAX_PATH ];
  586. if ( dirlen <= skipchars )
  587. {
  588. Q_snprintf( filename, sizeof( filename ), "%s", wfd.cFileName );
  589. }
  590. else
  591. {
  592. Q_snprintf( filename, sizeof( filename ), "%s\\%s", &dir[ skipchars ], wfd.cFileName );
  593. }
  594. _strlwr( filename );
  595. Q_FixSlashes( filename );
  596. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( filename );
  597. FileEntry entry;
  598. entry.sym = sym;
  599. int size = g_pFileSystem->Size( filename );
  600. entry.size = size >= 0 ? (unsigned int)size : 0;
  601. files.AddToTail( entry );
  602. if ( !( files.Count() % 3000 ) )
  603. {
  604. vprint( 0, "...found %i files\n", files.Count() );
  605. }
  606. }
  607. } while ( FindNextFile( ff, &wfd ) );
  608. }
  609. void BuildFileList( int depth, CUtlVector< FileEntry >& files, CUtlVector< FileEntry > * otherfiles, char const *rootdir, int skipchars )
  610. {
  611. files.RemoveAll();
  612. Assert( otherfiles );
  613. otherfiles->RemoveAll();
  614. BuildFileList_R( depth, files, otherfiles, rootdir, "...", skipchars );
  615. }
  616. void BuildFileListWildcard( int depth, CUtlVector< FileEntry >& files, char const *rootdir, char const *wildcard, int skipchars )
  617. {
  618. files.RemoveAll();
  619. BuildFileList_R( depth, files, NULL, rootdir, wildcard, skipchars );
  620. }
  621. static CUtlVector< UnusedContent::CUtlSymbol > g_DirList;
  622. static CUtlVector< UnusedContent::CUtlSymbol > g_IgnoreDir;
  623. bool ShouldCheckDir( char const *dirname )
  624. {
  625. int c = g_DirList.Count();
  626. for ( int i = 0; i < c; ++i )
  627. {
  628. char const *check = g_Analysis.symbols.String( g_DirList[ i ] );
  629. if ( !Q_stricmp( dirname, check ) )
  630. return true;
  631. }
  632. vprint( 1, "Skipping dir %s\n", dirname );
  633. return false;
  634. }
  635. bool ShouldIgnoreDir( const char *dirname )
  636. {
  637. int c = g_IgnoreDir.Count();
  638. for ( int i = 0; i < c; ++i )
  639. {
  640. char const *check = g_Analysis.symbols.String( g_IgnoreDir[ i ] );
  641. if ( Q_stristr( dirname, "reslists" ) )
  642. {
  643. vprint( 1, "Ignoring dir %s\n", dirname );
  644. return true;
  645. }
  646. if ( !Q_stricmp( dirname, check ) )
  647. {
  648. vprint( 1, "Ignoring dir %s\n", dirname );
  649. return true;
  650. }
  651. }
  652. return false;
  653. }
  654. void AddCheckdir( char const *dirname )
  655. {
  656. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( dirname );
  657. g_DirList.AddToTail( sym );
  658. vprint( 1, "AddCheckdir[ \"%s\" ]\n", dirname );
  659. }
  660. void AddIgnoredir( const char *dirname )
  661. {
  662. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( dirname );
  663. g_IgnoreDir.AddToTail( sym );
  664. vprint( 1, "AddIgnoredir[ \"%s\" ]\n", dirname );
  665. }
  666. #define UNUSEDCONTENT_CFG "unusedcontent.cfg"
  667. void BuildCheckdirList()
  668. {
  669. vprint( 0, "Checking for dirlist\n" );
  670. // Search for unusedcontent.cfg file
  671. if ( g_pFileSystem->FileExists( UNUSEDCONTENT_CFG, "GAME") )
  672. {
  673. KeyValues *kv = new KeyValues( UNUSEDCONTENT_CFG );
  674. if ( kv )
  675. {
  676. if ( kv->LoadFromFile( g_pFileSystem, UNUSEDCONTENT_CFG, "GAME" ) )
  677. {
  678. for ( KeyValues *sub = kv->GetFirstSubKey(); sub; sub = sub->GetNextKey() )
  679. {
  680. if ( !Q_stricmp( sub->GetName(), "dir" ) )
  681. {
  682. AddCheckdir( sub->GetString() );
  683. }
  684. else if ( !Q_stricmp( sub->GetName(), "ignore" ) )
  685. {
  686. AddIgnoredir( sub->GetString() );
  687. }
  688. else
  689. {
  690. vprint( 1, "Unknown subkey '%s' in %s\n", sub->GetName(), UNUSEDCONTENT_CFG );
  691. }
  692. }
  693. }
  694. kv->deleteThis();
  695. }
  696. }
  697. else
  698. {
  699. int c = ARRAYSIZE( directories_to_check );
  700. int i;
  701. for ( i = 0; i < c; ++i )
  702. {
  703. AddCheckdir( directories_to_check[ i ] );
  704. }
  705. // add the list of dirs to ignore from the others lists
  706. c = ARRAYSIZE( directories_to_ignore );
  707. for ( i = 0; i < c; ++i )
  708. {
  709. AddIgnoredir( directories_to_ignore[ i ] );
  710. }
  711. }
  712. }
  713. static CUtlRBTree< UnusedContent::CUtlSymbol, int > g_WhiteList( 0, 0, DefLessFunc( UnusedContent::CUtlSymbol ) );
  714. #define WHITELIST_FILE "whitelist.cfg"
  715. static int wl_added = 0;
  716. static int wl_removed = 0;
  717. void AddToWhiteList( char const *path )
  718. {
  719. vprint( 2, "+\t'%s'\n", path );
  720. char dir[ 512 ];
  721. Q_strncpy( dir, path, sizeof( dir ) );
  722. // Get the base filename from the path
  723. _strlwr( dir );
  724. Q_FixSlashes( dir );
  725. CUtlVector< FileEntry > files;
  726. char *lastslash = strrchr( dir, '\\' );
  727. if ( lastslash == 0 )
  728. {
  729. BuildFileListWildcard( 1, files, "", dir, 0 );
  730. }
  731. else
  732. {
  733. char *wild = lastslash + 1;
  734. *lastslash = 0;
  735. BuildFileListWildcard( 1, files, dir, wild, 0 );
  736. }
  737. int c = files.Count();
  738. for ( int i = 0; i < c; ++i )
  739. {
  740. UnusedContent::CUtlSymbol sym = files[ i ].sym;
  741. if ( g_WhiteList.Find( sym ) == g_WhiteList.InvalidIndex() )
  742. {
  743. g_WhiteList.Insert( sym );
  744. ++wl_added;
  745. }
  746. }
  747. }
  748. void RemoveFromWhiteList( char const *path )
  749. {
  750. vprint( 2, "-\t'%s'\n", path );
  751. char dir[ 512 ];
  752. Q_strncpy( dir, path, sizeof( dir ) );
  753. // Get the base filename from the path
  754. _strlwr( dir );
  755. Q_FixSlashes( dir );
  756. CUtlVector< FileEntry > files;
  757. char *lastslash = strrchr( dir, '\\' );
  758. if ( lastslash == 0 )
  759. {
  760. BuildFileListWildcard( 1, files, "", dir, 0 );
  761. }
  762. else
  763. {
  764. char *wild = lastslash + 1;
  765. *lastslash = 0;
  766. BuildFileListWildcard( 1, files, dir, wild, 0 );
  767. }
  768. int c = files.Count();
  769. for ( int i = 0; i < c; ++i )
  770. {
  771. UnusedContent::CUtlSymbol sym = files[ i ].sym;
  772. int idx = g_WhiteList.Find( sym );
  773. if ( idx != g_WhiteList.InvalidIndex() )
  774. {
  775. g_WhiteList.RemoveAt( idx );
  776. ++wl_removed;
  777. }
  778. }
  779. }
  780. void BuildWhiteList()
  781. {
  782. // Search for unusedcontent.cfg file
  783. if ( !g_pFileSystem->FileExists( WHITELIST_FILE ) )
  784. {
  785. vprint( 1, "Running with no whitelist.cfg file!!!\n" );
  786. return;
  787. }
  788. vprint( 1, "\nBuilding whitelist\n" );
  789. KeyValues *kv = new KeyValues( WHITELIST_FILE );
  790. if ( kv )
  791. {
  792. if ( kv->LoadFromFile( g_pFileSystem, WHITELIST_FILE, NULL ) )
  793. {
  794. for ( KeyValues *sub = kv->GetFirstSubKey(); sub; sub = sub->GetNextKey() )
  795. {
  796. if ( !Q_stricmp( sub->GetName(), "add" ) )
  797. {
  798. AddToWhiteList( sub->GetString() );
  799. }
  800. else if ( !Q_stricmp( sub->GetName(), "remove" ) )
  801. {
  802. RemoveFromWhiteList( sub->GetString() );
  803. }
  804. else
  805. {
  806. vprint( 1, "Unknown subkey '%s' in %s\n", sub->GetName(), WHITELIST_FILE );
  807. }
  808. }
  809. }
  810. kv->deleteThis();
  811. }
  812. if ( verbose || printwhitelist )
  813. {
  814. vprint( 1, "Whitelist:\n\n" );
  815. for ( int i = g_WhiteList.FirstInorder();
  816. i != g_WhiteList.InvalidIndex();
  817. i = g_WhiteList.NextInorder( i ) )
  818. {
  819. UnusedContent::CUtlSymbol& sym = g_WhiteList[ i ];
  820. char const *resolved = g_Analysis.symbols.String( sym );
  821. vprint( 2, " %s\n", resolved );
  822. }
  823. }
  824. // dump the whitelist file list anyway
  825. {
  826. filesystem->RemoveFile( "whitelist_files.txt", "GAME" );
  827. for ( int i = g_WhiteList.FirstInorder();
  828. i != g_WhiteList.InvalidIndex();
  829. i = g_WhiteList.NextInorder( i ) )
  830. {
  831. UnusedContent::CUtlSymbol& sym = g_WhiteList[ i ];
  832. char const *resolved = g_Analysis.symbols.String( sym );
  833. logprint( "whitelist_files.txt", "\"%s\"\n", resolved );
  834. }
  835. }
  836. vprint( 1, "Whitelist resolves to %d files (added %i/removed %i)\n\n", g_WhiteList.Count(), wl_added, wl_removed );
  837. }
  838. //-----------------------------------------------------------------------------
  839. // Purpose:
  840. //-----------------------------------------------------------------------------
  841. void printusage( void )
  842. {
  843. vprint( 0, "usage: unusedcontent maplistfile\n\
  844. \t Note that you must have generated the reslistsfile output via the engine first!!!\n\
  845. \t-d = spew command prompt deletion instructions to deletions.bat\n\
  846. \t-v = verbose output\n\
  847. \t-l = log to file log.txt\n\
  848. \t-r = print out all referenced files\n\
  849. \t-m = generate referenced.csv with map counts\n\
  850. \t-w = print out whitelist\n\
  851. \t-i = delete unused files immediately\n\
  852. \t-f <reslistdir> : specify reslists folder, 'reslists' assumed by default\n\
  853. \t\tmaps/\n\
  854. \t\tmaterials/\n\
  855. \t\tmodels/\n\
  856. \t\tsounds/\n\
  857. \ne.g.: unusedcontent -r maplist.txt\n" );
  858. // Exit app
  859. exit( 1 );
  860. }
  861. void ParseFilesFromResList( UnusedContent::CUtlSymbol & resfilesymbol, CUtlRBTree< ReferencedFile, int >& files, char const *resfile )
  862. {
  863. int addedStrings = 0;
  864. int resourcesConsidered = 0;
  865. int offset = Q_strlen( gamedir );
  866. char basedir[MAX_PATH];
  867. Q_strncpy( basedir, gamedir, sizeof( basedir ) );
  868. if ( !Q_StripLastDir( basedir, sizeof( basedir ) ) )
  869. Error( "Can't get basedir from %s.", gamedir );
  870. FileHandle_t resfilehandle;
  871. resfilehandle = g_pFileSystem->Open( resfile, "rb" );
  872. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  873. {
  874. // Read in the entire file
  875. int length = g_pFileSystem->Size(resfilehandle);
  876. if ( length > 0 )
  877. {
  878. char *pStart = (char *)new char[ length + 1 ];
  879. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) )
  880. {
  881. pStart[ length ] = 0;
  882. char *pFileList = pStart;
  883. char token[512];
  884. while ( 1 )
  885. {
  886. pFileList = ParseFile( pFileList, token, NULL );
  887. if ( !pFileList )
  888. break;
  889. if ( strlen( token ) > 0 )
  890. {
  891. char szFileName[ 256 ];
  892. Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", basedir, token );
  893. _strlwr( szFileName );
  894. Q_FixSlashes( szFileName );
  895. while ( szFileName[ strlen( szFileName ) - 1 ] == '\n' ||
  896. szFileName[ strlen( szFileName ) - 1 ] == '\r' )
  897. {
  898. szFileName[ strlen( szFileName ) - 1 ] = 0;
  899. }
  900. if ( Q_strnicmp( szFileName, gamedir, offset ) )
  901. continue;
  902. char *pFile = szFileName + offset;
  903. ++resourcesConsidered;
  904. ReferencedFile rf;
  905. rf.sym = g_Analysis.symbols.AddString( pFile );
  906. int idx = files.Find( rf );
  907. if ( idx == files.InvalidIndex() )
  908. {
  909. ++addedStrings;
  910. rf.maplist.AddToTail( resfilesymbol );
  911. files.Insert( rf );
  912. }
  913. else
  914. {
  915. //
  916. ReferencedFile & slot = files[ idx ];
  917. if ( slot.maplist.Find( resfilesymbol ) == slot.maplist.InvalidIndex() )
  918. {
  919. slot.maplist.AddToTail( resfilesymbol );
  920. }
  921. }
  922. }
  923. }
  924. }
  925. delete[] pStart;
  926. }
  927. g_pFileSystem->Close(resfilehandle);
  928. }
  929. int filesFound = addedStrings;
  930. vprint( 1, "Found %i new resources (%i total) in %s\n", filesFound, resourcesConsidered, resfile );
  931. }
  932. bool BuildReferencedFileList( CUtlVector< UnusedContent::CUtlSymbol >& resfiles, CUtlRBTree< ReferencedFile, int >& files, const char *resfile )
  933. {
  934. // Load the reslist file
  935. FileHandle_t resfilehandle;
  936. resfilehandle = g_pFileSystem->Open( resfile, "rb" );
  937. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  938. {
  939. // Read in and parse mapcycle.txt
  940. int length = g_pFileSystem->Size(resfilehandle);
  941. if ( length > 0 )
  942. {
  943. char *pStart = (char *)new char[ length + 1 ];
  944. if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) )
  945. )
  946. {
  947. pStart[ length ] = 0;
  948. char *pFileList = pStart;
  949. while ( 1 )
  950. {
  951. char szResList[ 256 ];
  952. pFileList = COM_Parse( pFileList );
  953. if ( strlen( com_token ) <= 0 )
  954. break;
  955. Q_snprintf(szResList, sizeof( szResList ), "%s%s.lst", g_szReslistDir, com_token );
  956. _strlwr( szResList );
  957. Q_FixSlashes( szResList );
  958. if ( !g_pFileSystem->FileExists( szResList ) )
  959. {
  960. vprint( 0, "Couldn't find %s\n", szResList );
  961. continue;
  962. }
  963. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( szResList );
  964. resfiles.AddToTail( sym );
  965. }
  966. }
  967. delete[] pStart;
  968. }
  969. g_pFileSystem->Close(resfilehandle);
  970. }
  971. else
  972. {
  973. Error( "Unable to open reslist file %s\n", resfile );
  974. exit( -1 );
  975. }
  976. if ( g_pFileSystem->FileExists( CFmtStr( "%sall.lst", g_szReslistDir ) ) )
  977. {
  978. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( CFmtStr( "%sall.lst", g_szReslistDir ) );
  979. resfiles.AddToTail( sym );
  980. }
  981. if ( g_pFileSystem->FileExists( CFmtStr( "%sengine.lst", g_szReslistDir ) ) )
  982. {
  983. UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( CFmtStr( "%sengine.lst", g_szReslistDir ) );
  984. resfiles.AddToTail( sym );
  985. }
  986. // Do we have any resfiles?
  987. if ( resfiles.Count() <= 0 )
  988. {
  989. vprint( 0, "%s didn't have any actual .lst files in the reslists folder, have you run the engine with %s\n", resfile,
  990. "-makereslists -usereslistfile maplist.txt" );
  991. return false;
  992. }
  993. vprint( 0, "Parsed %i reslist files\n", resfiles.Count() );
  994. // Now load in each res file
  995. int c = resfiles.Count();
  996. for ( int i = 0; i < c; ++i )
  997. {
  998. UnusedContent::CUtlSymbol& filename = resfiles[ i ];
  999. char fn[ 256 ];
  1000. Q_strncpy( fn, g_Analysis.symbols.String( filename ), sizeof( fn ) );
  1001. ParseFilesFromResList( filename, files, fn );
  1002. }
  1003. return true;
  1004. }
  1005. //-----------------------------------------------------------------------------
  1006. // Purpose:
  1007. //-----------------------------------------------------------------------------
  1008. void CheckLogFile( void )
  1009. {
  1010. if ( uselogfile )
  1011. {
  1012. _unlink( "log.txt" );
  1013. vprint( 0, " Outputting to log.txt\n" );
  1014. }
  1015. }
  1016. void PrintHeader()
  1017. {
  1018. vprint( 0, "Valve Software - unusedcontent.exe (%s)\n", __DATE__ );
  1019. vprint( 0, "--- Compares reslists with actual game content tree to show unreferenced content and stats ---\n" );
  1020. }
  1021. static bool ReferencedFileLessFunc( const ReferencedFile &lhs, const ReferencedFile &rhs )
  1022. {
  1023. char const *s1 = g_Analysis.symbols.String( lhs.sym );
  1024. char const *s2 = g_Analysis.symbols.String( rhs.sym );
  1025. return Q_stricmp( s1, s2 ) < 0;
  1026. }
  1027. static bool FileEntryLessFunc( const FileEntry &lhs, const FileEntry &rhs )
  1028. {
  1029. char const *s1 = g_Analysis.symbols.String( lhs.sym );
  1030. char const *s2 = g_Analysis.symbols.String( rhs.sym );
  1031. return Q_stricmp( s1, s2 ) < 0;
  1032. }
  1033. static bool RefFileLessFunc( const ReferencedFile &lhs, const ReferencedFile &rhs )
  1034. {
  1035. char const *s1 = g_Analysis.symbols.String( lhs.sym );
  1036. char const *s2 = g_Analysis.symbols.String( rhs.sym );
  1037. return Q_stricmp( s1, s2 ) < 0;
  1038. }
  1039. struct DirEntry
  1040. {
  1041. DirEntry()
  1042. {
  1043. total = 0;
  1044. unreferenced = 0;
  1045. whitelist = 0;
  1046. }
  1047. double total;
  1048. double unreferenced;
  1049. double whitelist;
  1050. };
  1051. void Correlate( CUtlRBTree< ReferencedFile, int >& referencedfiles, CUtlVector< FileEntry >& contentfiles, const char *modname )
  1052. {
  1053. int i;
  1054. int c = contentfiles.Count();
  1055. double totalDiskSize = 0;
  1056. double totalReferencedDiskSize = 0;
  1057. double totalWhiteListDiskSize = 0;
  1058. for ( i = 0; i < c; ++i )
  1059. {
  1060. totalDiskSize += contentfiles [ i ].size;
  1061. }
  1062. vprint( 0, "Content tree size on disk %s\n", Q_pretifymem( totalDiskSize, 3 ) );
  1063. // Analysis is to walk tree and see which files on disk are referenced in the .lst files
  1064. // Need a fast lookup from file symbol to referenced list
  1065. CUtlRBTree< ReferencedFile, int > tree( 0, 0, ReferencedFileLessFunc );
  1066. c = referencedfiles.Count();
  1067. for ( i = 0 ; i < c; ++i )
  1068. {
  1069. tree.Insert( referencedfiles[ i ] );
  1070. }
  1071. // Now walk the on disk file and see check off resources which are in referenced
  1072. c = contentfiles.Count();
  1073. int invalidindex = tree.InvalidIndex();
  1074. unsigned int refcounted = 0;
  1075. unsigned int whitelisted = 0;
  1076. filesystem->RemoveFile( CFmtStr( "%swhitelist.lst", g_szReslistDir ), "GAME" );
  1077. for ( i = 0; i < c; ++i )
  1078. {
  1079. FileEntry & entry = contentfiles[ i ];
  1080. ReferencedFile foo;
  1081. foo.sym = entry.sym;
  1082. bool gameref = tree.Find( foo ) != invalidindex;
  1083. char const *fn = g_Analysis.symbols.String( entry.sym );
  1084. bool whitelist = g_WhiteList.Find( entry.sym ) != g_WhiteList.InvalidIndex();
  1085. if ( gameref || whitelist )
  1086. {
  1087. entry.referenced = gameref ? REFERENCED_GAME : REFERENCED_WHITELIST;
  1088. totalReferencedDiskSize += entry.size;
  1089. if ( entry.referenced == REFERENCED_WHITELIST )
  1090. {
  1091. logprint( CFmtStr( "%swhitelist.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, fn );
  1092. totalWhiteListDiskSize += entry.size;
  1093. ++whitelisted;
  1094. }
  1095. ++refcounted;
  1096. }
  1097. }
  1098. vprint( 0, "Found %i referenced (%i whitelist) files in tree, %s\n", refcounted, whitelisted, Q_pretifymem( totalReferencedDiskSize, 2 ) );
  1099. vprint( 0, "%s appear unused\n", Q_pretifymem( totalDiskSize - totalReferencedDiskSize, 2 ) );
  1100. // Now sort and dump the unreferenced ones..
  1101. vprint( 0, "Sorting unreferenced files list...\n" );
  1102. CUtlRBTree< FileEntry, int > unreftree( 0, 0, FileEntryLessFunc );
  1103. for ( i = 0; i < c; ++i )
  1104. {
  1105. FileEntry & entry = contentfiles[ i ];
  1106. if ( entry.referenced != REFERENCED_NO )
  1107. continue;
  1108. unreftree.Insert( entry );
  1109. }
  1110. // Now walk the unref tree in order
  1111. i = unreftree.FirstInorder();
  1112. invalidindex = unreftree.InvalidIndex();
  1113. int index = 0;
  1114. while ( i != invalidindex )
  1115. {
  1116. FileEntry & entry = unreftree[ i ];
  1117. if ( showreferencedfiles )
  1118. {
  1119. vprint( 1, "%6i %12s: %s\n", ++index, Q_pretifymem( entry.size, 2 ), g_Analysis.symbols.String( entry.sym ) );
  1120. }
  1121. i = unreftree.NextInorder( i );
  1122. }
  1123. if ( showmapfileusage )
  1124. {
  1125. vprint( 0, "Writing referenced.csv...\n" );
  1126. // Now walk the list of referenced files and print out how many and which maps reference them
  1127. i = tree.FirstInorder();
  1128. invalidindex = tree.InvalidIndex();
  1129. index = 0;
  1130. while ( i != invalidindex )
  1131. {
  1132. ReferencedFile & entry = tree[ i ];
  1133. char ext[ 32 ];
  1134. Q_ExtractFileExtension( g_Analysis.symbols.String( entry.sym ), ext, sizeof( ext ) );
  1135. logprint( "referenced.csv", "\"%s\",\"%s\",%d", g_Analysis.symbols.String( entry.sym ), ext, entry.maplist.Count() );
  1136. int mapcount = entry.maplist.Count();
  1137. for ( int j = 0 ; j < mapcount; ++j )
  1138. {
  1139. char basemap[ 128 ];
  1140. Q_FileBase( g_Analysis.symbols.String( entry.maplist[ j ] ), basemap, sizeof( basemap ) );
  1141. logprint( "referenced.csv", ",\"%s\"", basemap );
  1142. }
  1143. logprint( "referenced.csv", "\n" );
  1144. i = tree.NextInorder( i );
  1145. }
  1146. }
  1147. vprint( 0, "\nBuilding directory summary list...\n" );
  1148. // Now build summaries by root branch off of gamedir (e.g., for sound, materials, models, etc.)
  1149. CUtlDict< DirEntry, int > directories;
  1150. invalidindex = directories.InvalidIndex();
  1151. for ( i = 0; i < c; ++i )
  1152. {
  1153. FileEntry & entry = contentfiles[ i ];
  1154. // Get the dir name
  1155. char const *dirname = g_Analysis.symbols.String( entry.sym );
  1156. const char *backslash = strstr( dirname, "\\" );
  1157. char dir[ 256 ];
  1158. if ( !backslash )
  1159. {
  1160. dir[0] = 0;
  1161. }
  1162. else
  1163. {
  1164. Q_strncpy( dir, dirname, backslash - dirname + 1);
  1165. }
  1166. int idx = directories.Find( dir );
  1167. if ( idx == invalidindex )
  1168. {
  1169. DirEntry foo;
  1170. idx = directories.Insert( dir, foo );
  1171. }
  1172. DirEntry & de = directories[ idx ];
  1173. de.total += entry.size;
  1174. if ( entry.referenced == REFERENCED_NO )
  1175. {
  1176. de.unreferenced += entry.size;
  1177. }
  1178. if ( entry.referenced == REFERENCED_WHITELIST )
  1179. {
  1180. de.whitelist += entry.size;
  1181. }
  1182. }
  1183. if ( spewdeletions )
  1184. {
  1185. // Spew deletion commands to console
  1186. if ( immediatedelete )
  1187. {
  1188. vprint( 0, "\n\nDeleting files...\n" );
  1189. }
  1190. else
  1191. {
  1192. vprint( 0, "\n\nGenerating deletions.bat\n" );
  1193. }
  1194. i = unreftree.FirstInorder();
  1195. invalidindex = unreftree.InvalidIndex();
  1196. float deletionSize = 0.0f;
  1197. int deletionCount = 0;
  1198. while ( i != invalidindex )
  1199. {
  1200. FileEntry & entry = unreftree[ i ];
  1201. i = unreftree.NextInorder( i );
  1202. // Don't delete stuff that's in the white list
  1203. if ( g_WhiteList.Find( entry.sym ) != g_WhiteList.InvalidIndex() )
  1204. {
  1205. if ( verbose )
  1206. {
  1207. vprint( 0, "whitelist blocked deletion of %s\n", g_Analysis.symbols.String( entry.sym ) );
  1208. }
  1209. continue;
  1210. }
  1211. ++deletionCount;
  1212. deletionSize += entry.size;
  1213. if ( immediatedelete )
  1214. {
  1215. if ( _chmod( g_Analysis.symbols.String( entry.sym ), _S_IWRITE ) == -1 )
  1216. {
  1217. vprint( 0, "Could not find file %s\n", g_Analysis.symbols.String( entry.sym ) );
  1218. }
  1219. if ( _unlink( g_Analysis.symbols.String( entry.sym ) ) == -1 )
  1220. {
  1221. vprint( 0, "Could not delete file %s\n", g_Analysis.symbols.String( entry.sym ) );
  1222. }
  1223. if ( deletionCount % 1000 == 0 )
  1224. {
  1225. vprint( 0, "...deleted %i files\n", deletionCount );
  1226. }
  1227. }
  1228. else
  1229. {
  1230. logprint( "deletions.bat", "del \"%s\" /f\n", g_Analysis.symbols.String( entry.sym ) );
  1231. }
  1232. }
  1233. vprint( 0, "\nFile deletion (%d files, %s)\n\n", deletionCount, Q_pretifymem(deletionSize, 2) );
  1234. }
  1235. double grand_total = 0;
  1236. double grand_total_unref = 0;
  1237. double grand_total_white = 0;
  1238. char totalstring[ 20 ];
  1239. char unrefstring[ 20 ];
  1240. char refstring[ 20 ];
  1241. char whiteliststring[ 20 ];
  1242. vprint( 0, "---------------------------------------- Summary ----------------------------------------\n" );
  1243. vprint( 0, "% 15s % 15s % 15s % 15s %12s\n",
  1244. "Referenced",
  1245. "WhiteListed",
  1246. "Unreferenced",
  1247. "Total",
  1248. "Directory" );
  1249. // Now walk the dictionary in order
  1250. i = directories.First();
  1251. while ( i != invalidindex )
  1252. {
  1253. DirEntry & de = directories[ i ];
  1254. double remainder = de.total - de.unreferenced;
  1255. float percent_unref = 0.0f;
  1256. float percent_white = 0.0f;
  1257. if ( de.total > 0 )
  1258. {
  1259. percent_unref = 100.0f * (float)de.unreferenced / (float)de.total;
  1260. percent_white = 100.0f * (float)de.whitelist / (float)de.total;
  1261. }
  1262. Q_strncpy( totalstring, Q_pretifymem( de.total, 2 ), sizeof( totalstring ) );
  1263. Q_strncpy( unrefstring, Q_pretifymem( de.unreferenced, 2 ), sizeof( unrefstring ) );
  1264. Q_strncpy( refstring, Q_pretifymem( remainder, 2 ), sizeof( refstring ) );
  1265. Q_strncpy( whiteliststring, Q_pretifymem( de.whitelist, 2 ), sizeof( whiteliststring ) );
  1266. vprint( 0, "%15s (%8.3f%%) %15s (%8.3f%%) %15s (%8.3f%%) %15s => dir: %s\n",
  1267. refstring, 100.0f - percent_unref, whiteliststring, percent_white, unrefstring, percent_unref, totalstring, directories.GetElementName( i ) );
  1268. grand_total += de.total;
  1269. grand_total_unref += de.unreferenced;
  1270. grand_total_white += de.whitelist;
  1271. i = directories.Next( i );
  1272. }
  1273. Q_strncpy( totalstring, Q_pretifymem( grand_total, 2 ), sizeof( totalstring ) );
  1274. Q_strncpy( unrefstring, Q_pretifymem( grand_total_unref, 2 ), sizeof( unrefstring ) );
  1275. Q_strncpy( refstring, Q_pretifymem( grand_total - grand_total_unref, 2 ), sizeof( refstring ) );
  1276. Q_strncpy( whiteliststring, Q_pretifymem( grand_total_white, 2 ), sizeof( whiteliststring ) );
  1277. double percent_unref = 100.0 * grand_total_unref / grand_total;
  1278. double percent_white = 100.0 * grand_total_white / grand_total;
  1279. vprint( 0, "-----------------------------------------------------------------------------------------\n" );
  1280. vprint( 0, "%15s (%8.3f%%) %15s (%8.3f%%) %15s (%8.3f%%) %15s\n",
  1281. refstring, 100.0f - percent_unref, whiteliststring, percent_white, unrefstring, percent_unref, totalstring );
  1282. }
  1283. //-----------------------------------------------------------------------------
  1284. // Purpose:
  1285. // Input : argc -
  1286. // argv[] -
  1287. // Output : int
  1288. //-----------------------------------------------------------------------------
  1289. int main( int argc, char* argv[] )
  1290. {
  1291. SpewOutputFunc( SpewFunc );
  1292. SpewActivate( "unusedcontent", 2 );
  1293. CommandLine()->CreateCmdLine( argc, argv );
  1294. int i=1;
  1295. for ( i ; i<argc ; i++)
  1296. {
  1297. if ( argv[ i ][ 0 ] == '-' )
  1298. {
  1299. switch( argv[ i ][ 1 ] )
  1300. {
  1301. case 'l':
  1302. uselogfile = true;
  1303. break;
  1304. case 'v':
  1305. verbose = true;
  1306. break;
  1307. case 'r':
  1308. showreferencedfiles = true;
  1309. break;
  1310. case 'd':
  1311. spewdeletions = true;
  1312. break;
  1313. case 'i':
  1314. immediatedelete = true;
  1315. break;
  1316. case 'w':
  1317. printwhitelist = true;
  1318. break;
  1319. case 'm':
  1320. showmapfileusage = true;
  1321. break;
  1322. case 'g':
  1323. // Just skip -game
  1324. Assert( !Q_stricmp( argv[ i ], "-game" ) );
  1325. ++i;
  1326. break;
  1327. case 'f':
  1328. // grab reslists folder
  1329. {
  1330. ++i;
  1331. Q_strncpy( g_szReslistDir, argv[ i ], sizeof( g_szReslistDir ) );
  1332. Q_strlower( g_szReslistDir );
  1333. Q_FixSlashes( g_szReslistDir );
  1334. Q_AppendSlash( g_szReslistDir, sizeof( g_szReslistDir ) );
  1335. }
  1336. break;
  1337. default:
  1338. printusage();
  1339. break;
  1340. }
  1341. }
  1342. }
  1343. if ( argc < 3 || ( i != argc ) )
  1344. {
  1345. PrintHeader();
  1346. printusage();
  1347. return 0;
  1348. }
  1349. CheckLogFile();
  1350. PrintHeader();
  1351. vprint( 0, " Using reslist dir '%s'\n", g_szReslistDir );
  1352. vprint( 0, " Looking for extraneous content...\n" );
  1353. char resfile[ 256 ];
  1354. strcpy( resfile, argv[ i - 1 ] );
  1355. vprint( 0, " Comparing results of resfile (%s) with files under current directory...\n", resfile );
  1356. char workingdir[ 256 ];
  1357. workingdir[0] = 0;
  1358. Q_getwd( workingdir, sizeof( workingdir ) );
  1359. // If they didn't specify -game on the command line, use VPROJECT.
  1360. CmdLib_InitFileSystem( workingdir );
  1361. filesystem = (IFileSystem *)(CmdLib_GetFileSystemFactory()( FILESYSTEM_INTERFACE_VERSION, NULL ));
  1362. if ( !filesystem )
  1363. {
  1364. AssertMsg( 0, "Failed to create/get IFileSystem" );
  1365. return 1;
  1366. }
  1367. g_pFullFileSystem->RemoveAllSearchPaths();
  1368. g_pFullFileSystem->AddSearchPath(gamedir, "GAME");
  1369. Q_strlower( gamedir );
  1370. Q_FixSlashes( gamedir );
  1371. //
  1372. //ProcessMaterialsDirectory( vmtdir );
  1373. // find out the mod dir name
  1374. Q_strncpy( modname, gamedir, sizeof(modname) );
  1375. modname[ strlen(modname) - 1] = 0;
  1376. if ( strrchr( modname, '\\' ) )
  1377. {
  1378. Q_strncpy( modname, strrchr( modname, '\\' ) + 1, sizeof(modname) );
  1379. }
  1380. else
  1381. {
  1382. Q_strncpy( modname, "", sizeof(modname) );
  1383. }
  1384. vprint( 1, "Mod Name:%s\n", modname);
  1385. BuildCheckdirList();
  1386. BuildWhiteList();
  1387. vprint( 0, "Building aggregate file list from resfile output\n" );
  1388. CUtlRBTree< ReferencedFile, int > referencedfiles( 0, 0, RefFileLessFunc );
  1389. CUtlVector< UnusedContent::CUtlSymbol > resfiles;
  1390. BuildReferencedFileList( resfiles, referencedfiles, resfile );
  1391. vprint( 0, "found %i files\n\n", referencedfiles.Count() );
  1392. vprint( 0, "Building list of all game content files\n" );
  1393. CUtlVector< FileEntry > contentfiles;
  1394. CUtlVector< FileEntry > otherfiles;
  1395. BuildFileList( 0, contentfiles, &otherfiles, "", 0 );
  1396. vprint( 0, "found %i files in content tree\n\n", contentfiles.Count() );
  1397. Correlate( referencedfiles, contentfiles, modname );
  1398. // now output the files not referenced in the whitelist or general reslists
  1399. filesystem->RemoveFile( CFmtStr( "%sunreferenced_files.lst", g_szReslistDir ), "GAME" );
  1400. int c = otherfiles.Count();
  1401. for ( i = 0; i < c; ++i )
  1402. {
  1403. FileEntry & entry = otherfiles[ i ];
  1404. char const *name = g_Analysis.symbols.String( entry.sym );
  1405. logprint( CFmtStr( "%sunreferenced_files.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, name );
  1406. }
  1407. // also include the files from deletions.bat, as we don't actually run that now
  1408. c = contentfiles.Count();
  1409. for ( i = 0; i < c; ++i )
  1410. {
  1411. FileEntry & entry = contentfiles[ i ];
  1412. if ( entry.referenced != REFERENCED_NO )
  1413. continue;
  1414. char const *fn = g_Analysis.symbols.String( entry.sym );
  1415. logprint( CFmtStr( "%sunreferenced_files.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, fn );
  1416. }
  1417. FileSystem_Term();
  1418. return 0;
  1419. }