//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: searches through all bsp files in the current directory parsing out entity details // //=============================================================================// #include #include #include #include #define max(x,y) ( ((x) > (y)) ? (x) : (y) ) void SetSearchWord( const char *searchWord ); char *FindSearchWord( char *buffer, char *bufend ); char *ParseToken( char *data, char *newToken ); void ClearTable( void ); void ClearUsageCountTable( void ); void AddToTable( const char *name ); void PrintOutTable( void ); void ParseFGD( char *buffer, char *bufend, const char *searchKey ); const char *g_UsageString = "usage: entcount [-fgd ] [-nofgd] [-permap] [-onlyent ] [-files ]\n"; int main( int argc, char *argv[] ) { if ( argc < 2 ) { printf( g_UsageString ); return 0; } bool printPerMap = false; const char *filterEnt = NULL; const char *fgdFile = NULL; const char *fileMask = "*.bsp"; // parse the arguments for ( int count = 1; count < argc; count++ ) { if ( !stricmp( argv[count], "-permap" ) ) { printPerMap = true; } else if ( !stricmp( argv[count], "-onlyent" ) ) { count++; if ( count < argc ) { filterEnt = argv[count]; } } else if ( !stricmp( argv[count], "-fgd" ) ) { count++; if ( count < argc ) { fgdFile = argv[count]; } } else if ( !stricmp( argv[count], "-files" ) ) { count++; if ( count < argc ) { fileMask = argv[count]; } } else if ( !stricmp( argv[count], "-nofgd" ) ) { } else { printf( "error: unknown parameter \"%s\"\n", argv[count] ); printf( g_UsageString ); return 1; } } // clear the entity accumulator table ClearTable(); // open and parse the FGD, unless the -nofgd flag is specified if ( fgdFile && !filterEnt && !printPerMap ) { FILE *f = fopen( fgdFile, "rb" ); if ( !f ) { printf( "error: could not open file %s\n", fgdFile ); return 2; } int filelen; fseek( f, 0, SEEK_END ); filelen = ftell( f ); fseek( f, 0, SEEK_SET ); // allocate and load into memory char *buffer = (char*)malloc( filelen ); char *bufend = buffer + filelen; fread( buffer, filelen, 1, f ); fclose( f ); // search for all @pointclass, then @solidclass ParseFGD( buffer, bufend, "PointClass" ); ParseFGD( buffer, bufend, "SolidClass" ); // reset the usage counts to 0 ClearUsageCountTable(); free( buffer ); } // parse through all the bsp files _finddata_t fileinfo; int FHandle = _findfirst( fileMask, &fileinfo ); if ( FHandle == -1 ) { printf( "error: no files found in current directory\n" ); return 1; } SetSearchWord( "\"classname\"" ); do { // open the file FILE *f = fopen( fileinfo.name, "rb" ); if ( !f ) { printf( "error: couldn't open file %s\n", fileinfo.name ); return 2; } // calculate file length int filelen; fseek( f, 0, SEEK_END ); filelen = ftell( f ); fseek( f, 0, SEEK_SET ); // allocate and load into memory char *buffer = (char*)malloc( filelen ); char *bufpos = buffer + strlen( "\"classname\"" ) - 1; char *bufend = buffer + filelen; fread( buffer, filelen, 1, f ); fclose( f ); bool entFound = false; while ( 1 ) { bufpos = FindSearchWord( bufpos, bufend ); if ( !bufpos ) break; // find the next word static char Token[256]; ParseToken( bufpos, Token ); // add the word to the list, filtering if necessary if ( !filterEnt || !stricmp(filterEnt, Token) ) { AddToTable( Token ); entFound = true; } bufpos += strlen( Token ); } free( buffer ); // print the bsp name, if an ent is found, or we are not filtering for ents if ( entFound || !filterEnt ) printf( "%s\n", fileinfo.name ); if ( printPerMap ) { PrintOutTable(); ClearUsageCountTable(); } } while ( _findnext(FHandle, &fileinfo) == 0 ); PrintOutTable(); return 0; } void ParseFGD( char *buffer, char *bufend, const char *searchKey ) { char *bufpos = buffer + strlen( searchKey ) - 1; SetSearchWord( searchKey ); while ( 1 ) { bufpos = FindSearchWord( bufpos, bufend ); if ( !bufpos ) break; // search for the corresponding '=' while ( *bufpos != '=' ) bufpos++; bufpos++; // find the classname static char Token[256]; ParseToken( bufpos, Token ); AddToTable( Token ); bufpos += strlen( Token ); } } /////////// entity table stuff ////////////// const int MAX_ENTS = 2000; int NumEnts = 0; char *EntNames[ MAX_ENTS ]; int EntUsage[ MAX_ENTS ]; void ClearTable( void ) { memset( EntNames, 0, sizeof(EntNames) ); memset( EntUsage, 0, sizeof(EntUsage) ); } void ClearUsageCountTable( void ) { memset( EntUsage, 0, sizeof(EntUsage) ); } void AddToTable( const char *name ) { // search for it in the table for ( int i = 0; i < NumEnts; i++ ) { if ( EntNames[i] && !strcmp(EntNames[i], name) ) { // it's already in the table // increment the usage count EntUsage[i] += 1; return; } } // append to the table EntNames[NumEnts] = (char*)malloc( strlen(name) + 1 ); strcpy( EntNames[NumEnts], name ); EntUsage[NumEnts] = 1; NumEnts++; } void PrintOutTable( void ) { while ( 1 ) { // find the highest item int highestUsage = -1; int highestEnt = 0; for ( int i = 0; i < NumEnts; i++ ) { if ( EntNames[i] && highestUsage < EntUsage[i] ) { highestUsage = EntUsage[i]; highestEnt = i; } } // check for no more ents if ( highestUsage == -1 ) return; // display usage stats of item printf( " %5d %s\n", highestUsage, EntNames[highestEnt] ); // remove item from list free( EntNames[highestEnt] ); EntNames[highestEnt] = NULL; } } ////////// string search stuff //////////// static unsigned char JumpTable[256]; static int SearchWordLen = 0; static const char *SearchWord; void SetSearchWord( const char *Word ) { SearchWord = Word; SearchWordLen = strlen( SearchWord ); // build the jump table // initialize all values to jump the length of the string memset( JumpTable, SearchWordLen, sizeof(JumpTable) ); // set the amount the searcher can jump forward, depending on the character for ( int i = 0; i < SearchWordLen; i++ ) { JumpTable[ (unsigned char)SearchWord[i] ] = max( SearchWordLen - i - 1, 1 ); } } char *FindSearchWord( char *buffer, char *bufend ) { /* for ( int i = SearchWordLen-1; i >= 0; i-- ) { if ( *buffer != SearchWord[i] ) { buffer += ( JumpTable[ (unsigned char)(*(buffer + i - SearchWordLen + 1)) ] - 1 ); // no more buffer to search if ( buffer >= bufend ) return NULL; // reset search counter i = SearchWordLen; } else { // it's a match, move backwards to search buffer--; } } */ while ( 1 ) { if ( strnicmp(buffer - SearchWordLen, SearchWord, SearchWordLen) ) { // strings not equal, jump ahead buffer += JumpTable[ (unsigned char)*buffer ]; if ( buffer >= bufend ) return NULL; } else { break; } } // we have a match! // return a pointer just past the found key return buffer; } /* ============== ParseToken Parse a token out of a string outputs the parsed token into newToken ============== */ char *ParseToken( char *data, char *newToken ) { int c; int len; len = 0; newToken[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) return NULL; // end of file; data++; } // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; while ( len < 256 ) { c = *data++; if (c=='\"' || !c) { newToken[len] = 0; return data; } newToken[len] = c; len++; } if ( len >= 256 ) { len--; newToken[len] = 0; } } // parse single characters if ( c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' ) { newToken[len] = c; len++; newToken[len] = 0; return data+1; } // parse a regular word do { newToken[len] = c; data++; len++; c = *data; if ( c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' ) break; if ( len >= 256 ) { len--; newToken[len] = 0; break; } } while (c>32); newToken[len] = 0; return data; }