diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/unusedcontent/unusedcontent.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/unusedcontent/unusedcontent.cpp')
| -rw-r--r-- | utils/unusedcontent/unusedcontent.cpp | 1767 |
1 files changed, 1767 insertions, 0 deletions
diff --git a/utils/unusedcontent/unusedcontent.cpp b/utils/unusedcontent/unusedcontent.cpp new file mode 100644 index 0000000..98725b4 --- /dev/null +++ b/utils/unusedcontent/unusedcontent.cpp @@ -0,0 +1,1767 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: unusedcontent.cpp : Defines the entry point for the console application. +// +//=============================================================================// +#include "cbase.h" +#include <stdio.h> +#include <windows.h> +#include <io.h> +#include <sys/stat.h> +#include "tier0/dbg.h" +#pragma warning( disable : 4018 ) + +#include "utlrbtree.h" +#include "utlvector.h" +#include "utldict.h" +#include "filesystem.h" +#include "FileSystem_Tools.h" +#include "FileSystem_Helpers.h" +#include "KeyValues.h" +#include "cmdlib.h" +#include "scriplib.h" +#include "tier0/icommandline.h" +#include "tier1/fmtstr.h" + +bool uselogfile = false; +bool spewdeletions = false; +bool showreferencedfiles = false; +bool immediatedelete = false; +bool printwhitelist = false; +bool showmapfileusage = false; + +static char modname[MAX_PATH]; +static char g_szReslistDir[ MAX_PATH ] = "reslists/"; + +namespace UnusedContent +{ + +class CCleanupUtlSymbolTable; + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- + +class CUtlSymbolTable; + + +//----------------------------------------------------------------------------- +// This is a symbol, which is a easier way of dealing with strings. +//----------------------------------------------------------------------------- + +typedef unsigned int UtlSymId_t; + +#define UC_UTL_INVAL_SYMBOL ((UnusedContent::UtlSymId_t)~0) + +class CUtlSymbol +{ +public: + // constructor, destructor + CUtlSymbol() : m_Id(UTL_INVAL_SYMBOL) {} + CUtlSymbol( UtlSymId_t id ) : m_Id(id) {} + CUtlSymbol( char const* pStr ); + CUtlSymbol( CUtlSymbol const& sym ) : m_Id(sym.m_Id) {} + + // operator= + CUtlSymbol& operator=( CUtlSymbol const& src ) { m_Id = src.m_Id; return *this; } + + // operator== + bool operator==( CUtlSymbol const& src ) const { return m_Id == src.m_Id; } + bool operator==( char const* pStr ) const; + + // Is valid? + bool IsValid() const { return m_Id != UTL_INVAL_SYMBOL; } + + // Gets at the symbol + operator UtlSymId_t const() const { return m_Id; } + + // Gets the string associated with the symbol + char const* String( ) const; + + // Modules can choose to disable the static symbol table so to prevent accidental use of them. + static void DisableStaticSymbolTable(); + +protected: + UtlSymId_t m_Id; + + // Initializes the symbol table + static void Initialize(); + + // returns the current symbol table + static CUtlSymbolTable* CurrTable(); + + // The standard global symbol table + static CUtlSymbolTable* s_pSymbolTable; + + static bool s_bAllowStaticSymbolTable; + + friend class UnusedContent::CCleanupUtlSymbolTable; +}; + + +//----------------------------------------------------------------------------- +// CUtlSymbolTable: +// description: +// This class defines a symbol table, which allows us to perform mappings +// of strings to symbols and back. The symbol class itself contains +// a static version of this class for creating global strings, but this +// class can also be instanced to create local symbol tables. +//----------------------------------------------------------------------------- + +class CUtlSymbolTable +{ +public: + // constructor, destructor + CUtlSymbolTable( int growSize = 0, int initSize = 32, bool caseInsensitive = false ); + ~CUtlSymbolTable(); + + // Finds and/or creates a symbol based on the string + CUtlSymbol AddString( char const* pString ); + + // Finds the symbol for pString + CUtlSymbol Find( char const* pString ); + + // Look up the string associated with a particular symbol + char const* String( CUtlSymbol id ) const; + + // Remove all symbols in the table. + void RemoveAll(); + + int GetNumStrings( void ) const + { + return m_Lookup.Count(); + } + +protected: + class CStringPoolIndex + { + public: + inline CStringPoolIndex() + { + } + + inline CStringPoolIndex( unsigned int iPool, unsigned int iOffset ) + { + m_iPool = iPool; + m_iOffset = iOffset; + } + + inline bool operator==( const CStringPoolIndex &other ) const + { + return m_iPool == other.m_iPool && m_iOffset == other.m_iOffset; + } + + unsigned int m_iPool; // Index into m_StringPools. + unsigned int m_iOffset; // Index into the string pool. + }; + + // Stores the symbol lookup + CUtlRBTree<CStringPoolIndex, unsigned int> m_Lookup; + + typedef struct + { + int m_TotalLen; // How large is + int m_SpaceUsed; + char m_Data[1]; + } StringPool_t; + + // stores the string data + CUtlVector<StringPool_t*> m_StringPools; + + + +private: + + int FindPoolWithSpace( int len ) const; + + const char* StringFromIndex( const CStringPoolIndex &index ) const; + + // Less function, for sorting strings + static bool SymLess( CStringPoolIndex const& i1, CStringPoolIndex const& i2 ); + // case insensitive less function + static bool SymLessi( CStringPoolIndex const& i1, CStringPoolIndex const& i2 ); +}; + +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Defines a symbol table +// +// $Header: $ +// $NoKeywords: $ +//============================================================================= + +#pragma warning (disable:4514) + +#define INVALID_STRING_INDEX CStringPoolIndex( 0xFFFF, 0xFFFF ) + +#define MIN_STRING_POOL_SIZE 2048 + +//----------------------------------------------------------------------------- +// globals +//----------------------------------------------------------------------------- + +CUtlSymbolTable* CUtlSymbol::s_pSymbolTable = 0; +bool CUtlSymbol::s_bAllowStaticSymbolTable = true; + + +//----------------------------------------------------------------------------- +// symbol methods +//----------------------------------------------------------------------------- + +void CUtlSymbol::Initialize() +{ + // If this assert fails, then the module that this call is in has chosen to disallow + // use of the static symbol table. Usually, it's to prevent confusion because it's easy + // to accidentally use the global symbol table when you really want to use a specific one. + Assert( s_bAllowStaticSymbolTable ); + + // necessary to allow us to create global symbols + static bool symbolsInitialized = false; + if (!symbolsInitialized) + { + s_pSymbolTable = new CUtlSymbolTable; + symbolsInitialized = true; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Singleton to delete table on exit from module +//----------------------------------------------------------------------------- +class CCleanupUtlSymbolTable +{ +public: + ~CCleanupUtlSymbolTable() + { + delete CUtlSymbol::s_pSymbolTable; + CUtlSymbol::s_pSymbolTable = NULL; + } +}; + +static CCleanupUtlSymbolTable g_CleanupSymbolTable; + +CUtlSymbolTable* CUtlSymbol::CurrTable() +{ + Initialize(); + return s_pSymbolTable; +} + + +//----------------------------------------------------------------------------- +// string->symbol->string +//----------------------------------------------------------------------------- + +CUtlSymbol::CUtlSymbol( char const* pStr ) +{ + m_Id = CurrTable()->AddString( pStr ); +} + +char const* CUtlSymbol::String( ) const +{ + return CurrTable()->String(m_Id); +} + +void CUtlSymbol::DisableStaticSymbolTable() +{ + s_bAllowStaticSymbolTable = false; +} + +//----------------------------------------------------------------------------- +// checks if the symbol matches a string +//----------------------------------------------------------------------------- + +bool CUtlSymbol::operator==( char const* pStr ) const +{ + if (m_Id == UTL_INVAL_SYMBOL) + return false; + return strcmp( String(), pStr ) == 0; +} + + + +//----------------------------------------------------------------------------- +// symbol table stuff +//----------------------------------------------------------------------------- + +struct LessCtx_t +{ + char const* m_pUserString; + CUtlSymbolTable* m_pTable; + + LessCtx_t( ) : m_pUserString(0), m_pTable(0) {} +}; + +static LessCtx_t g_LessCtx; + + +inline const char* CUtlSymbolTable::StringFromIndex( const CStringPoolIndex &index ) const +{ + Assert( index.m_iPool < m_StringPools.Count() ); + Assert( index.m_iOffset < m_StringPools[index.m_iPool]->m_TotalLen ); + + return &m_StringPools[index.m_iPool]->m_Data[index.m_iOffset]; +} + + +bool CUtlSymbolTable::SymLess( CStringPoolIndex const& i1, CStringPoolIndex const& i2 ) +{ + char const* str1 = (i1 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString : + g_LessCtx.m_pTable->StringFromIndex( i1 ); + char const* str2 = (i2 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString : + g_LessCtx.m_pTable->StringFromIndex( i2 ); + + return strcmp( str1, str2 ) < 0; +} + + +bool CUtlSymbolTable::SymLessi( CStringPoolIndex const& i1, CStringPoolIndex const& i2 ) +{ + char const* str1 = (i1 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString : + g_LessCtx.m_pTable->StringFromIndex( i1 ); + char const* str2 = (i2 == INVALID_STRING_INDEX) ? g_LessCtx.m_pUserString : + g_LessCtx.m_pTable->StringFromIndex( i2 ); + + return strcmpi( str1, str2 ) < 0; +} + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +CUtlSymbolTable::CUtlSymbolTable( int growSize, int initSize, bool caseInsensitive ) : + m_Lookup( growSize, initSize, caseInsensitive ? SymLessi : SymLess ), m_StringPools( 8 ) +{ +} + +CUtlSymbolTable::~CUtlSymbolTable() +{ +} + + +CUtlSymbol CUtlSymbolTable::Find( char const* pString ) +{ + if (!pString) + return CUtlSymbol(); + + // Store a special context used to help with insertion + g_LessCtx.m_pUserString = pString; + g_LessCtx.m_pTable = this; + + // Passing this special invalid symbol makes the comparison function + // use the string passed in the context + UtlSymId_t idx = m_Lookup.Find( INVALID_STRING_INDEX ); + return CUtlSymbol( idx ); +} + + +int CUtlSymbolTable::FindPoolWithSpace( int len ) const +{ + for ( int i=0; i < m_StringPools.Count(); i++ ) + { + StringPool_t *pPool = m_StringPools[i]; + + if ( (pPool->m_TotalLen - pPool->m_SpaceUsed) >= len ) + { + return i; + } + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Finds and/or creates a symbol based on the string +//----------------------------------------------------------------------------- + +CUtlSymbol CUtlSymbolTable::AddString( char const* pString ) +{ + if (!pString) + return CUtlSymbol( UTL_INVAL_SYMBOL ); + + CUtlSymbol id = Find( pString ); + + if (id.IsValid()) + return id; + + int len = strlen(pString) + 1; + + // Find a pool with space for this string, or allocate a new one. + int iPool = FindPoolWithSpace( len ); + if ( iPool == -1 ) + { + // Add a new pool. + int newPoolSize = max( len, MIN_STRING_POOL_SIZE ); + StringPool_t *pPool = (StringPool_t*)malloc( sizeof( StringPool_t ) + newPoolSize - 1 ); + pPool->m_TotalLen = newPoolSize; + pPool->m_SpaceUsed = 0; + iPool = m_StringPools.AddToTail( pPool ); + } + + // Copy the string in. + StringPool_t *pPool = m_StringPools[iPool]; + Assert( pPool->m_SpaceUsed < 0xFFFF ); // This should never happen, because if we had a string > 64k, it + // would have been given its entire own pool. + + unsigned int iStringOffset = pPool->m_SpaceUsed; + + memcpy( &pPool->m_Data[pPool->m_SpaceUsed], pString, len ); + pPool->m_SpaceUsed += len; + + // didn't find, insert the string into the vector. + CStringPoolIndex index; + index.m_iPool = iPool; + index.m_iOffset = iStringOffset; + + UtlSymId_t idx = m_Lookup.Insert( index ); + return CUtlSymbol( idx ); +} + + +//----------------------------------------------------------------------------- +// Look up the string associated with a particular symbol +//----------------------------------------------------------------------------- + +char const* CUtlSymbolTable::String( CUtlSymbol id ) const +{ + if (!id.IsValid()) + return ""; + + Assert( m_Lookup.IsValidIndex((UtlSymId_t)id) ); + return StringFromIndex( m_Lookup[id] ); +} + + +//----------------------------------------------------------------------------- +// Remove all symbols in the table. +//----------------------------------------------------------------------------- + +void CUtlSymbolTable::RemoveAll() +{ + m_Lookup.RemoveAll(); + + for ( int i=0; i < m_StringPools.Count(); i++ ) + free( m_StringPools[i] ); + + m_StringPools.RemoveAll(); +} + +} // Namespace UnusedContent + +struct AnalysisData +{ + UnusedContent::CUtlSymbolTable symbols; +}; + +char *directories_to_check[] = +{ + "", + "bin", + "maps", + "materials", + "models", + "scenes", + "scripts", + "sound", + "hl2", +}; + +char *directories_to_ignore[] = // don't include these dirs in the others list +{ + "reslists", + "reslists_temp", + "logs", + "media", + "downloads", + "save", + "screenshots", + "testscripts", + "logos" +}; + + +enum +{ + REFERENCED_NO = 0, + REFERENCED_WHITELIST, + REFERENCED_GAME +}; + +struct FileEntry +{ + FileEntry() + { + sym = UC_UTL_INVAL_SYMBOL; + size = 0; + referenced = REFERENCED_NO; + } + + UnusedContent::CUtlSymbol sym; + unsigned int size; + int referenced; +}; + +struct ReferencedFile +{ + ReferencedFile() + { + sym = UC_UTL_INVAL_SYMBOL; + } + + ReferencedFile( const ReferencedFile& src ) + { + sym = src.sym; + maplist.RemoveAll(); + int c = src.maplist.Count(); + for ( int i = 0; i < c; ++i ) + { + maplist.AddToTail( src.maplist[ i ] ); + } + } + + ReferencedFile & operator =( const ReferencedFile& src ) + { + if ( this == &src ) + return *this; + + sym = src.sym; + maplist.RemoveAll(); + int c = src.maplist.Count(); + for ( int i = 0; i < c; ++i ) + { + maplist.AddToTail( src.maplist[ i ] ); + } + return *this; + } + + UnusedContent::CUtlSymbol sym; + CUtlVector< UnusedContent::CUtlSymbol > maplist; +}; + + +static AnalysisData g_Analysis; + +IFileSystem *filesystem = NULL; +static CUniformRandomStream g_Random; +IUniformRandomStream *random = &g_Random; + +static bool spewed = false; + +SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg ) +{ + spewed = true; + + printf( "%s", pMsg ); + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + exit(-1); + } + + return SPEW_CONTINUE; +} + +char *va( const char *fmt, ... ) +{ + static char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + return string; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : depth - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void vprint( int depth, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + if ( uselogfile ) + { + fp = fopen( "log.txt", "ab" ); + } + + while ( depth-- > 0 ) + { + printf( " " ); + OutputDebugString( " " ); + if ( fp ) + { + fprintf( fp, " " ); + } + } + + ::printf( "%s", string ); + OutputDebugString( string ); + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + +void logprint( char const *logfile, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.Find( logfile ); + static CUtlRBTree< UnusedContent::CUtlSymbol, int > previousfiles( 0, 0, DefLessFunc( UnusedContent::CUtlSymbol ) ); + if ( previousfiles.Find( sym ) == previousfiles.InvalidIndex() ) + { + previousfiles.Insert( sym ); + fp = fopen( logfile, "wb" ); + } + else + { + fp = fopen( logfile, "ab" ); + } + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + +void Con_Printf( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + vprint( 0, output ); +} + +bool ShouldCheckDir( char const *dirname ); +bool ShouldIgnoreDir( const char *dirname ); + +void BuildFileList_R( int depth, CUtlVector< FileEntry >& files, CUtlVector< FileEntry > * otherfiles, char const *dir, char const *wild, int skipchars ) +{ + WIN32_FIND_DATA wfd; + + char directory[ 256 ]; + char filename[ 256 ]; + HANDLE ff; + + bool canrecurse = true; + if ( !Q_stricmp( wild, "..." ) ) + { + canrecurse = true; + sprintf( directory, "%s%s%s", dir[0] == '\\' ? dir + 1 : dir, dir[0] != 0 ? "\\" : "", "*.*" ); + } + else + { + sprintf( directory, "%s%s%s", dir, dir[0] != 0 ? "\\" : "", wild ); + } + int dirlen = Q_strlen( dir ); + + if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE ) + return; + + do + { + if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + bool useOtherFiles = false; + + if ( wfd.cFileName[ 0 ] == '.' ) + continue; + + if ( depth == 0 && !ShouldCheckDir( wfd.cFileName ) && otherfiles ) + { + if ( !ShouldIgnoreDir( wfd.cFileName ) ) + { + useOtherFiles = true; + } + } + + if ( !canrecurse ) + continue; + + // Recurse down directory + if ( dir[0] ) + { + sprintf( filename, "%s\\%s", dir, wfd.cFileName ); + } + else + { + sprintf( filename, "%s", wfd.cFileName ); + } + BuildFileList_R( depth + 1, useOtherFiles ? *otherfiles: files, NULL, filename, wild, skipchars ); + } + else + { + if (!stricmp(wfd.cFileName, "vssver.scc")) + continue; + + char filename[ MAX_PATH ]; + if ( dirlen <= skipchars ) + { + Q_snprintf( filename, sizeof( filename ), "%s", wfd.cFileName ); + } + else + { + Q_snprintf( filename, sizeof( filename ), "%s\\%s", &dir[ skipchars ], wfd.cFileName ); + } + _strlwr( filename ); + + Q_FixSlashes( filename ); + + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( filename ); + + FileEntry entry; + entry.sym = sym; + int size = g_pFileSystem->Size( filename ); + entry.size = size >= 0 ? (unsigned int)size : 0; + + files.AddToTail( entry ); + + if ( !( files.Count() % 3000 ) ) + { + vprint( 0, "...found %i files\n", files.Count() ); + } + } + } while ( FindNextFile( ff, &wfd ) ); +} + +void BuildFileList( int depth, CUtlVector< FileEntry >& files, CUtlVector< FileEntry > * otherfiles, char const *rootdir, int skipchars ) +{ + files.RemoveAll(); + Assert( otherfiles ); + otherfiles->RemoveAll(); + BuildFileList_R( depth, files, otherfiles, rootdir, "...", skipchars ); +} + +void BuildFileListWildcard( int depth, CUtlVector< FileEntry >& files, char const *rootdir, char const *wildcard, int skipchars ) +{ + files.RemoveAll(); + BuildFileList_R( depth, files, NULL, rootdir, wildcard, skipchars ); +} + + +static CUtlVector< UnusedContent::CUtlSymbol > g_DirList; +static CUtlVector< UnusedContent::CUtlSymbol > g_IgnoreDir; + +bool ShouldCheckDir( char const *dirname ) +{ + int c = g_DirList.Count(); + for ( int i = 0; i < c; ++i ) + { + char const *check = g_Analysis.symbols.String( g_DirList[ i ] ); + + if ( !Q_stricmp( dirname, check ) ) + return true; + } + + vprint( 1, "Skipping dir %s\n", dirname ); + return false; +} + +bool ShouldIgnoreDir( const char *dirname ) +{ + int c = g_IgnoreDir.Count(); + for ( int i = 0; i < c; ++i ) + { + char const *check = g_Analysis.symbols.String( g_IgnoreDir[ i ] ); + + if ( Q_stristr( dirname, "reslists" ) ) + { + vprint( 1, "Ignoring dir %s\n", dirname ); + return true; + } + + if ( !Q_stricmp( dirname, check ) ) + { + vprint( 1, "Ignoring dir %s\n", dirname ); + return true; + } + } + + return false; +} + +void AddCheckdir( char const *dirname ) +{ + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( dirname ); + g_DirList.AddToTail( sym ); + + vprint( 1, "AddCheckdir[ \"%s\" ]\n", dirname ); +} + +void AddIgnoredir( const char *dirname ) +{ + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( dirname ); + g_IgnoreDir.AddToTail( sym ); + + vprint( 1, "AddIgnoredir[ \"%s\" ]\n", dirname ); +} + + +#define UNUSEDCONTENT_CFG "unusedcontent.cfg" + +void BuildCheckdirList() +{ + vprint( 0, "Checking for dirlist\n" ); + // Search for unusedcontent.cfg file + if ( g_pFileSystem->FileExists( UNUSEDCONTENT_CFG, "GAME") ) + { + KeyValues *kv = new KeyValues( UNUSEDCONTENT_CFG ); + if ( kv ) + { + if ( kv->LoadFromFile( g_pFileSystem, UNUSEDCONTENT_CFG, "GAME" ) ) + { + for ( KeyValues *sub = kv->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "dir" ) ) + { + AddCheckdir( sub->GetString() ); + } + else if ( !Q_stricmp( sub->GetName(), "ignore" ) ) + { + AddIgnoredir( sub->GetString() ); + } + else + { + vprint( 1, "Unknown subkey '%s' in %s\n", sub->GetName(), UNUSEDCONTENT_CFG ); + } + } + } + + kv->deleteThis(); + } + } + else + { + int c = ARRAYSIZE( directories_to_check ); + int i; + for ( i = 0; i < c; ++i ) + { + AddCheckdir( directories_to_check[ i ] ); + } + + // add the list of dirs to ignore from the others lists + c = ARRAYSIZE( directories_to_ignore ); + for ( i = 0; i < c; ++i ) + { + AddIgnoredir( directories_to_ignore[ i ] ); + } + } +} + +static CUtlRBTree< UnusedContent::CUtlSymbol, int > g_WhiteList( 0, 0, DefLessFunc( UnusedContent::CUtlSymbol ) ); + +#define WHITELIST_FILE "whitelist.cfg" + +static int wl_added = 0; +static int wl_removed = 0; + +void AddToWhiteList( char const *path ) +{ + vprint( 2, "+\t'%s'\n", path ); + + char dir[ 512 ]; + Q_strncpy( dir, path, sizeof( dir ) ); + + // Get the base filename from the path + _strlwr( dir ); + Q_FixSlashes( dir ); + + CUtlVector< FileEntry > files; + + char *lastslash = strrchr( dir, '\\' ); + if ( lastslash == 0 ) + { + BuildFileListWildcard( 1, files, "", dir, 0 ); + } + else + { + char *wild = lastslash + 1; + *lastslash = 0; + + BuildFileListWildcard( 1, files, dir, wild, 0 ); + } + + int c = files.Count(); + for ( int i = 0; i < c; ++i ) + { + UnusedContent::CUtlSymbol sym = files[ i ].sym; + if ( g_WhiteList.Find( sym ) == g_WhiteList.InvalidIndex() ) + { + g_WhiteList.Insert( sym ); + ++wl_added; + } + } +} + +void RemoveFromWhiteList( char const *path ) +{ + vprint( 2, "-\t'%s'\n", path ); + + char dir[ 512 ]; + Q_strncpy( dir, path, sizeof( dir ) ); + + // Get the base filename from the path + _strlwr( dir ); + Q_FixSlashes( dir ); + + CUtlVector< FileEntry > files; + + char *lastslash = strrchr( dir, '\\' ); + if ( lastslash == 0 ) + { + BuildFileListWildcard( 1, files, "", dir, 0 ); + } + else + { + char *wild = lastslash + 1; + *lastslash = 0; + + BuildFileListWildcard( 1, files, dir, wild, 0 ); + } + + int c = files.Count(); + for ( int i = 0; i < c; ++i ) + { + UnusedContent::CUtlSymbol sym = files[ i ].sym; + int idx = g_WhiteList.Find( sym ); + if ( idx != g_WhiteList.InvalidIndex() ) + { + g_WhiteList.RemoveAt( idx ); + ++wl_removed; + } + } +} + +void BuildWhiteList() +{ + // Search for unusedcontent.cfg file + if ( !g_pFileSystem->FileExists( WHITELIST_FILE ) ) + { + vprint( 1, "Running with no whitelist.cfg file!!!\n" ); + return; + } + + vprint( 1, "\nBuilding whitelist\n" ); + + KeyValues *kv = new KeyValues( WHITELIST_FILE ); + if ( kv ) + { + if ( kv->LoadFromFile( g_pFileSystem, WHITELIST_FILE, NULL ) ) + { + for ( KeyValues *sub = kv->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "add" ) ) + { + AddToWhiteList( sub->GetString() ); + } + else if ( !Q_stricmp( sub->GetName(), "remove" ) ) + { + RemoveFromWhiteList( sub->GetString() ); + } + else + { + vprint( 1, "Unknown subkey '%s' in %s\n", sub->GetName(), WHITELIST_FILE ); + } + } + } + + kv->deleteThis(); + } + + if ( verbose || printwhitelist ) + { + vprint( 1, "Whitelist:\n\n" ); + + + for ( int i = g_WhiteList.FirstInorder(); + i != g_WhiteList.InvalidIndex(); + i = g_WhiteList.NextInorder( i ) ) + { + UnusedContent::CUtlSymbol& sym = g_WhiteList[ i ]; + + char const *resolved = g_Analysis.symbols.String( sym ); + vprint( 2, " %s\n", resolved ); + } + } + + // dump the whitelist file list anyway + { + filesystem->RemoveFile( "whitelist_files.txt", "GAME" ); + for ( int i = g_WhiteList.FirstInorder(); + i != g_WhiteList.InvalidIndex(); + i = g_WhiteList.NextInorder( i ) ) + { + UnusedContent::CUtlSymbol& sym = g_WhiteList[ i ]; + char const *resolved = g_Analysis.symbols.String( sym ); + logprint( "whitelist_files.txt", "\"%s\"\n", resolved ); + } + } + + + vprint( 1, "Whitelist resolves to %d files (added %i/removed %i)\n\n", g_WhiteList.Count(), wl_added, wl_removed ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void printusage( void ) +{ + vprint( 0, "usage: unusedcontent maplistfile\n\ + \t Note that you must have generated the reslistsfile output via the engine first!!!\n\ + \t-d = spew command prompt deletion instructions to deletions.bat\n\ + \t-v = verbose output\n\ + \t-l = log to file log.txt\n\ + \t-r = print out all referenced files\n\ + \t-m = generate referenced.csv with map counts\n\ + \t-w = print out whitelist\n\ + \t-i = delete unused files immediately\n\ + \t-f <reslistdir> : specify reslists folder, 'reslists' assumed by default\n\ + \t\tmaps/\n\ + \t\tmaterials/\n\ + \t\tmodels/\n\ + \t\tsounds/\n\ + \ne.g.: unusedcontent -r maplist.txt\n" ); + + // Exit app + exit( 1 ); +} + +void ParseFilesFromResList( UnusedContent::CUtlSymbol & resfilesymbol, CUtlRBTree< ReferencedFile, int >& files, char const *resfile ) +{ + int addedStrings = 0; + int resourcesConsidered = 0; + + int offset = Q_strlen( gamedir ); + + char basedir[MAX_PATH]; + Q_strncpy( basedir, gamedir, sizeof( basedir ) ); + if ( !Q_StripLastDir( basedir, sizeof( basedir ) ) ) + Error( "Can't get basedir from %s.", gamedir ); + + FileHandle_t resfilehandle; + resfilehandle = g_pFileSystem->Open( resfile, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) + { + // Read in the entire file + int length = g_pFileSystem->Size(resfilehandle); + if ( length > 0 ) + { + char *pStart = (char *)new char[ length + 1 ]; + if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) ) + { + pStart[ length ] = 0; + + char *pFileList = pStart; + + char token[512]; + + while ( 1 ) + { + pFileList = ParseFile( pFileList, token, NULL ); + if ( !pFileList ) + break; + + if ( strlen( token ) > 0 ) + { + char szFileName[ 256 ]; + Q_snprintf( szFileName, sizeof( szFileName ), "%s%s", basedir, token ); + _strlwr( szFileName ); + Q_FixSlashes( szFileName ); + while ( szFileName[ strlen( szFileName ) - 1 ] == '\n' || + szFileName[ strlen( szFileName ) - 1 ] == '\r' ) + { + szFileName[ strlen( szFileName ) - 1 ] = 0; + } + + if ( Q_strnicmp( szFileName, gamedir, offset ) ) + continue; + + char *pFile = szFileName + offset; + ++resourcesConsidered; + + ReferencedFile rf; + rf.sym = g_Analysis.symbols.AddString( pFile ); + + int idx = files.Find( rf ); + if ( idx == files.InvalidIndex() ) + { + ++addedStrings; + rf.maplist.AddToTail( resfilesymbol ); + files.Insert( rf ); + } + else + { + // + ReferencedFile & slot = files[ idx ]; + if ( slot.maplist.Find( resfilesymbol ) == slot.maplist.InvalidIndex() ) + { + slot.maplist.AddToTail( resfilesymbol ); + } + + } + } + } + + } + delete[] pStart; + } + + g_pFileSystem->Close(resfilehandle); + } + + int filesFound = addedStrings; + + vprint( 1, "Found %i new resources (%i total) in %s\n", filesFound, resourcesConsidered, resfile ); +} + +bool BuildReferencedFileList( CUtlVector< UnusedContent::CUtlSymbol >& resfiles, CUtlRBTree< ReferencedFile, int >& files, const char *resfile ) +{ + + // Load the reslist file + FileHandle_t resfilehandle; + resfilehandle = g_pFileSystem->Open( resfile, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE != resfilehandle ) + { + // Read in and parse mapcycle.txt + int length = g_pFileSystem->Size(resfilehandle); + if ( length > 0 ) + { + char *pStart = (char *)new char[ length + 1 ]; + if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) + ) + { + pStart[ length ] = 0; + + char *pFileList = pStart; + + while ( 1 ) + { + char szResList[ 256 ]; + + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) <= 0 ) + break; + + Q_snprintf(szResList, sizeof( szResList ), "%s%s.lst", g_szReslistDir, com_token ); + _strlwr( szResList ); + Q_FixSlashes( szResList ); + + if ( !g_pFileSystem->FileExists( szResList ) ) + { + vprint( 0, "Couldn't find %s\n", szResList ); + continue; + } + + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( szResList ); + resfiles.AddToTail( sym ); + + } + } + delete[] pStart; + } + + g_pFileSystem->Close(resfilehandle); + } + else + { + Error( "Unable to open reslist file %s\n", resfile ); + exit( -1 ); + } + + if ( g_pFileSystem->FileExists( CFmtStr( "%sall.lst", g_szReslistDir ) ) ) + { + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( CFmtStr( "%sall.lst", g_szReslistDir ) ); + resfiles.AddToTail( sym ); + } + + if ( g_pFileSystem->FileExists( CFmtStr( "%sengine.lst", g_szReslistDir ) ) ) + { + UnusedContent::CUtlSymbol sym = g_Analysis.symbols.AddString( CFmtStr( "%sengine.lst", g_szReslistDir ) ); + resfiles.AddToTail( sym ); + } + + // Do we have any resfiles? + if ( resfiles.Count() <= 0 ) + { + vprint( 0, "%s didn't have any actual .lst files in the reslists folder, have you run the engine with %s\n", resfile, + "-makereslists -usereslistfile maplist.txt" ); + return false; + } + + vprint( 0, "Parsed %i reslist files\n", resfiles.Count() ); + + // Now load in each res file + int c = resfiles.Count(); + for ( int i = 0; i < c; ++i ) + { + UnusedContent::CUtlSymbol& filename = resfiles[ i ]; + char fn[ 256 ]; + Q_strncpy( fn, g_Analysis.symbols.String( filename ), sizeof( fn ) ); + + ParseFilesFromResList( filename, files, fn ); + } + + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckLogFile( void ) +{ + if ( uselogfile ) + { + _unlink( "log.txt" ); + vprint( 0, " Outputting to log.txt\n" ); + } +} + +void PrintHeader() +{ + vprint( 0, "Valve Software - unusedcontent.exe (%s)\n", __DATE__ ); + vprint( 0, "--- Compares reslists with actual game content tree to show unreferenced content and stats ---\n" ); +} + +static bool ReferencedFileLessFunc( const ReferencedFile &lhs, const ReferencedFile &rhs ) +{ + char const *s1 = g_Analysis.symbols.String( lhs.sym ); + char const *s2 = g_Analysis.symbols.String( rhs.sym ); + + return Q_stricmp( s1, s2 ) < 0; +} + +static bool FileEntryLessFunc( const FileEntry &lhs, const FileEntry &rhs ) +{ + char const *s1 = g_Analysis.symbols.String( lhs.sym ); + char const *s2 = g_Analysis.symbols.String( rhs.sym ); + + return Q_stricmp( s1, s2 ) < 0; +} + +static bool RefFileLessFunc( const ReferencedFile &lhs, const ReferencedFile &rhs ) +{ + char const *s1 = g_Analysis.symbols.String( lhs.sym ); + char const *s2 = g_Analysis.symbols.String( rhs.sym ); + + return Q_stricmp( s1, s2 ) < 0; +} + +struct DirEntry +{ + DirEntry() + { + total = 0; + unreferenced = 0; + whitelist = 0; + } + + double total; + double unreferenced; + double whitelist; +}; + +void Correlate( CUtlRBTree< ReferencedFile, int >& referencedfiles, CUtlVector< FileEntry >& contentfiles, const char *modname ) +{ + int i; + int c = contentfiles.Count(); + + double totalDiskSize = 0; + double totalReferencedDiskSize = 0; + double totalWhiteListDiskSize = 0; + + for ( i = 0; i < c; ++i ) + { + totalDiskSize += contentfiles [ i ].size; + } + + vprint( 0, "Content tree size on disk %s\n", Q_pretifymem( totalDiskSize, 3 ) ); + + // Analysis is to walk tree and see which files on disk are referenced in the .lst files + // Need a fast lookup from file symbol to referenced list + CUtlRBTree< ReferencedFile, int > tree( 0, 0, ReferencedFileLessFunc ); + c = referencedfiles.Count(); + for ( i = 0 ; i < c; ++i ) + { + tree.Insert( referencedfiles[ i ] ); + } + + // Now walk the on disk file and see check off resources which are in referenced + c = contentfiles.Count(); + int invalidindex = tree.InvalidIndex(); + unsigned int refcounted = 0; + unsigned int whitelisted = 0; + + filesystem->RemoveFile( CFmtStr( "%swhitelist.lst", g_szReslistDir ), "GAME" ); + + for ( i = 0; i < c; ++i ) + { + FileEntry & entry = contentfiles[ i ]; + + ReferencedFile foo; + foo.sym = entry.sym; + + bool gameref = tree.Find( foo ) != invalidindex; + char const *fn = g_Analysis.symbols.String( entry.sym ); + + bool whitelist = g_WhiteList.Find( entry.sym ) != g_WhiteList.InvalidIndex(); + + if ( gameref || whitelist ) + { + entry.referenced = gameref ? REFERENCED_GAME : REFERENCED_WHITELIST; + totalReferencedDiskSize += entry.size; + if ( entry.referenced == REFERENCED_WHITELIST ) + { + logprint( CFmtStr( "%swhitelist.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, fn ); + + totalWhiteListDiskSize += entry.size; + ++whitelisted; + } + ++refcounted; + } + } + + vprint( 0, "Found %i referenced (%i whitelist) files in tree, %s\n", refcounted, whitelisted, Q_pretifymem( totalReferencedDiskSize, 2 ) ); + vprint( 0, "%s appear unused\n", Q_pretifymem( totalDiskSize - totalReferencedDiskSize, 2 ) ); + + // Now sort and dump the unreferenced ones.. + vprint( 0, "Sorting unreferenced files list...\n" ); + + CUtlRBTree< FileEntry, int > unreftree( 0, 0, FileEntryLessFunc ); + for ( i = 0; i < c; ++i ) + { + FileEntry & entry = contentfiles[ i ]; + if ( entry.referenced != REFERENCED_NO ) + continue; + + unreftree.Insert( entry ); + } + + // Now walk the unref tree in order + i = unreftree.FirstInorder(); + invalidindex = unreftree.InvalidIndex(); + int index = 0; + while ( i != invalidindex ) + { + FileEntry & entry = unreftree[ i ]; + + if ( showreferencedfiles ) + { + vprint( 1, "%6i %12s: %s\n", ++index, Q_pretifymem( entry.size, 2 ), g_Analysis.symbols.String( entry.sym ) ); + } + + i = unreftree.NextInorder( i ); + } + + if ( showmapfileusage ) + { + vprint( 0, "Writing referenced.csv...\n" ); + + // Now walk the list of referenced files and print out how many and which maps reference them + i = tree.FirstInorder(); + invalidindex = tree.InvalidIndex(); + index = 0; + while ( i != invalidindex ) + { + ReferencedFile & entry = tree[ i ]; + + char ext[ 32 ]; + Q_ExtractFileExtension( g_Analysis.symbols.String( entry.sym ), ext, sizeof( ext ) ); + + logprint( "referenced.csv", "\"%s\",\"%s\",%d", g_Analysis.symbols.String( entry.sym ), ext, entry.maplist.Count() ); + + int mapcount = entry.maplist.Count(); + for ( int j = 0 ; j < mapcount; ++j ) + { + char basemap[ 128 ]; + Q_FileBase( g_Analysis.symbols.String( entry.maplist[ j ] ), basemap, sizeof( basemap ) ); + logprint( "referenced.csv", ",\"%s\"", basemap ); + } + + logprint( "referenced.csv", "\n" ); + + i = tree.NextInorder( i ); + } + } + + + vprint( 0, "\nBuilding directory summary list...\n" ); + + // Now build summaries by root branch off of gamedir (e.g., for sound, materials, models, etc.) + CUtlDict< DirEntry, int > directories; + invalidindex = directories.InvalidIndex(); + for ( i = 0; i < c; ++i ) + { + FileEntry & entry = contentfiles[ i ]; + + // Get the dir name + char const *dirname = g_Analysis.symbols.String( entry.sym ); + + const char *backslash = strstr( dirname, "\\" ); + + char dir[ 256 ]; + if ( !backslash ) + { + dir[0] = 0; + } + else + { + Q_strncpy( dir, dirname, backslash - dirname + 1); + } + + + int idx = directories.Find( dir ); + if ( idx == invalidindex ) + { + DirEntry foo; + idx = directories.Insert( dir, foo ); + } + + DirEntry & de = directories[ idx ]; + de.total += entry.size; + if ( entry.referenced == REFERENCED_NO ) + { + de.unreferenced += entry.size; + } + if ( entry.referenced == REFERENCED_WHITELIST ) + { + de.whitelist += entry.size; + } + } + + if ( spewdeletions ) + { + // Spew deletion commands to console + if ( immediatedelete ) + { + vprint( 0, "\n\nDeleting files...\n" ); + } + else + { + vprint( 0, "\n\nGenerating deletions.bat\n" ); + } + + i = unreftree.FirstInorder(); + invalidindex = unreftree.InvalidIndex(); + float deletionSize = 0.0f; + int deletionCount = 0; + + while ( i != invalidindex ) + { + FileEntry & entry = unreftree[ i ]; + i = unreftree.NextInorder( i ); + + // Don't delete stuff that's in the white list + if ( g_WhiteList.Find( entry.sym ) != g_WhiteList.InvalidIndex() ) + { + if ( verbose ) + { + vprint( 0, "whitelist blocked deletion of %s\n", g_Analysis.symbols.String( entry.sym ) ); + } + continue; + } + + ++deletionCount; + deletionSize += entry.size; + + if ( immediatedelete ) + { + if ( _chmod( g_Analysis.symbols.String( entry.sym ), _S_IWRITE ) == -1 ) + { + vprint( 0, "Could not find file %s\n", g_Analysis.symbols.String( entry.sym ) ); + } + if ( _unlink( g_Analysis.symbols.String( entry.sym ) ) == -1 ) + { + vprint( 0, "Could not delete file %s\n", g_Analysis.symbols.String( entry.sym ) ); + } + + if ( deletionCount % 1000 == 0 ) + { + vprint( 0, "...deleted %i files\n", deletionCount ); + } + } + else + { + logprint( "deletions.bat", "del \"%s\" /f\n", g_Analysis.symbols.String( entry.sym ) ); + } + } + + vprint( 0, "\nFile deletion (%d files, %s)\n\n", deletionCount, Q_pretifymem(deletionSize, 2) ); + } + + double grand_total = 0; + double grand_total_unref = 0; + double grand_total_white = 0; + + char totalstring[ 20 ]; + char unrefstring[ 20 ]; + char refstring[ 20 ]; + char whiteliststring[ 20 ]; + + vprint( 0, "---------------------------------------- Summary ----------------------------------------\n" ); + + vprint( 0, "% 15s % 15s % 15s % 15s %12s\n", + "Referenced", + "WhiteListed", + "Unreferenced", + "Total", + "Directory" ); + + // Now walk the dictionary in order + i = directories.First(); + while ( i != invalidindex ) + { + DirEntry & de = directories[ i ]; + + double remainder = de.total - de.unreferenced; + + float percent_unref = 0.0f; + float percent_white = 0.0f; + if ( de.total > 0 ) + { + percent_unref = 100.0f * (float)de.unreferenced / (float)de.total; + percent_white = 100.0f * (float)de.whitelist / (float)de.total; + } + + Q_strncpy( totalstring, Q_pretifymem( de.total, 2 ), sizeof( totalstring ) ); + Q_strncpy( unrefstring, Q_pretifymem( de.unreferenced, 2 ), sizeof( unrefstring ) ); + Q_strncpy( refstring, Q_pretifymem( remainder, 2 ), sizeof( refstring ) ); + Q_strncpy( whiteliststring, Q_pretifymem( de.whitelist, 2 ), sizeof( whiteliststring ) ); + + vprint( 0, "%15s (%8.3f%%) %15s (%8.3f%%) %15s (%8.3f%%) %15s => dir: %s\n", + refstring, 100.0f - percent_unref, whiteliststring, percent_white, unrefstring, percent_unref, totalstring, directories.GetElementName( i ) ); + + grand_total += de.total; + grand_total_unref += de.unreferenced; + grand_total_white += de.whitelist; + + i = directories.Next( i ); + } + + Q_strncpy( totalstring, Q_pretifymem( grand_total, 2 ), sizeof( totalstring ) ); + Q_strncpy( unrefstring, Q_pretifymem( grand_total_unref, 2 ), sizeof( unrefstring ) ); + Q_strncpy( refstring, Q_pretifymem( grand_total - grand_total_unref, 2 ), sizeof( refstring ) ); + Q_strncpy( whiteliststring, Q_pretifymem( grand_total_white, 2 ), sizeof( whiteliststring ) ); + + double percent_unref = 100.0 * grand_total_unref / grand_total; + double percent_white = 100.0 * grand_total_white / grand_total; + + vprint( 0, "-----------------------------------------------------------------------------------------\n" ); + vprint( 0, "%15s (%8.3f%%) %15s (%8.3f%%) %15s (%8.3f%%) %15s\n", + refstring, 100.0f - percent_unref, whiteliststring, percent_white, unrefstring, percent_unref, totalstring ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : argc - +// argv[] - +// Output : int +//----------------------------------------------------------------------------- +int main( int argc, char* argv[] ) +{ + SpewOutputFunc( SpewFunc ); + SpewActivate( "unusedcontent", 2 ); + + CommandLine()->CreateCmdLine( argc, argv ); + + int i=1; + for ( i ; i<argc ; i++) + { + if ( argv[ i ][ 0 ] == '-' ) + { + switch( argv[ i ][ 1 ] ) + { + case 'l': + uselogfile = true; + break; + case 'v': + verbose = true; + break; + case 'r': + showreferencedfiles = true; + break; + case 'd': + spewdeletions = true; + break; + case 'i': + immediatedelete = true; + break; + case 'w': + printwhitelist = true; + break; + case 'm': + showmapfileusage = true; + break; + case 'g': + // Just skip -game + Assert( !Q_stricmp( argv[ i ], "-game" ) ); + ++i; + break; + case 'f': + // grab reslists folder + { + ++i; + Q_strncpy( g_szReslistDir, argv[ i ], sizeof( g_szReslistDir ) ); + Q_strlower( g_szReslistDir ); + Q_FixSlashes( g_szReslistDir ); + Q_AppendSlash( g_szReslistDir, sizeof( g_szReslistDir ) ); + + } + break; + default: + printusage(); + break; + } + } + } + + if ( argc < 3 || ( i != argc ) ) + { + PrintHeader(); + printusage(); + return 0; + } + + CheckLogFile(); + + PrintHeader(); + + vprint( 0, " Using reslist dir '%s'\n", g_szReslistDir ); + + vprint( 0, " Looking for extraneous content...\n" ); + + char resfile[ 256 ]; + strcpy( resfile, argv[ i - 1 ] ); + + vprint( 0, " Comparing results of resfile (%s) with files under current directory...\n", resfile ); + + char workingdir[ 256 ]; + workingdir[0] = 0; + Q_getwd( workingdir, sizeof( workingdir ) ); + + // If they didn't specify -game on the command line, use VPROJECT. + CmdLib_InitFileSystem( workingdir ); + + filesystem = (IFileSystem *)(CmdLib_GetFileSystemFactory()( FILESYSTEM_INTERFACE_VERSION, NULL )); + if ( !filesystem ) + { + AssertMsg( 0, "Failed to create/get IFileSystem" ); + return 1; + } + + g_pFullFileSystem->RemoveAllSearchPaths(); + g_pFullFileSystem->AddSearchPath(gamedir, "GAME"); + + Q_strlower( gamedir ); + Q_FixSlashes( gamedir ); + + // + //ProcessMaterialsDirectory( vmtdir ); + + // find out the mod dir name + Q_strncpy( modname, gamedir, sizeof(modname) ); + modname[ strlen(modname) - 1] = 0; + + if ( strrchr( modname, '\\' ) ) + { + Q_strncpy( modname, strrchr( modname, '\\' ) + 1, sizeof(modname) ); + } + else + { + Q_strncpy( modname, "", sizeof(modname) ); + } + vprint( 1, "Mod Name:%s\n", modname); + + + BuildCheckdirList(); + BuildWhiteList(); + + vprint( 0, "Building aggregate file list from resfile output\n" ); + CUtlRBTree< ReferencedFile, int > referencedfiles( 0, 0, RefFileLessFunc ); + CUtlVector< UnusedContent::CUtlSymbol > resfiles; + + BuildReferencedFileList( resfiles, referencedfiles, resfile ); + + vprint( 0, "found %i files\n\n", referencedfiles.Count() ); + + vprint( 0, "Building list of all game content files\n" ); + CUtlVector< FileEntry > contentfiles; + CUtlVector< FileEntry > otherfiles; + BuildFileList( 0, contentfiles, &otherfiles, "", 0 ); + vprint( 0, "found %i files in content tree\n\n", contentfiles.Count() ); + + Correlate( referencedfiles, contentfiles, modname ); + + // now output the files not referenced in the whitelist or general reslists + filesystem->RemoveFile( CFmtStr( "%sunreferenced_files.lst", g_szReslistDir ), "GAME" ); + int c = otherfiles.Count(); + for ( i = 0; i < c; ++i ) + { + FileEntry & entry = otherfiles[ i ]; + char const *name = g_Analysis.symbols.String( entry.sym ); + + logprint( CFmtStr( "%sunreferenced_files.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, name ); + } + + // also include the files from deletions.bat, as we don't actually run that now + c = contentfiles.Count(); + for ( i = 0; i < c; ++i ) + { + FileEntry & entry = contentfiles[ i ]; + if ( entry.referenced != REFERENCED_NO ) + continue; + + char const *fn = g_Analysis.symbols.String( entry.sym ); + logprint( CFmtStr( "%sunreferenced_files.lst", g_szReslistDir ), "\"%s\\%s\"\n", modname, fn ); + } + + FileSystem_Term(); + + return 0; +}
\ No newline at end of file |