summaryrefslogtreecommitdiff
path: root/utils/unusedcontent/unusedcontent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/unusedcontent/unusedcontent.cpp')
-rw-r--r--utils/unusedcontent/unusedcontent.cpp1767
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