//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: vcd_sound_check.cpp : Defines the entry point for the console application. // //===========================================================================// #include #include #include "tier0/dbg.h" #include "tier1/utldict.h" #include "filesystem.h" #include "cmdlib.h" #include "scriplib.h" #include "vstdlib/random.h" #include "tier1/UtlBuffer.h" #include "pacifier.h" #include "appframework/tier3app.h" #include "tier0/icommandline.h" #include "vgui/IVGui.h" #include "vgui_controls/controls.h" #include "vgui/ILocalize.h" #include "tier1/checksum_crc.h" #include "tier1/UtlSortVector.h" #include "tier1/utlmap.h" #include "captioncompiler.h" #include "tier0/fasttimer.h" using namespace vgui; // #define TESTING 1 bool uselogfile = false; bool bX360 = false; struct AnalysisData { CUtlSymbolTable symbols; }; static AnalysisData g_Analysis; IBaseFileSystem *filesystem = NULL; 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" ); } return SPEW_CONTINUE; } //----------------------------------------------------------------------------- // 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; static bool first = true; if ( first ) { first = false; 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 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void printusage( void ) { vprint( 0, "usage: captioncompiler closecaptionfile.txt\n\ \t-v = verbose output\n\ \t-l = log to file log.txt\n\ \ne.g.: kvc -l u:/xbox/game/hl2x/resource/closecaption_english.txt" ); // Exit app exit( 1 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CheckLogFile( void ) { if ( uselogfile ) { _unlink( "log.txt" ); vprint( 0, " Outputting to log.txt\n" ); } } void PrintHeader() { vprint( 0, "Valve Software - captioncompiler.exe (%s)\n", __DATE__ ); vprint( 0, "--- Close Caption File compiler ---\n" ); } //----------------------------------------------------------------------------- // The application object //----------------------------------------------------------------------------- class CCompileCaptionsApp : public CTier3SteamApp { typedef CTier3SteamApp BaseClass; public: // Methods of IApplication virtual bool Create(); virtual bool PreInit(); virtual int Main(); virtual void PostShutdown(); virtual void Destroy(); private: // Sets up the search paths bool SetupSearchPaths(); void CompileCaptionFile( char const *infile, char const *outfile ); void DescribeCaptions( char const *file ); }; bool CCompileCaptionsApp::Create() { SpewOutputFunc( SpewFunc ); SpewActivate( "kvc", 2 ); AppSystemInfo_t appSystems[] = { { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, { "", "" } // Required to terminate the list }; return AddSystems( appSystems ); } void CCompileCaptionsApp::Destroy() { } //----------------------------------------------------------------------------- // Sets up the game path //----------------------------------------------------------------------------- bool CCompileCaptionsApp::SetupSearchPaths() { if ( !BaseClass::SetupSearchPaths( NULL, false, true ) ) return false; // Set gamedir. Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), GetGameInfoPath() ); Q_AppendSlash( gamedir, sizeof( gamedir ) ); return true; } //----------------------------------------------------------------------------- // Init, shutdown //----------------------------------------------------------------------------- bool CCompileCaptionsApp::PreInit( ) { if ( !BaseClass::PreInit() ) return false; g_pFileSystem = g_pFullFileSystem; if ( !g_pFileSystem || !g_pVGui || !g_pVGuiLocalize ) { Error( "Unable to load required library interface!\n" ); return false; } // MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); g_pFullFileSystem->SetWarningFunc( Warning ); // Add paths... if ( !SetupSearchPaths() ) return false; return true; } void CCompileCaptionsApp::PostShutdown() { g_pFileSystem = NULL; BaseClass::PostShutdown(); } void CCompileCaptionsApp::CompileCaptionFile( char const *infile, char const *outfile ) { StringIndex_t maxindex = (StringIndex_t)-1; int maxunicodesize = 0; int totalsize = 0; int c = 0; int curblock = 0; int usedBytes = 0; int blockSize = MAX_BLOCK_SIZE; int freeSpace = 0; CUtlVector< CaptionLookup_t > directory; CUtlBuffer data; CUtlRBTree< unsigned int > hashcollision( 0, 0, DefLessFunc( unsigned int ) ); for ( StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex(); i != INVALID_LOCALIZE_STRING_INDEX; i = g_pVGuiLocalize->GetNextStringIndex( i ), ++c ) { char const *entryName = g_pVGuiLocalize->GetNameByIndex( i ); CaptionLookup_t entry; entry.SetHash( entryName ); // vprint( 0, "%d / %d: %s == %u\n", c, i, g_pVGuiLocalize->GetNameByIndex( i ), entry.hash ); if ( hashcollision.Find( entry.hash ) != hashcollision.InvalidIndex() ) { Error( "Hash name collision on %s!!!\n", g_pVGuiLocalize->GetNameByIndex( i ) ); } hashcollision.Insert( entry.hash ); const wchar_t *text = g_pVGuiLocalize->GetValueByIndex( i ); if ( verbose ) { vprint( 0, "Processing: '%30.30s' = '%S'\n", entryName, text ); } int len = text ? ( wcslen( text ) + 1 ) * sizeof( short ) : 0; if ( len > maxunicodesize ) { maxindex = i; maxunicodesize = len; } if ( len > blockSize ) { Error( "Caption text file '%s' contains a single caption '%s' of %d bytes (%d is max), change MAX_BLOCK_SIZE in captioncompiler.h to fix!!!\n", g_pVGuiLocalize->GetNameByIndex( i ), entryName, len, blockSize ); } totalsize += len; if ( usedBytes + len >= blockSize ) { ++curblock; int leftover = ( blockSize - usedBytes ); totalsize += leftover; freeSpace += leftover; while ( --leftover >= 0 ) { data.PutChar( 0 ); } usedBytes = len; entry.offset = 0; data.Put( (const void *)text, len ); } else { entry.offset = usedBytes; usedBytes += len; data.Put( (const void *)text, len ); } entry.length = len; entry.blockNum = curblock; directory.AddToTail( entry ); } int leftover = ( blockSize - usedBytes ); totalsize += leftover; freeSpace += leftover; while ( --leftover >= 0 ) { data.PutChar( 0 ); } vprint( 0, "Found %i strings in '%s'\n", c, infile ); if ( maxindex != INVALID_LOCALIZE_STRING_INDEX ) { vprint( 0, "Longest string '%s' = (%i) bytes average(%.3f)\n%", g_pVGuiLocalize->GetNameByIndex( maxindex ), maxunicodesize, (float)totalsize/(float)c ); } vprint( 0, "%d blocks (%d bytes each), %d bytes wasted (%.3f per block average), total bytes %d\n", curblock + 1, blockSize, freeSpace, (float)freeSpace/(float)( curblock + 1 ), totalsize ); vprint( 0, "directory size %d entries, %d bytes, data size %d bytes\n", directory.Count(), directory.Count() * sizeof( CaptionLookup_t ), data.TellPut() ); CompiledCaptionHeader_t header; header.magic = COMPILED_CAPTION_FILEID; header.version = COMPILED_CAPTION_VERSION; header.numblocks = curblock + 1; header.blocksize = blockSize; header.directorysize = directory.Count(); header.dataoffset = 0; // Now write the outfile CUtlBuffer out; out.Put( &header, sizeof( header ) ); out.Put( directory.Base(), directory.Count() * sizeof( CaptionLookup_t ) ); int curOffset = out.TellPut(); // Round it up to the next 512 byte boundary int nBytesDestBuffer = AlignValue( curOffset, 512 ); // align to HD sector int nPadding = nBytesDestBuffer - curOffset; while ( --nPadding >= 0 ) { out.PutChar( 0 ); } out.Put( data.Base(), data.TellPut() ); // Write out a corrected header header.dataoffset = nBytesDestBuffer; int savePos = out.TellPut(); out.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); out.Put( &header, sizeof( header ) ); out.SeekPut( CUtlBuffer::SEEK_HEAD, savePos ); g_pFullFileSystem->WriteFile( outfile, NULL, out ); // Jeep: this function no longer exisits /*if ( bX360 ) { UpdateOrCreateCaptionFile_X360( g_pFullFileSystem, outfile, NULL, true ); }*/ } void CCompileCaptionsApp::DescribeCaptions( char const *file ) { CUtlBuffer buf; if ( !g_pFullFileSystem->ReadFile( file, NULL, buf ) ) { Error( "Unable to read '%s' into buffer\n", file ); } CompiledCaptionHeader_t header; buf.Get( &header, sizeof( header ) ); if ( header.magic != COMPILED_CAPTION_FILEID ) Error( "Invalid file id for %s\n", file ); if ( header.version != COMPILED_CAPTION_VERSION ) Error( "Invalid file version for %s\n", file ); // Read the directory CUtlSortVector< CaptionLookup_t, CCaptionLookupLess > directory; directory.EnsureCapacity( header.directorysize ); directory.CopyArray( (const CaptionLookup_t *)buf.PeekGet(), header.directorysize ); directory.RedoSort( true ); buf.SeekGet( CUtlBuffer::SEEK_HEAD, header.dataoffset ); int i; CUtlVector< CaptionBlock_t > blocks; for ( i = 0; i < header.numblocks; ++i ) { CaptionBlock_t& newBlock = blocks[ blocks.AddToTail() ]; Q_memset( newBlock.data, 0, sizeof( newBlock.data ) ); buf.Get( newBlock.data, header.blocksize ); } CUtlMap< unsigned int, StringIndex_t > inverseMap( 0, 0, DefLessFunc( unsigned int ) ); for ( StringIndex_t idx = g_pVGuiLocalize->GetFirstStringIndex(); idx != INVALID_LOCALIZE_STRING_INDEX; idx = g_pVGuiLocalize->GetNextStringIndex( idx ) ) { const char *name = g_pVGuiLocalize->GetNameByIndex( idx ); CaptionLookup_t dummy; dummy.SetHash( name ); inverseMap.Insert( dummy.hash, idx ); } // Now print everything out... for ( i = 0; i < header.directorysize; ++i ) { const CaptionLookup_t& entry = directory[ i ]; char const *name = g_pVGuiLocalize->GetNameByIndex( inverseMap.Element( inverseMap.Find( entry.hash ) ) ); const CaptionBlock_t& block = blocks[ entry.blockNum ]; const wchar_t *data = (const wchar_t *)&block.data[ entry.offset ]; wchar_t *temp = ( wchar_t * )_alloca( entry.length * sizeof( short ) ); wcsncpy( temp, data, ( entry.length / sizeof( short ) ) - 1 ); vprint( 0, "%3.3d: (%40.40s) hash(%15.15u), block(%4.4d), offset(%4.4d), len(%4.4d) %S\n", i, name, entry.hash, entry.blockNum, entry.offset, entry.length, temp ); } } //----------------------------------------------------------------------------- // main application //----------------------------------------------------------------------------- int CCompileCaptionsApp::Main() { CUtlVector< CUtlSymbol > worklist; int i = 1; for ( i ; iParmCount() ; i++) { if ( CommandLine()->GetParm( i )[ 0 ] == '-' ) { switch( CommandLine()->GetParm( i )[ 1 ] ) { case 'l': uselogfile = true; break; case 'v': verbose = true; break; case 'x': bX360 = true; break; case 'g': // -game ++i; break; default: printusage(); break; } } else if ( i != 0 ) { char fn[ 512 ]; Q_strncpy( fn, CommandLine()->GetParm( i ), sizeof( fn ) ); Q_FixSlashes( fn ); Q_strlower( fn ); CUtlSymbol sym; sym = fn; worklist.AddToTail( sym ); } } if ( CommandLine()->ParmCount() < 2 || ( i != CommandLine()->ParmCount() ) || worklist.Count() != 1 ) { PrintHeader(); printusage(); } CheckLogFile(); PrintHeader(); char binaries[MAX_PATH]; Q_strncpy( binaries, gamedir, MAX_PATH ); Q_StripTrailingSlash( binaries ); Q_strncat( binaries, "/../bin", MAX_PATH, MAX_PATH ); char outfile[ 512 ]; if ( Q_stristr( worklist[ worklist.Count() - 1 ].String(), gamedir ) ) { Q_strncpy( outfile, &worklist[ worklist.Count() - 1 ].String()[ Q_strlen( gamedir ) ] , sizeof( outfile ) ); } else { Q_snprintf( outfile, sizeof( outfile ), "resource\\%s", worklist[ worklist.Count() - 1 ].String() ); } char infile[ 512 ]; Q_strncpy( infile, outfile, sizeof( infile ) ); Q_SetExtension( outfile, ".dat", sizeof( outfile ) ); vprint( 0, "gamedir[ %s ]\n", gamedir ); vprint( 0, "infile[ %s ]\n", infile ); vprint( 0, "outfile[ %s ]\n", outfile ); g_pFullFileSystem->AddSearchPath( binaries, "EXECUTABLE_PATH" ); if ( !g_pVGuiLocalize->AddFile( infile, "MOD", false ) ) { Error( "Unable to add localization file '%s'\n", infile ); } vprint( 0, " Compiling Captions for '%s'...\n", infile ); CompileCaptionFile( infile, outfile ); if ( verbose ) { DescribeCaptions( outfile ); } g_pVGuiLocalize->RemoveAll(); return 0; } //----------------------------------------------------------------------------- // Purpose: Main entry point //----------------------------------------------------------------------------- DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CCompileCaptionsApp )