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.
1767 lines
43 KiB
1767 lines
43 KiB
//========= 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;
|
|
}
|