summaryrefslogtreecommitdiff
path: root/utils/localization_check/localization_check.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/localization_check/localization_check.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'utils/localization_check/localization_check.cpp')
-rw-r--r--utils/localization_check/localization_check.cpp3638
1 files changed, 3638 insertions, 0 deletions
diff --git a/utils/localization_check/localization_check.cpp b/utils/localization_check/localization_check.cpp
new file mode 100644
index 0000000..3b37034
--- /dev/null
+++ b/utils/localization_check/localization_check.cpp
@@ -0,0 +1,3638 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: localization_check.cpp : Defines the entry point for the console application.
+//
+//
+// What the tool does:
+//
+// Load g_pSoundEmitterSystem, vgui::localize, soundcombiner system, vcd system
+//
+// Catalog all files in closecaption/ folder
+// Iterate all known vcds
+//
+// 1) For each sound emitted by a vcd not marked as CC_DISABLED, verify that there's an entry in the localization table
+// 2) For each combined sound in a vcd, make sure there's a valid entry in the localization table
+// 3) For each combined sound, verify that the english version combined .wav file has the proper checksum
+// 4) Note any files in the closecaption folder which are orphaned after parsing the above
+// 5) If hl2_french directories etc. exist, then compare combined .wav files with localized versions and
+// see if localized checksum tag differs from US one, or warn if tag missing, but complain if .wav duration is different.
+//
+// UNDONE:re-create combined .wav files in english to the extent that the checksums mismatch?
+//
+//===========================================================================//
+#include "cbase.h"
+#include <stdio.h>
+#include <conio.h>
+#include <windows.h>
+#include <mmreg.h>
+#include <direct.h>
+#include "tier0/dbg.h"
+#include "utldict.h"
+#include "filesystem.h"
+#include "KeyValues.h"
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "appframework/tier3app.h"
+#include "vstdlib/random.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "choreoscene.h"
+#include "choreoevent.h"
+#include "choreochannel.h"
+#include "choreoactor.h"
+#include "iscenetokenprocessor.h"
+#include "ifaceposersound.h"
+#include "snd_audio_source.h"
+#include "snd_wave_source.h"
+#include "AudioWaveOutput.h"
+#include "isoundcombiner.h"
+#include "tier0/icommandline.h"
+#include <vgui/ILocalize.h>
+#include "vgui/ivgui.h"
+#include "soundchars.h"
+#include "sentence.h"
+#include "tier2/riff.h"
+#include "utlbuffer.h"
+#include "FileSystem_Helpers.h"
+#include "pacifier.h"
+#include "phonemeextractor/PhonemeExtractor.h"
+#include "UnicodeFileHelpers.h"
+
+using namespace vgui;
+
+bool uselogfile = false;
+bool regenerate = false;
+bool regenerate_quiet = false;
+bool regenerate_all = false; // user hit a to y/n/all prompt
+bool generate_usage = false;
+bool nuke = false;
+bool checkscriptsounds = false;
+bool build_cc = false;
+bool build_script = false;
+bool wavcheck = false;
+bool extractphonemes = false;
+bool checkforloops = false;
+bool importcaptions = false;
+bool checkfordups = false;
+bool makecopybatch = false;
+bool syncducking = false;
+
+char sounddir[ 512 ];
+char importfile[ 512 ]; // for -i processing
+char fromdir[ 512 ];
+char todir[ 512 ];
+
+struct AnalysisData
+{
+ CUtlSymbolTable symbols;
+};
+
+IFileSystem *filesystem = NULL;
+
+static AnalysisData g_Analysis;
+
+static CUniformRandomStream g_Random;
+IUniformRandomStream *random = &g_Random;
+
+static bool spewed = false;
+static bool forceextract = false;
+
+#define SOUND_DURATION_TOLERANCE 0.1f // 100 msec of slop
+
+
+void vprint( int depth, const char *fmt, ... );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void printusage( void )
+{
+ vprint( 0, "usage: localization_check <opts> languagename\n\
+ \t-v = verbose output\n\
+ \t-l = log to file log.txt\n\
+ \t-u = generate usage data for .vcds based on -makereslists maplist.txt files\n\
+ \t-b = generate nuke.bat which will nuke all of the unreferenced .vcds from your tree\n\
+ \t-s = generate list of unused sounds.txt entries\n\
+ \t-c = build cc fixed/fixed2.txt files\n\
+ \t-r = regenerate missing/mismatched combined wav files\n\
+ \t\t-q = quiet mode during regenerate\n\
+ languagename = check combined language files for existence and duration, 'english' for no extra checks\n\
+ \t-x = build script of dialog from .vcds\n\
+ \t-w sounddir = spew csv of wave files in directory, including sound and cc info\n\
+ \t-e sounddir = do textless phoneme processing on files in dir and subdirs\n\
+ \t-f with above, forces extraction even if wav already has phonemes (danger!)\n\
+ \t-i = import unicode wavename/caption into a new closecaption_test.txt file\n\
+ \t-d = check for duplicated unicode strings\n\
+ \t-p = pull raw english txt out of closecaption document, for spellchecking\n\
+ \t-m = given a directory of .wav files finds the full directory path they should live in based on english\n\
+ \t-a english_sound_dir localized_sound_dir = sets voice duck for sounds in localized_sound_dir to match the values in the english dir\n\
+ \t-loop = warn on any sound files in specified directory having loop markers in the .wav\n\
+ \ne.g.: localization_check -l -w npc/metropolice/vo -r french\n" );
+
+ // Exit app
+ exit( 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Helper for parsing scene data file
+//-----------------------------------------------------------------------------
+class CSceneTokenProcessor : public ISceneTokenProcessor
+{
+public:
+ const char *CurrentToken( void );
+ bool GetToken( bool crossline );
+ bool TokenAvailable( void );
+ void Error( const char *fmt, ... );
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CSceneTokenProcessor::CurrentToken( void )
+{
+ return token;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : crossline -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CSceneTokenProcessor::GetToken( bool crossline )
+{
+ return ::GetToken( crossline ) ? true : false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CSceneTokenProcessor::TokenAvailable( void )
+{
+ return ::TokenAvailable() ? true : false;
+}
+
+static char *va( char const *fmt, ... )
+{
+ static char string[ 2048 ];
+ va_list argptr;
+ va_start( argptr, fmt );
+ Q_vsnprintf( string, sizeof(string), fmt, argptr );
+ va_end( argptr );
+ return string;
+}
+
+void cleanquotes( char *text )
+{
+ char *out = text;
+ while ( *out )
+ {
+ if ( *out == '\"' )
+ {
+ *out++ = '\'';
+ }
+ else
+ {
+ ++out;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *fmt -
+// ... -
+//-----------------------------------------------------------------------------
+void CSceneTokenProcessor::Error( const char *fmt, ... )
+{
+ char string[ 2048 ];
+ va_list argptr;
+ va_start( argptr, fmt );
+ Q_vsnprintf( string, sizeof(string), fmt, argptr );
+ va_end( argptr );
+
+ Warning( "%s", string );
+ Assert(0);
+}
+
+static CSceneTokenProcessor g_TokenProcessor;
+
+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;
+}
+
+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 nuke_print( int depth, const char *fmt, ... )
+{
+ char string[ 8192 ];
+ va_list va;
+ va_start( va, fmt );
+ vsprintf( string, fmt, va );
+ va_end( va );
+
+ static bool first = false;
+
+
+ FILE *fp = NULL;
+
+ char const *nukefile = "nuke.bat";
+
+ if ( first )
+ {
+ first = false;
+ fp = fopen( nukefile, "wb" );
+ }
+ else
+ {
+ fp = fopen( nukefile, "ab" );
+ }
+
+ while ( depth-- > 0 )
+ {
+ fprintf( fp, " " );
+ }
+
+ char *p = string;
+ while ( *p )
+ {
+ if ( *p == '\n' )
+ {
+ fputc( '\r', fp );
+ }
+ fputc( *p, fp );
+ p++;
+ }
+
+ fclose( fp );
+}
+
+//-----------------------------------------------------------------------------
+// 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 Con_Printf( const char *fmt, ... )
+{
+ va_list args;
+ static char output[1024];
+
+ va_start( args, fmt );
+ Q_vsnprintf( output, sizeof( output ), fmt, args );
+ va_end( args );
+
+ vprint( 0, output );
+}
+
+void BuildFileList_R( CUtlVector< CUtlSymbol >& files, char const *dir, char const *extension )
+{
+ WIN32_FIND_DATA wfd;
+
+ char directory[ 256 ];
+ char filename[ MAX_PATH ];
+ HANDLE ff;
+
+ sprintf( directory, "%s\\*.*", dir );
+
+ if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE )
+ return;
+
+ int extlen = strlen( extension );
+
+ do
+ {
+ if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
+ {
+
+ if ( wfd.cFileName[ 0 ] == '.' )
+ continue;
+
+ // Recurse down directory
+ sprintf( filename, "%s\\%s", dir, wfd.cFileName );
+ BuildFileList_R( files, filename, extension );
+ }
+ else
+ {
+ int len = strlen( wfd.cFileName );
+ if ( len > extlen )
+ {
+ if ( !stricmp( &wfd.cFileName[ len - extlen ], extension ) )
+ {
+ Q_snprintf( filename, sizeof( filename ), "%s\\%s", dir, wfd.cFileName );
+ _strlwr( filename );
+
+ Q_FixSlashes( filename );
+
+ CUtlSymbol sym = g_Analysis.symbols.AddString( filename );
+ files.AddToTail( sym );
+
+ if ( !( files.Count() % 3000 ) )
+ {
+ vprint( 0, "...found %i .%s files\n", files.Count(), extension );
+ }
+ }
+ }
+ }
+ } while ( FindNextFile( ff, &wfd ) );
+}
+
+void BuildFileList( CUtlVector< CUtlSymbol >& files, char const *rootdir, char const *extension )
+{
+ files.RemoveAll();
+ BuildFileList_R( files, rootdir, extension );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CheckLogFile( void )
+{
+ if ( uselogfile )
+ {
+ _unlink( "log.txt" );
+ vprint( 0, " Outputting to log.txt\n" );
+ }
+}
+
+void PrintHeader()
+{
+ vprint( 0, "Valve Software - localization_check.exe (%s)\n", __DATE__ );
+ vprint( 0, "--- Voice Wav File .vcd Checker ---\n" );
+}
+
+char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender )
+{
+ if ( Q_stristr( soundname, ".wav" ) )
+ return PSkipSoundChars( soundname );
+
+ return PSkipSoundChars( g_pSoundEmitterSystem->GetWavFileForSound( soundname, gender ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Implements the RIFF i/o interface on stdio
+//-----------------------------------------------------------------------------
+class StdIOReadBinary : public IFileReadBinary
+{
+public:
+ int open( const char *pFileName )
+ {
+ return (int)g_pFullFileSystem->Open( pFileName, "rb" );
+ }
+
+ int read( void *pOutput, int size, int file )
+ {
+ if ( !file )
+ return 0;
+
+ return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file );
+ }
+
+ void seek( int file, int pos )
+ {
+ if ( !file )
+ return;
+
+ g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
+ }
+
+ unsigned int tell( int file )
+ {
+ if ( !file )
+ return 0;
+
+ return g_pFullFileSystem->Tell( (FileHandle_t)file );
+ }
+
+ unsigned int size( int file )
+ {
+ if ( !file )
+ return 0;
+
+ return g_pFullFileSystem->Size( (FileHandle_t)file );
+ }
+
+ void close( int file )
+ {
+ if ( !file )
+ return;
+
+ g_pFullFileSystem->Close( (FileHandle_t)file );
+ }
+};
+
+
+class StdIOWriteBinary : public IFileWriteBinary
+{
+public:
+ int create( const char *pFileName )
+ {
+ g_pFullFileSystem->SetFileWritable( pFileName, true, "GAME" );
+ return (int)g_pFullFileSystem->Open( pFileName, "wb" );
+ }
+
+ int write( void *pData, int size, int file )
+ {
+ return g_pFullFileSystem->Write( pData, size, (FileHandle_t)file );
+ }
+
+ void close( int file )
+ {
+ g_pFullFileSystem->Close( (FileHandle_t)file );
+ }
+
+ void seek( int file, int pos )
+ {
+ g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
+ }
+
+ unsigned int tell( int file )
+ {
+ return g_pFullFileSystem->Tell( (FileHandle_t)file );
+ }
+};
+
+static StdIOWriteBinary io_out;
+static StdIOReadBinary io_in;
+
+#define RIFF_WAVE MAKEID('W','A','V','E')
+#define WAVE_FMT MAKEID('f','m','t',' ')
+#define WAVE_DATA MAKEID('d','a','t','a')
+#define WAVE_FACT MAKEID('f','a','c','t')
+#define WAVE_CUE MAKEID('c','u','e',' ')
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &walk -
+//-----------------------------------------------------------------------------
+static void ParseSentence( CSentence& sentence, IterateRIFF &walk )
+{
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ buf.EnsureCapacity( walk.ChunkSize() );
+ walk.ChunkRead( buf.Base() );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
+
+ sentence.InitFromDataChunk( buf.Base(), buf.TellPut() );
+}
+
+bool LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io, void *formatbuffer = NULL, int* formatsize = NULL, int *datasize = NULL )
+{
+ int insize = 0;
+
+ if ( formatsize )
+ {
+ insize = *formatsize;
+ *formatsize = 0;
+ }
+
+ if ( datasize )
+ {
+ *datasize = 0;
+ }
+
+ sentence.Reset();
+
+ InFileRIFF riff( wavfile, io );
+
+ // UNDONE: Don't use printf to handle errors
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ return false;
+ }
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ // This chunk must be first as it contains the wave's format
+ // break out when we've parsed it
+ bool found = false;
+ while ( walk.ChunkAvailable( ) )
+ {
+ switch( walk.ChunkName() )
+ {
+ case WAVE_FMT:
+ {
+ if ( formatbuffer && formatsize )
+ {
+ if ( walk.ChunkSize() <= insize )
+ {
+ *formatsize = walk.ChunkSize();
+ walk.ChunkRead( formatbuffer );
+ }
+ else
+ {
+ Error( "oops, format tag too big!!!" );
+ }
+ }
+ }
+ break;
+ case WAVE_VALVEDATA:
+ {
+ found = true;
+ ParseSentence( sentence, walk );
+ }
+ break;
+ case WAVE_DATA:
+ {
+ if ( datasize )
+ {
+ *datasize = walk.ChunkSize();
+ }
+ }
+ break;
+ }
+ walk.ChunkNext();
+ }
+
+ return true;
+}
+
+bool LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence, void *formatbuffer = NULL, int* formatsize = NULL, int *dataSize = NULL )
+{
+ return LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in, formatbuffer, formatsize, dataSize );
+}
+
+bool ValidateCombinedFileCheckSum( char const *outfilename, char const *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves )
+{
+ CUtlVector< CombinerEntry > work;
+
+ char actualfile[ 512 ];
+ g_pSoundEmitterSystem->GenderExpandString( gender, outfilename, actualfile, sizeof( actualfile ) );
+ if ( Q_strlen( actualfile ) <= 0 )
+ {
+ return false;
+ }
+
+ int i = sorted.FirstInorder();
+ if ( i != sorted.InvalidIndex() )
+ {
+ CChoreoEvent *e = sorted[ i ];
+
+ float startoffset = e->GetStartTime();
+
+ do
+ {
+ e = sorted[ i ];
+
+ float curoffset = e->GetStartTime();
+
+ CombinerEntry ce;
+ Q_snprintf( ce.wavefile, sizeof( ce.wavefile ), "sound/%s", FacePoser_TranslateSoundNameGender( e->GetParameters(), gender ) );
+ ce.startoffset = curoffset - startoffset;
+
+ work.AddToTail( ce );
+
+ i = sorted.NextInorder( i );
+ }
+ while ( i != sorted.InvalidIndex() );
+
+ int c = work.Count();
+
+ char worklist[ 2048 ];
+
+ worklist[ 0 ] = 0;
+
+ for ( i = 0; i < c; ++i )
+ {
+ CombinerEntry &item = work[ i ];
+ Q_strncat( worklist, item.wavefile, sizeof( worklist ), COPY_ALL_CHARACTERS );
+ if ( i != c - 1 )
+ {
+ Q_strncat( worklist, ", ", sizeof( worklist ), COPY_ALL_CHARACTERS );
+ }
+ }
+
+ logprint( "cc_combined.txt", "combined .wav '%s': %s\n", actualfile, worklist );
+ }
+
+ bool valid = soundcombiner->IsCombinedFileChecksumValid( g_pFullFileSystem, actualfile, work );
+ if ( !valid )
+ {
+ vprint( 0, "combined file (%s) checksum mismatch for '%s'\n event '%s' of scene '%s'\n", actualfile, cctoken,
+ sorted[0]->GetName(), sorted[0]->GetScene()->GetFilename() );
+
+ if ( regenerate )
+ {
+ bool dothisfile = false;
+ if ( !regenerate_all )
+ {
+ vprint( 0, "Regenerate '%s'? (Yes/No/All)", actualfile );
+ char ch = getch();
+ if ( ch == 'y' || ch == 'Y' )
+ {
+ dothisfile = true;
+ }
+ if ( ch == 'a' || ch == 'A' )
+ {
+ regenerate_all = true;
+ dothisfile = true;
+ }
+ vprint( 0, "\n" );
+ }
+ else
+ {
+ dothisfile = true;
+ }
+
+ if ( dothisfile )
+ {
+ bool success = soundcombiner->CombineSoundFiles( g_pFullFileSystem, actualfile, work );
+ vprint( 1, "%s: %s\n", actualfile, success ? "succeeded" : "FAILED" );
+ }
+ }
+ }
+ else
+ {
+ if ( regenerate )
+ {
+ vprint( 0, "combined file (%s) checksum still matches for %s, skipping rebuild...\n", actualfile, cctoken );
+ }
+ }
+
+ // Mark the file as referenced
+ //
+ char fn[ 512 ];
+ Q_snprintf( fn, sizeof( fn ), "%s%s", gamedir, actualfile );
+
+ _strlwr( fn );
+ Q_FixSlashes( fn );
+ CUtlSymbol sym = g_Analysis.symbols.AddString( fn );
+
+ if ( referencedcaptionwaves.Find( sym ) == referencedcaptionwaves.InvalidIndex() )
+ {
+ referencedcaptionwaves.Insert( sym );
+ }
+
+ return valid;
+}
+
+static bool EventStartTimeLessFunc( CChoreoEvent * const &p1, CChoreoEvent * const &p2 )
+{
+ CChoreoEvent *w1;
+ CChoreoEvent *w2;
+
+ w1 = const_cast< CChoreoEvent * >( p1 );
+ w2 = const_cast< CChoreoEvent * >( p2 );
+
+ return w1->GetStartTime() < w2->GetStartTime();
+}
+
+static bool SymbolLessFunc( const CUtlSymbol & p1, const CUtlSymbol &p2 )
+{
+ if ( Q_stricmp( g_Analysis.symbols.String( p1 ), g_Analysis.symbols.String( p2 ) ) < 0 )
+ return true;
+ return false;
+}
+
+bool ValidateCombinedSoundCheckSum( CChoreoEvent *e, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves )
+{
+ if ( !e || e->GetType() != CChoreoEvent::SPEAK )
+ return false;
+
+ bool genderwildcard = e->IsCombinedUsingGenderToken();
+ char outfilename[ 512 ];
+ Q_memset( outfilename, 0, sizeof( outfilename ) );
+ if ( !e->ComputeCombinedBaseFileName( outfilename, sizeof( outfilename ), genderwildcard ) )
+ {
+ vprint( 0, "Unable to regenerate wav file name for combined sound (%s)\n", e->GetCloseCaptionToken() );
+ return false;
+ }
+
+ bool checksumvalid = false;
+
+ CUtlRBTree< CChoreoEvent * > eventList( 0, 0, EventStartTimeLessFunc );
+
+ if ( !e->GetChannel()->GetSortedCombinedEventList( e->GetCloseCaptionToken(), eventList ) )
+ {
+ vprint( 0, "Unable to generated combined event list (%s)\n", e->GetCloseCaptionToken() );
+ return false;
+ }
+
+
+ if ( genderwildcard )
+ {
+ checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_MALE, eventList, referencedcaptionwaves );
+ checksumvalid &= ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_FEMALE, eventList, referencedcaptionwaves );
+ }
+ else
+ {
+ checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_NONE, eventList, referencedcaptionwaves );
+ }
+
+ return checksumvalid;
+}
+
+struct PerMapVCDS
+{
+ PerMapVCDS()
+ {
+ }
+
+ PerMapVCDS( const PerMapVCDS& src )
+ {
+ int i = src.vcds.FirstInorder();
+ while ( i != src.vcds.InvalidIndex() )
+ {
+ vcds.Insert( src.vcds[ i ] );
+ i = src.vcds.NextInorder( i );
+ }
+ }
+
+ class CTree : public CUtlRBTree< CUtlSymbol >
+ {
+ public:
+ CTree()
+ : CUtlRBTree< CUtlSymbol >( 0, 0, DefLessFunc( CUtlSymbol ) )
+ {
+ }
+
+ CTree &operator=( const CTree &from )
+ {
+ CopyFrom( from );
+ return *this;
+ }
+ };
+
+ CTree vcds;
+};
+
+CUtlDict< PerMapVCDS, int > g_PerMapVCDS;
+CUtlDict< CUtlSymbol, int > g_FirstMapForVCD;
+
+void ParseVCDFilesFromResList( CUtlVector< CUtlSymbol >& vcdsinreslist, char const *resfile )
+{
+ char gd[ 256 ];
+ Q_strncpy( gd, gamedir, sizeof( gd ) );
+ Q_StripTrailingSlash( gd );
+ _strlwr( gd );
+ Q_FixSlashes( gd );
+
+ int gdlen = strlen( gd );
+
+ char resbase[ 512 ];
+ Q_FileBase( resfile, resbase, sizeof( resbase ) );
+
+ int addedStrings = 0;
+ int resourcesConsidered = 0;
+
+ FileHandle_t resfilehandle;
+ resfilehandle = g_pFullFileSystem->Open( resfile, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
+ {
+ // Read in the entire file
+ int length = g_pFullFileSystem->Size(resfilehandle);
+ if ( length > 0 )
+ {
+ char *pStart = (char *)new char[ length + 1 ];
+ if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) )
+ )
+ {
+ pStart[ length ] = 0;
+
+ char *pFileList = pStart;
+
+ char tokenFile[512];
+
+ while ( 1 )
+ {
+ pFileList = ParseFile( pFileList, tokenFile, NULL );
+ if ( !pFileList )
+ break;
+
+ if ( strlen( tokenFile ) > 0 )
+ {
+ char szFileName[ 256 ];
+ Q_strncpy( szFileName, tokenFile, sizeof( szFileName ) );
+ _strlwr( szFileName );
+ Q_FixSlashes( szFileName );
+ while ( szFileName[ strlen( szFileName ) - 1 ] == '\n' ||
+ szFileName[ strlen( szFileName ) - 1 ] == '\r' )
+ {
+ szFileName[ strlen( szFileName ) - 1 ] = 0;
+ }
+
+ char *pFile = szFileName;
+ if ( !Q_strnicmp( szFileName, gd, gdlen ) )
+ {
+ pFile = szFileName + gdlen + 1;
+ }
+ else
+ {
+ // Ack
+ //vprint( 1, "File %s not under game directory but in reslist, skipping!!!\n", szFileName );
+ pFileList = ParseFile( pFileList, tokenFile, NULL );
+ continue;
+ }
+
+ ++resourcesConsidered;
+
+ // Is it a .vcd?
+ if ( !Q_stristr( pFile, ".vcd" ) )
+ continue;
+
+ char symname[ 512 ];
+ Q_snprintf( symname, sizeof( symname ), "%s%s", gamedir, pFile );
+ _strlwr( symname );
+ Q_FixSlashes( symname );
+
+ CUtlSymbol sym = g_Analysis.symbols.AddString( symname );
+
+ int idx = vcdsinreslist.Find( sym );
+ if ( idx == vcdsinreslist.InvalidIndex() )
+ {
+ ++addedStrings;
+
+ // This is the first time this vcd was encountered, remember which map we are in
+ PerMapVCDS e;
+ e.vcds.Insert( sym );
+ g_PerMapVCDS.Insert( resbase, e );
+
+ CUtlSymbol mapsym = g_Analysis.symbols.AddString( resbase );
+ g_FirstMapForVCD.Insert( symname, mapsym );
+
+ vcdsinreslist.AddToTail( sym );
+ }
+ }
+ }
+
+ }
+ delete[] pStart;
+ }
+
+ g_pFullFileSystem->Close(resfilehandle);
+ }
+
+// int filesFound = addedStrings;
+// vprint( 1, "\rFound %i new resources (%7i total) in %64s", filesFound, resourcesConsidered, resfile );
+}
+
+#define MAPLIST_FILE "maplist.txt"
+
+void AddFileToList( CUtlVector< CUtlSymbol >& list, char const *filename )
+{
+ char fn[ 512 ];
+ Q_strncpy( fn, filename, sizeof( fn ) );
+ _strlwr( fn );
+ Q_FixSlashes( fn );
+ CUtlSymbol sym = g_Analysis.symbols.AddString( fn );
+ list.AddToTail( sym );
+}
+
+void BuildVCDAndMapNameListsFromReslists( CUtlVector< CUtlSymbol >& vcdsinreslist )
+{
+ // Load all .rst files in the reslists folder
+ CUtlVector< CUtlSymbol > reslists;
+
+ // If maplist.txt exists, use it, otherwise
+ bool loaded = false;
+
+ if ( g_pFullFileSystem->FileExists( MAPLIST_FILE ) )
+ {
+ // Parse the true list from the maplist.txt file
+ // and add engine.lst and all.lst at the very end
+
+ // Load them in
+ FileHandle_t resfilehandle;
+ resfilehandle = g_pFullFileSystem->Open( MAPLIST_FILE, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
+ {
+ // Read in and parse mapcycle.txt
+ int length = g_pFullFileSystem->Size(resfilehandle);
+ if ( length > 0 )
+ {
+ char *pStart = (char *)new char[ length + 1 ];
+ if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) )
+ )
+ {
+ pStart[ length ] = 0;
+ const char *pFileList = pStart;
+
+ while ( 1 )
+ {
+ char szMap[ 512 ];
+
+ pFileList = ParseFile( pFileList, com_token, NULL );
+
+ if ( strlen( com_token ) <= 0 )
+ break;
+
+ Q_strncpy(szMap, com_token, sizeof(szMap));
+
+ // Any more tokens on this line?
+ //while ( TokenWaiting( pFileList ) )
+ //{
+ // pFileList = ParseFile( pFileList, com_token, NULL );
+ //}
+
+ char fn[ 512 ];
+ Q_snprintf( fn, sizeof( fn ), "%sreslists/%s.lst", gamedir, szMap );
+
+ AddFileToList( reslists, fn );
+ }
+ }
+ delete[] pStart;
+
+ AddFileToList( reslists, va( "%sreslists/engine.lst", gamedir ) );
+ AddFileToList( reslists, va( "%sreslists/all.lst", gamedir ) );
+
+ loaded = true;
+ }
+
+ g_pFullFileSystem->Close(resfilehandle);
+ }
+ }
+
+
+ if ( !loaded )
+ {
+ char reslistdir[ 512 ];
+ Q_snprintf( reslistdir, sizeof( reslistdir ), "%sreslists", gamedir );
+
+ BuildFileList_R( reslists, reslistdir, ".lst" );
+ }
+
+ int c = reslists.Count();
+
+ StartPacifier( "ParseVCDFilesFromResList: " );
+
+ for ( int i = 0; i < c; ++i )
+ {
+ UpdatePacifier( (float)( i + 1 ) / (float)c );
+ ParseVCDFilesFromResList( vcdsinreslist, g_Analysis.symbols.String( reslists[ i ] ) );
+ }
+
+ EndPacifier( true );
+}
+
+void CheckUnusedVcds( CUtlVector< CUtlSymbol >& vcdsinreslist, CUtlVector< CUtlSymbol >& vcdfiles )
+{
+ vprint( 1, "Checking for orphaned vcd files\n" );
+
+ // For each reslist, load in the filenames, looking for .vcds
+ vprint( 1, "Found %i .vcd files referenced (%i total)\n", vcdsinreslist.Count(), vcdfiles.Count() );
+
+ // For each vcd in the min list, see if it's in the sublist
+ int i;
+ int c = vcdfiles.Count();
+
+ int invalid_index = vcdsinreslist.InvalidIndex();
+
+ int unrefcount = 0;
+
+ for ( i = 0; i < c; ++i )
+ {
+ CUtlSymbol& sym = vcdfiles[ i ];
+
+ if ( vcdsinreslist.Find( sym ) == invalid_index )
+ {
+ ++unrefcount;
+ vprint( 1, " unref .vcd: %s\n", g_Analysis.symbols.String( sym ) );
+
+ if ( nuke )
+ {
+ nuke_print( 0, "del %s /f\n", g_Analysis.symbols.String( sym ) );
+ }
+ }
+ }
+
+ // For each reslist, load in the filenames, looking for .vcds
+ vprint( 1, "Found %i unreferenced vcds (%i total)\n", unrefcount, vcdfiles.Count() );
+
+}
+
+void ParseUsedSoundsFromSndFile( CUtlRBTree< int, int >& usedsounds, char const *sndfile )
+{
+ char gd[ 256 ];
+ Q_strncpy( gd, gamedir, sizeof( gd ) );
+ Q_StripTrailingSlash( gd );
+ _strlwr( gd );
+ Q_FixSlashes( gd );
+
+ int addedStrings = 0;
+ int resourcesConsidered = 0;
+
+ FileHandle_t resfilehandle;
+ resfilehandle = g_pFullFileSystem->Open( sndfile, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
+ {
+ // Read in the entire file
+ int length = g_pFullFileSystem->Size(resfilehandle);
+ if ( length > 0 )
+ {
+ char *pStart = (char *)new char[ length + 1 ];
+ if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) )
+ )
+ {
+ pStart[ length ] = 0;
+
+ char *pFileList = pStart;
+
+ char tokenFile[512];
+
+ while ( 1 )
+ {
+ pFileList = ParseFile( pFileList, tokenFile, NULL );
+ if ( !pFileList )
+ break;
+
+ if ( strlen( tokenFile ) > 0 )
+ {
+ char soundname[ 256 ];
+ Q_strncpy( soundname, tokenFile, sizeof( soundname ) );
+ _strlwr( soundname );
+
+ ++resourcesConsidered;
+
+ int index = g_pSoundEmitterSystem->GetSoundIndex( soundname );
+ if ( !g_pSoundEmitterSystem->IsValidIndex( index ) )
+ {
+ vprint( 1, "---> Sound %s doesn't exist in g_pSoundEmitterSystemsystem!!!\n", soundname );
+ continue;
+ }
+
+ int idx = usedsounds.Find( index );
+ if ( idx == usedsounds.InvalidIndex() )
+ {
+ ++addedStrings;
+ usedsounds.Insert( index );
+ }
+ }
+ }
+
+ }
+ delete[] pStart;
+ }
+
+ g_pFullFileSystem->Close(resfilehandle);
+ }
+
+ vprint( 1, "Found %i new resources (%i total) in %s\n", addedStrings, resourcesConsidered, sndfile );
+}
+
+bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args );
+
+void SpewDuplicatedText( char const *lang, const char *entry, const wchar_t *str )
+{
+ const wchar_t *curpos = str;
+
+ wchar_t cleaned[ 4096 ];
+
+ wchar_t *out = cleaned;
+
+ for ( ; curpos && *curpos != L'\0'; ++curpos )
+ {
+ wchar_t cmd[ 256 ];
+ wchar_t args[ 256 ];
+
+ if ( SplitCommand( &curpos, cmd, args ) )
+ {
+ continue;
+ }
+
+ // Only copy non command, non-whitespace characters
+ if ( iswspace( *curpos ) )
+ {
+ continue;
+ }
+
+ *out++ = *curpos;
+ }
+
+ *out = L'\0';
+
+ int len = wcslen( cleaned );
+ if ( len < 5 )
+ return;
+
+ // Now see how many characters from the first 50% of the text are also in the second 50%
+ int halflen = len / 2;
+
+ int foundcount = 0;
+ for ( int i = 0; i < halflen; ++i )
+ {
+ wchar_t ch[3];
+ ch[0] = cleaned[ i ];
+ ch[1] = cleaned[ i + 1 ];
+ ch[2] = L'\0';
+
+ if ( wcsstr( &cleaned[ halflen ], ch ) )
+ {
+ ++foundcount;
+ }
+ }
+
+ if ( foundcount > 0.7 * halflen )
+ {
+ logprint( "cc_duplicatedtext.txt", "%s: Suspect token %s\n", lang, entry );
+ }
+}
+
+void CheckDuplcatedText( void )
+{
+ g_pFullFileSystem->RemoveFile( "cc_duplicatedtext.txt", "GAME" );
+
+ for ( int lang = 0; lang < CC_NUM_LANGUAGES; ++lang )
+ {
+ char language[ 256 ];
+ Q_strncpy( language, CSentence::NameForLanguage( lang ), sizeof( language ) );
+
+ vprint( 0, "adding langauge file for '%s'\n", language );
+
+ g_pVGuiLocalize->AddFile( "resource/closecaption_english.txt" );
+
+ char fn[ 256 ];
+ Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", language );
+
+ g_pVGuiLocalize->AddFile( fn );
+
+ // Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them
+ StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex();
+ while ( str != INVALID_LOCALIZE_STRING_INDEX )
+ {
+ char const *keyname = g_pVGuiLocalize->GetNameByIndex( str );
+ if ( keyname )
+ {
+ const wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str );
+
+ SpewDuplicatedText( language, keyname, value );
+ }
+
+ str = g_pVGuiLocalize->GetNextStringIndex( str );
+ }
+ }
+}
+
+static bool IsAllSpaces( const wchar_t *stream )
+{
+ const wchar_t *p = stream;
+ while ( *p != L'\0' )
+ {
+ if ( !iswspace( *p ) )
+ return false;
+
+ p++;
+ }
+
+ return true;
+}
+
+void SpewEnglishText( const wchar_t *str )
+{
+ const wchar_t *curpos = str;
+
+ wchar_t cleaned[ 4096 ];
+
+ wchar_t *out = cleaned;
+
+ for ( ; curpos && *curpos != L'\0'; ++curpos )
+ {
+ wchar_t cmd[ 256 ];
+ wchar_t args[ 256 ];
+
+ if ( SplitCommand( &curpos, cmd, args ) )
+ {
+ continue;
+ }
+
+ *out++ = *curpos;
+ }
+
+ *out = L'\0';
+
+ if ( IsAllSpaces( cleaned ) )
+ return;
+
+ char ansi[ 4096 ];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( cleaned, ansi, sizeof( ansi ) );
+
+ logprint( "cc_english.txt", "\"%s\"\n", ansi );
+}
+
+void ExtractEnglish()
+{
+ g_pFullFileSystem->RemoveFile( "cc_english.txt", "GAME" );
+ // Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them
+ StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex();
+ while ( str != INVALID_LOCALIZE_STRING_INDEX )
+ {
+ char const *keyname = g_pVGuiLocalize->GetNameByIndex( str );
+ if ( keyname )
+ {
+ const wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str );
+
+ SpewEnglishText( value );
+ }
+
+ str = g_pVGuiLocalize->GetNextStringIndex( str );
+ }
+}
+
+void CheckUnusedSounds()
+{
+ vprint( 1, "Checking for unused sounds.txt entries\n" );
+
+ CUtlRBTree< int, int > usedsounds( 0, 0, DefLessFunc(int) );
+
+ // Load all .snd files in the reslists folder
+ CUtlVector< CUtlSymbol > sndlists;
+
+ char reslistdir[ 512 ];
+ Q_snprintf( reslistdir, sizeof( reslistdir ), "%sreslists", gamedir );
+
+ BuildFileList_R( sndlists, reslistdir, ".snd" );
+
+ int c = sndlists.Count();
+
+ for ( int i = 0; i < c; ++i )
+ {
+ ParseUsedSoundsFromSndFile( usedsounds, g_Analysis.symbols.String( sndlists[ i ] ) );
+ }
+
+ // For each reslist, load in the filenames, looking for .vcds
+ vprint( 1, "Found %i unique sounds referenced\n", usedsounds.Count() );
+
+ // For each vcd in the min list, see if it's in the sublist
+ c = g_pSoundEmitterSystem->GetSoundCount();
+
+ int unrefcount = 0;
+
+ int invalidindex = usedsounds.InvalidIndex();
+
+ CUtlRBTree< int, int > usedscripts( 0, 0, DefLessFunc(int) );
+
+ for ( int i = 0; i < c; ++i )
+ {
+ int slot = usedsounds.Find( i );
+ if ( invalidindex == slot )
+ {
+ ++unrefcount;
+ char const *soundname = g_pSoundEmitterSystem->GetSoundName( i );
+
+ vprint( 1, " unref: %s : %s\n", soundname, g_pSoundEmitterSystem->GetSourceFileForSound( i ) );
+ }
+ else
+ {
+ int scriptindex = g_pSoundEmitterSystem->FindSoundScript( g_pSoundEmitterSystem->GetSourceFileForSound( i ) );
+ if ( scriptindex != -1 )
+ {
+ slot = usedscripts.Find( scriptindex );
+ if ( usedscripts.InvalidIndex() == slot )
+ {
+ usedscripts.Insert( scriptindex );
+ }
+ }
+ }
+ }
+
+ // For each reslist, load in the filenames, looking for .vcds
+ vprint( 1, "Found %i unreferenced sounds (%i total)\n", unrefcount, c );
+
+ c = g_pSoundEmitterSystem->GetNumSoundScripts();
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *scriptname = g_pSoundEmitterSystem->GetSoundScriptName( i );
+
+ int slot = usedscripts.Find( i );
+ if ( usedscripts.InvalidIndex() == slot )
+ {
+ vprint( 1, " No sounds fron script %s are being used, should delete from manifest!!!\n", scriptname );
+ }
+ }
+
+ if ( !build_cc )
+ return;
+
+ g_pFullFileSystem->RemoveFile( "fixed.txt", "GAME" );
+ g_pFullFileSystem->RemoveFile( "fixed2.txt", "GAME" );
+ g_pFullFileSystem->RemoveFile( "todo.csv", "GAME" );
+ g_pFullFileSystem->RemoveFile( "cc_add.txt", "GAME" );
+ g_pFullFileSystem->RemoveFile( "cc_delete.txt", "GAME" );
+ g_pFullFileSystem->RemoveFile( "cc_foundphonemes.txt", "GAME" );
+ g_pFullFileSystem->RemoveFile( "cc_combined.txt", "GAME" );
+
+ logprint( "todo.csv", "\"CC_TOKEN\",\"TEXT\",\"WAVE FILE\"\n" );
+
+ // Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them
+ StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex();
+ while ( str != INVALID_LOCALIZE_STRING_INDEX )
+ {
+ char const *keyname = g_pVGuiLocalize->GetNameByIndex( str );
+
+ if ( keyname )
+ {
+ wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str );
+
+ char ansi[ 512 ];
+ g_pVGuiLocalize->ConvertUnicodeToANSI( value, ansi, sizeof( ansi ) );
+
+ // See if key exists in g_pSoundEmitterSystem system
+ int soundindex = g_pSoundEmitterSystem->GetSoundIndex( keyname );
+ if( soundindex == -1 )
+ {
+ vprint( 1, " cc token %s not in current g_pSoundEmitterSystem scripts\n", keyname );
+
+ // Just write it back out as is...
+ logprint( "fixed2.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
+ }
+ else
+ {
+ // See if it's referenced
+ int slot = usedsounds.Find( soundindex );
+ if ( usedsounds.InvalidIndex() == slot )
+ {
+ vprint( 1, " cc token %s exists, but the sound is not used by the game\n", keyname );
+
+ logprint( "cc_delete.txt", "\"%s\"\n", keyname );
+ }
+ else
+ {
+ // Now try to find a better bit of text
+ CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex );
+ if ( internal && internal->NumSoundNames() > 0 )
+ {
+ CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
+ char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave );
+ if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) )
+ {
+ // See if 1) it's marked as !!! and try to figure out the text from .wav files...
+ if ( !Q_strnicmp( ansi, "!!!", 3 ) )
+ {
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
+ {
+ if ( Q_strlen( sentence.GetText() ) > 0 )
+ {
+ Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() );
+ cleanquotes( ansi );
+
+ logprint( "cc_foundphonemes.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
+ }
+ }
+ }
+
+ logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
+
+ for ( int w = 0; w < internal->NumSoundNames() ; ++w )
+ {
+ wavname = g_pSoundEmitterSystem->GetWaveName( internal->GetSoundNames()[ w ].symbol );
+ logprint( "todo.csv", "\"%s\",\"%s\",\"%s\"\n",
+ keyname, ansi, va( "sound/%s", PSkipSoundChars( wavname ) ) );
+ }
+ }
+ }
+ else
+ {
+ logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
+ }
+ }
+ }
+ }
+ str = g_pVGuiLocalize->GetNextStringIndex( str );
+ }
+
+ // Now walk through all of the sounds that were used, but not in the localization file and and those, too
+ c = g_pSoundEmitterSystem->GetSoundCount();
+ for ( int i = 0; i < c; ++i )
+ {
+ int slot = usedsounds.Find( i );
+ if ( usedsounds.InvalidIndex() == slot )
+ continue;
+
+ char const *soundname = g_pSoundEmitterSystem->GetSoundName( i );
+
+ // See if it exists in the localization file
+ wchar_t *text = g_pVGuiLocalize->Find( soundname );
+ if ( text )
+ {
+ continue;
+ }
+ else
+ {
+ char ansi[ 512 ];
+ Q_snprintf( ansi, sizeof( ansi ), "!!!%s", soundname );
+
+ // Now try to find a better bit of text
+ CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( i );
+ if ( internal && internal->NumSoundNames() > 0 )
+ {
+ CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
+ char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave );
+ if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) )
+ {
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
+ {
+ if ( Q_strlen( sentence.GetText() ) > 0 )
+ {
+ Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() );
+ cleanquotes( ansi );
+ }
+ }
+
+ // Add an entry for stuff in vo/
+ logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", soundname, ansi );
+
+ for ( int w = 0; w < internal->NumSoundNames() ; ++w )
+ {
+ wavname = g_pSoundEmitterSystem->GetWaveName( internal->GetSoundNames()[ w ].symbol );
+ logprint( "todo.csv", "\"%s\",\"%s\",\"%s\"\n",
+ soundname, ansi, va( "sound/%s", PSkipSoundChars( wavname ) ) );
+ }
+
+ logprint( "cc_add.txt", "\"%s\"\n", soundname );
+ }
+ }
+ }
+ }
+}
+
+// Removes commas from text
+void RemoveCommas( char *in )
+{
+ char *out = in;
+ while ( out && *out )
+ {
+ if ( *in == ',' )
+ {
+ *out++ = ';';
+ in++;
+ }
+ else
+ {
+ *out++ = *in++;
+ }
+ }
+ *out = 0;
+}
+
+void SpewScript( char const *vcdname, CUtlRBTree< CChoreoEvent *, int >& list )
+{
+ if ( !build_script )
+ return;
+
+ if ( list.Count() == 0 )
+ return;
+
+ logprint( "script.txt", "VCD( %s )\n\n", vcdname );
+
+ for ( int i = list.FirstInorder(); i != list.InvalidIndex(); i = list.NextInorder( i ) )
+ {
+ CChoreoEvent *e = list[ i ];
+
+ if ( e->GetCloseCaptionType() != CChoreoEvent::CC_MASTER )
+ {
+ continue;
+ }
+
+ char actorname[ 512 ];
+
+ if ( e->GetActor() )
+ {
+ Q_strncpy( actorname, e->GetActor()->GetName(), sizeof( actorname ) );
+ _strupr( actorname );
+ }
+ else
+ {
+ Q_strncpy( actorname, "(NULL ACTOR)", sizeof( actorname ) );
+
+ }
+
+ logprint( "script.txt", "\t\t\t%s\n", actorname);
+
+
+ // Now try to find a better bit of text
+
+ char wavname[ 512 ];
+ wavname[ 0 ] = 0;
+
+ char sentence_text[ 1024 ];
+ sentence_text[ 0 ] = 0;
+
+ int soundindex = g_pSoundEmitterSystem->GetSoundIndex( e->GetParameters() );
+ if ( soundindex != -1 )
+ {
+ CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex );
+ if ( internal && internal->NumSoundNames() > 0 )
+ {
+ CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
+ char const *pname = g_pSoundEmitterSystem->GetWaveName( symwave );
+ if ( pname && ( Q_stristr( pname, "vo/" ) || Q_stristr( pname, "vo\\" ) || Q_stristr( pname, "combined" ) ) )
+ {
+ Q_strncpy( wavname, pname, sizeof( wavname ) );
+
+ // Convert to regular text
+ logprint( "script.txt", "\t\t\t\twav(%s)\n", wavname );
+
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
+ {
+ if ( Q_strlen( sentence.GetText() ) > 0 )
+ {
+ Q_snprintf( sentence_text, sizeof( sentence_text ), "%s", sentence.GetText() );
+ cleanquotes( sentence_text );
+ }
+ }
+ }
+ }
+ }
+
+ char tok[ 256 ];
+ Q_strncpy( tok, e->GetParameters(), sizeof( tok ) );
+
+ char ansi[ 2048 ];
+ ansi[ 0 ] = 0;
+
+ wchar_t *str = g_pVGuiLocalize->Find( tok );
+ if ( !str )
+ {
+ logprint( "script.txt", "\t\tMissing token '%s' event '%s'\n\n", tok, e->GetName() );
+ Q_snprintf( ansi, sizeof( ansi ), "missing '%s' for '%s'", tok, e->GetName() );
+ }
+ else
+ {
+ if ( !wcsncmp( str, L"!!!", wcslen( L"!!!" ) ) )
+ {
+ logprint( "script.txt", "\t\t'%s': event '%s'\n\n", tok, e->GetName() );
+ Q_snprintf( ansi, sizeof( ansi ), "!!! '%s' for '%s'", tok, e->GetName() );
+ }
+ else
+ {
+ g_pVGuiLocalize->ConvertUnicodeToANSI( str, ansi, sizeof( ansi ) );
+
+ // Convert to regular text
+ logprint( "script.txt", "\t\t\t\tcc_token(%s)\n\n\t\t\"%s\"\n\n", tok, ansi );
+ }
+ }
+
+ // Now spit out the CSV version...
+ RemoveCommas( actorname );
+ RemoveCommas( tok );
+ RemoveCommas( ansi );
+
+ char mapname[ 512 ];
+
+ mapname[ 0 ] = 0;
+
+ int idx = g_FirstMapForVCD.Find( vcdname );
+ if ( idx != g_FirstMapForVCD.InvalidIndex() )
+ {
+ Q_strncpy( mapname, g_Analysis.symbols.String( g_FirstMapForVCD[ idx ] ), sizeof( mapname ) );
+ }
+
+ static unsigned int sortindex = 0;
+
+ logprint( "script.csv", "%u,%s,%s,%s,%6.3f,%s,%s,\"%s\",\"%s\"\n",
+ sortindex++, mapname, vcdname, actorname, e->GetStartTime(), tok, wavname, ansi, sentence_text );
+ }
+}
+
+void CheckLocalizationEntries( CUtlVector< CUtlSymbol >& vcdfiles, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves )
+{
+ int disabledcount = 0;
+ int validcount = 0;
+ int missingcount = 0;
+ int wavfile = 0;
+
+ int gamedirskip = Q_strlen( gamedir );
+
+ if ( build_script )
+ {
+ g_pFullFileSystem->RemoveFile( "script.txt", "GAME" );
+ g_pFullFileSystem->RemoveFile( "script.csv", "GAME" );
+ }
+
+ int c = vcdfiles.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ CUtlSymbol& vcdname = vcdfiles[ i ];
+
+ CUtlRBTree< CChoreoEvent *, int > sortedSpeakEvents( 0, 0, EventStartTimeLessFunc );
+
+
+ // Load the .vcd
+ char fullname[ 512 ];
+ Q_snprintf( fullname, sizeof( fullname ), "%s", g_Analysis.symbols.String( vcdname ) );
+
+ LoadScriptFile( fullname );
+
+ CChoreoScene *scene = ChoreoLoadScene( fullname, NULL, &g_TokenProcessor, Con_Printf );
+ if ( !scene )
+ {
+ vprint( 0, "Warning: Unable to load %s\n", fullname );
+ continue;
+ }
+
+ // Now iterate the events looking for speak events
+ int numevents = scene->GetNumEvents();
+ for ( int j = 0; j < numevents; j++ )
+ {
+ CChoreoEvent *e = scene->GetEvent( j );
+ if ( e->GetType() != CChoreoEvent::SPEAK )
+ continue;
+
+ if ( e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED )
+ {
+ ++disabledcount;
+ continue;
+ }
+
+ if ( build_script )
+ {
+ if ( sortedSpeakEvents.Find( e ) == sortedSpeakEvents.InvalidIndex() )
+ {
+ sortedSpeakEvents.Insert( e );
+ }
+ }
+
+ char tok[ 256 ];
+
+ for ( int pass = 0; pass <= 1; ++pass )
+ {
+ bool iscombined = false;
+
+ if ( pass == 0 )
+ {
+ Q_strncpy( tok, e->GetParameters(), sizeof( tok ) );
+ }
+ else
+ {
+ if ( e->GetCloseCaptionType() != CChoreoEvent::CC_MASTER )
+ continue;
+
+ if ( e->GetNumSlaves() <= 0 )
+ continue;
+
+ if ( !e->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) )
+ {
+ ++missingcount;
+ continue;;
+ }
+
+ iscombined = true;
+ }
+
+ // Look it up
+ wchar_t *str = g_pVGuiLocalize->Find( tok );
+ if ( !str )
+ {
+ char fn[ 256 ];
+ //Q_FileBase( g_Analysis.symbols.String( vcdname ), fn, sizeof( fn ) );
+ Q_strncpy( fn, &g_Analysis.symbols.String( vcdname )[ gamedirskip ], sizeof( fn ) );
+
+
+ if ( Q_stristr( tok, ".wav" ) )
+ {
+ if ( verbose )
+ {
+ if ( !regenerate_quiet )
+ {
+ vprint( 0, "(OBSOLETE???)missing cc token '%s' (!.wav file): vcd (%s), event (%s)\n",
+ tok, fn, e->GetName() );
+ }
+ }
+
+ ++wavfile;
+ }
+ else
+ {
+ if ( !regenerate_quiet )
+ {
+ vprint( 0, "missing %s cc token '%s': vcd (%s), event (%s)\n",
+ pass == 0 ? "normal" : "combined",
+ tok,
+ fn,
+ e->GetName() );
+ }
+
+ // Add the "!!!entry" to a temp file
+ if ( verbose )
+ {
+ char suggested[ 4096 ];
+ Q_snprintf( suggested, sizeof( suggested ), "!!!%s", tok );
+
+ int soundindex = g_pSoundEmitterSystem->GetSoundIndex( tok );
+ if ( soundindex != -1 )
+ {
+ // Now try to find a better bit of text
+ CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex );
+ if ( internal && internal->NumSoundNames() > 0 )
+ {
+ CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
+ char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave );
+ if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) )
+ {
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
+ {
+ if ( Q_strlen( sentence.GetText() ) > 0 )
+ {
+ Q_snprintf( suggested, sizeof( suggested ), "%s", sentence.GetText() );
+ cleanquotes( suggested );
+ }
+ }
+ }
+ }
+ }
+
+ logprint( "missing.txt", "\t\"%s\"\t\t\"%s\"\n", tok, suggested );
+ }
+
+ ++missingcount;
+ }
+
+
+ }
+ else
+ {
+ if ( verbose )
+ {
+ if ( !wcsncmp( str, L"!!!", wcslen( L"!!!" ) ) )
+ {
+ if ( !regenerate_quiet )
+ {
+ vprint( 0, "Autogenerated closecaption token '%s' not edited\n", tok );
+ }
+ }
+ }
+ ++validcount;
+ }
+
+ // Verify checksum
+ if ( iscombined )
+ {
+ ValidateCombinedSoundCheckSum( e, referencedcaptionwaves );
+ }
+ }
+ }
+
+ SpewScript( fullname, sortedSpeakEvents );
+ sortedSpeakEvents.RemoveAll();
+
+ delete scene;
+ }
+
+ int total = validcount + missingcount + wavfile + disabledcount;
+ if ( total != 0 )
+ {
+ vprint( 0, "\n%.2f %%%% invalid (%i valid, %i missing, %i wavfile(OBSOLETE), %i disabled - total %i)\n",
+ 100.0f * (float)missingcount / (float)total,
+ validcount,
+ missingcount,
+ wavfile,
+ disabledcount,
+ total );
+ }
+}
+
+void CheckForOrphanedCombinedWavs( CUtlVector< CUtlSymbol >& diskwaves, CUtlRBTree< CUtlSymbol, int >& captionsused )
+{
+ if ( g_pFullFileSystem->FileExists( "orphaned.bat", "GAME" ) )
+ {
+ g_pFullFileSystem->RemoveFile( "orphaned.bat", "GAME" );
+ }
+
+ int orphans = 0;
+ int c = diskwaves.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ CUtlSymbol &sym = diskwaves[ i ];
+
+ if ( captionsused.Find( sym ) != captionsused.InvalidIndex() )
+ continue;
+
+ char fn[ 256 ];
+ Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
+
+ vprint( 1, "Orphaned wav file '%s'\n", fn );
+
+ logprint( "orphaned.bat", "del \"%s\" /f\n", fn );
+
+ ++orphans;
+ }
+
+ if ( orphans != 0 )
+ {
+ vprint( 0, "\n%.2f %%%% (%i/%i), orphaned combined .wav files in sound/combined/... folder\n",
+ 100.0f * (float)orphans / (float)c,
+ orphans, c );
+
+ vprint( 0, "created orphaned.bat file\n" );
+ }
+ else
+ {
+ vprint( 0, "\nNo orphaned files found among %d possible disk waves\n", c );
+ }
+}
+
+float GetWaveDuration( char const *wavname )
+{
+ if ( !g_pFullFileSystem->FileExists( wavname ) )
+ {
+ return 0.0f;
+ }
+
+ CAudioSource *wave = sound->LoadSound( wavname );
+ if ( !wave )
+ {
+ //vprint( 0, "unable to load %s\n", wavname );
+ return 0.0f;
+ }
+
+ CAudioMixer *pMixer = wave->CreateMixer();
+ if ( !pMixer )
+ {
+ vprint( 0, "unable to create mixer for %s\n", wavname );
+ delete wave;
+ return 0.0f;
+ }
+
+ float duration = wave->GetRunningLength();
+ return duration;
+}
+
+void GetWaveSentence( char const *wavname, CSentence& sentence )
+{
+ sentence.Reset();
+ if ( !g_pFullFileSystem->FileExists( wavname ) )
+ {
+ return;
+ }
+
+ CAudioSource *wave = sound->LoadSound( wavname );
+ if ( !wave )
+ {
+ //vprint( 0, "unable to load %s\n", wavname );
+ return;
+ }
+
+ sentence = *wave->GetSentence();
+}
+
+void ValidateForeignLanguageWaves( char const *language, CUtlVector< CUtlSymbol >& combinedwavfiles )
+{
+ // Need to compute the gamedir to the specified language
+ char langdir[ 512 ];
+ char strippedgamedir[ 512 ];
+ Q_strncpy( langdir, gamedir, sizeof( langdir ) );
+
+ Q_StripTrailingSlash( langdir );
+
+ Q_strncpy( strippedgamedir, langdir, sizeof( strippedgamedir ) );
+
+ Q_strcat( langdir, "_", sizeof(langdir) );
+ Q_strcat( langdir, language, sizeof(langdir) );
+
+ int skipchars = Q_strlen( strippedgamedir );
+
+ // Need to add this to the file system
+ int missing = 0;
+ int outdated = 0;
+
+ int c = combinedwavfiles.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ CUtlSymbol& sym = combinedwavfiles[ i ];
+
+ char wavname[ 512 ];
+ Q_strncpy( wavname, g_Analysis.symbols.String( sym ), sizeof( wavname ) );
+
+ // Now get language specific wav name
+ char localizedwavename[ 512 ];
+ Q_snprintf( localizedwavename, sizeof( localizedwavename ), "%s%s",
+ langdir,
+ &wavname[ skipchars ] );
+
+ float duration_english = GetWaveDuration( wavname );
+ if ( !duration_english )
+ {
+ continue;
+ }
+ // Now see if the localized file exists
+
+ float duration_localized = GetWaveDuration( localizedwavename );
+ if ( !duration_localized )
+ {
+ ++missing;
+ vprint( 0, "Missing localized file %s\n", localizedwavename );
+ continue;
+ }
+
+ CSentence sentence_english;
+ GetWaveSentence( wavname, sentence_english );
+ CSentence sentence_localized;
+ GetWaveSentence( localizedwavename, sentence_localized );
+
+ if ( sentence_english.GetText() &&
+ sentence_english.GetText()[0] )
+ {
+ if ( !sentence_localized.GetText() || !sentence_localized.GetText()[0] )
+ {
+ vprint( 0, "--> Localized combined file for '%s' doesn't have sentence data '%s'\n",
+ language, localizedwavename );
+ }
+ else if ( !Q_stricmp( sentence_english.GetText(), sentence_localized.GetText()) )
+ {
+ vprint( 0, "--> Localized combined file for '%s' still using english phoneme and text data '%s'\n",
+ language, localizedwavename );
+ }
+ }
+
+ if ( fabs( duration_localized - duration_english ) > SOUND_DURATION_TOLERANCE )
+ {
+ ++outdated;
+ vprint( 0, "--> Mismatched localized file %s (english %.2f s./%s %.2f s.)\n", localizedwavename,
+ duration_english, language, duration_localized );
+ continue;
+ }
+ }
+
+ if ( c != 0 )
+ {
+ vprint( 0, "%.2f %%%% missing(%i)+outdated(%i)/total(%i), combined .wav files in %s closecaption/ folder\n",
+ 100.0f * (float)(missing + outdated ) / (float)c,
+ missing, outdated, c, language );
+ }
+}
+
+void BuildReverseSoundLookup( CUtlDict< CUtlSymbol, int >& wavtosound )
+{
+ // Build a dictionary of wav names to sound names
+ int c = g_pSoundEmitterSystem->GetSoundCount();
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *soundname = g_pSoundEmitterSystem->GetSoundName( i );
+
+ CUtlSymbol soundSymbol = g_Analysis.symbols.AddString( soundname );
+
+ CSoundParametersInternal* params = g_pSoundEmitterSystem->InternalGetParametersForSound( i );
+ if ( soundname && params )
+ {
+ int soundcount = params->NumSoundNames();
+ for ( int j = 0; j < soundcount; ++j )
+ {
+ SoundFile& sf = params->GetSoundNames()[ j ];
+
+ char const *pwavname = g_pSoundEmitterSystem->GetWaveName( sf.symbol );
+ char fixed[ 512 ];
+ Q_strncpy( fixed, PSkipSoundChars( pwavname ), sizeof( fixed ) );
+ _strlwr( fixed );
+ Q_FixSlashes( fixed );
+
+ int curidx = wavtosound.Find( fixed );
+
+ if ( curidx == wavtosound.InvalidIndex() )
+ {
+ wavtosound.Insert( fixed, soundSymbol );
+
+ //vprint( 0, "entry %s == %s\n", fixed, soundname );
+ }
+ }
+ }
+ }
+
+ vprint( 0, "Reverse lookup has %i entries from %i available sounds\n", wavtosound.Count(), c );
+}
+
+#define UNK_SOUND_ENTRY "<nosoundentry>"
+char const *FindSoundEntry( CUtlDict< CUtlSymbol, int >& wavtosound, char const *wavname )
+{
+ char fixed[ 512 ];
+ Q_strncpy( fixed, PSkipSoundChars( wavname ), sizeof( fixed ) );
+ _strlwr( fixed );
+ Q_FixSlashes( fixed );
+
+
+ int idx = wavtosound.Find( fixed );
+ if ( idx != wavtosound.InvalidIndex() )
+ {
+ CUtlSymbol snd = wavtosound[ idx ];
+ return g_Analysis.symbols.String( snd );
+ }
+ return UNK_SOUND_ENTRY;
+}
+
+void CheckWaveFile( CUtlDict< CUtlSymbol, int >& wavtosound, char const *wavname )
+{
+// vprint( 0, "%s\n", wavname );
+
+ char const *soundname = FindSoundEntry( wavtosound, wavname );
+
+ char ansi[ 512 ];
+
+ ansi[ 0 ] = 0;
+
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
+ {
+ if ( Q_strlen( sentence.GetText() ) > 0 )
+ {
+ Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() );
+ cleanquotes( ansi );
+ }
+ }
+
+ // Now look up cc token
+ wchar_t *text = g_pVGuiLocalize->Find( soundname );
+
+ char caption[ 1024 ];
+ Q_strncpy( caption, "!!!", sizeof( caption ) );
+ if ( text )
+ {
+ g_pVGuiLocalize->ConvertUnicodeToANSI( text, caption, sizeof( caption ) );
+ }
+ else
+ {
+ if ( !Q_stricmp( soundname, UNK_SOUND_ENTRY ) )
+ {
+ Q_snprintf( caption, sizeof( caption ), "!!!%s", soundname );
+ }
+ }
+
+ logprint( "wavcheck.csv",
+ "\"%s\",\"%s\",\"%s\",\"%s\"\n",
+ wavname,
+ soundname,
+ caption,
+ ansi );
+}
+
+void WavCheck( CUtlVector< CUtlSymbol >& wavfiles )
+{
+ g_pFullFileSystem->RemoveFile( "wavcheck.csv", "GAME" );
+
+ vprint( 0, "Building reverse lookup\n" );
+
+
+ logprint( "wavcheck.csv",
+ "\"%s\",\"%s\",\"%s\",\"%s\"\n",
+ "WaveName",
+ "Sound Script Name",
+ "Close Caption",
+ "Phoneme Data String" );
+
+ CUtlDict< CUtlSymbol, int > wavtosound;
+ BuildReverseSoundLookup( wavtosound );
+
+ vprint( 0, "Performing wavcheck\n" );
+ int c = wavfiles.Count();
+ int offset = Q_strlen( gamedir ) + Q_strlen( "sound/" );
+
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] );
+ CheckWaveFile( wavtosound, wavname + offset );
+
+ if ( !(i % 100 ) )
+ {
+ vprint( 0, "Finished %i/%i\n", i, c );
+ }
+ }
+}
+
+void BuildWavFileToFullPathLookup( CUtlVector< CUtlSymbol >& wavfile, CUtlDict< int, int >& wavtofullpath )
+{
+ int c = wavfile.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ CUtlSymbol &sym = wavfile[ i ];
+
+ char shortname[ 512 ];
+ Q_FileBase( g_Analysis.symbols.String( sym ), shortname, sizeof( shortname ) );
+
+ Q_SetExtension( shortname, ".wav", sizeof( shortname ) );
+ Q_FixSlashes( shortname );
+ Q_strlower( shortname );
+
+ int idx = wavtofullpath.Find( shortname );
+ if ( idx == wavtofullpath.InvalidIndex() )
+ {
+ wavtofullpath.Insert( shortname, i );
+ }
+ }
+}
+
+static void COM_CreatePath (const char *path)
+{
+ char temppath[512];
+ Q_strncpy( temppath, path, sizeof(temppath) );
+
+ for (char *ofs = temppath+1 ; *ofs ; ofs++)
+ {
+ if (*ofs == '/' || *ofs == '\\')
+ { // create the directory
+ char old = *ofs;
+ *ofs = 0;
+ mkdir (temppath);
+ *ofs = old;
+ }
+ }
+}
+
+void MakeBatchFile( CUtlVector< CUtlSymbol >& wavfiles, char const *pchFromdir, char const *pchTodir )
+{
+ g_pFullFileSystem->RemoveFile( "copywaves.bat", "GAME" );
+
+ vprint( 0, "Building reverse lookup\n" );
+
+ CUtlDict< int, int > wavtofullpath;
+ BuildWavFileToFullPathLookup( wavfiles, wavtofullpath );
+
+ CUtlVector< CUtlSymbol > files;
+ BuildFileList( files, pchFromdir, ".wav" );
+
+ int gamedirskip = Q_strlen( gamedir ) + Q_strlen( "sound//" );
+
+ int c = files.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *sname = g_Analysis.symbols.String( files[ i ] );
+ if ( !sname )
+ continue;
+
+ char shortname[ 512 ];
+ Q_strncpy( shortname, sname, sizeof( shortname ) );
+
+ char fn[ 512 ];
+ Q_FileBase( shortname, fn, sizeof( fn ) );
+ Q_SetExtension( fn, ".wav", sizeof( fn ) );
+ Q_strlower( fn );
+ Q_FixSlashes( fn );
+
+ int slot = wavtofullpath.Find( fn );
+ if ( slot == wavtofullpath.InvalidIndex() )
+ {
+ vprint( 0, "Couldn't find slot for '%s'\n", sname );
+ continue;
+ }
+
+ char fullname[ 512 ];
+ Q_snprintf( fullname, sizeof( fullname ), "%s/%s", pchTodir, &g_Analysis.symbols.String( wavfiles[ wavtofullpath[ slot ] ] )[ gamedirskip ] );
+ Q_strlower( fullname );
+ Q_FixSlashes( fullname );
+
+ //logprint( "copywaves.bat", "xcopy \"%s\" \"%s\"\n",
+ //shortname,
+ //fullname );
+
+
+ COM_CreatePath( fullname );
+
+ CopyFile( shortname, fullname, TRUE );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : store -
+//-----------------------------------------------------------------------------
+void StoreValveDataChunk( CSentence& sentence, IterateOutputRIFF& store )
+{
+ // Buffer and dump data
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ sentence.SaveToBuffer( buf );
+
+ // Copy into store
+ store.ChunkWriteData( buf.Base(), buf.TellPut() );
+}
+
+void SaveWave( char const *filename, CSentence& s )
+{
+ char infile[ 512 ];
+ Q_strncpy( infile, filename, sizeof( infile ) );
+
+ Q_SetExtension( infile, ".tmp", sizeof( infile ) );
+
+ // Rename infile
+ MoveFile( filename, infile );
+ SetFileAttributes( filename, FILE_ATTRIBUTE_NORMAL );
+
+ {
+ InFileRIFF riff( infile, io_in );
+ Assert( riff.RIFFName() == RIFF_WAVE );
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ OutFileRIFF riffout( filename, io_out );
+
+ IterateOutputRIFF store( riffout );
+
+ bool wordtrackwritten = false;
+
+ // Walk input chunks and copy to output
+ while ( walk.ChunkAvailable() )
+ {
+ unsigned int originalPos = store.ChunkGetPosition();
+
+ store.ChunkStart( walk.ChunkName() );
+
+ bool skipchunk = false;
+
+ switch ( walk.ChunkName() )
+ {
+ case WAVE_VALVEDATA:
+ // Overwrite data
+ StoreValveDataChunk( s, store );
+ wordtrackwritten = true;
+ break;
+ default:
+ store.CopyChunkData( walk );
+ break;
+ }
+
+ store.ChunkFinish();
+ if ( skipchunk )
+ {
+ store.ChunkSetPosition( originalPos );
+ }
+
+ walk.ChunkNext();
+ }
+
+ if ( !wordtrackwritten )
+ {
+ store.ChunkStart( WAVE_VALVEDATA );
+ StoreValveDataChunk( s, store );
+ store.ChunkFinish();
+ }
+ }
+
+ SetFileAttributes( infile, FILE_ATTRIBUTE_NORMAL );
+ DeleteFile( infile );
+}
+
+void ExtractPhonemesForWave( IPhonemeExtractor *extractor, char const *wavname )
+{
+ char formatbuffer[ 1024 ];
+ int formatsize = sizeof( formatbuffer );
+ int dataSize = 0;
+
+ CSentence sentence;
+ if ( !LoadSentenceFromWavFile( wavname, sentence, formatbuffer, &formatsize, &dataSize ) )
+ {
+ vprint( 0, " skip '%s' missing\n", wavname );
+ return;
+ }
+
+ if ( !forceextract &&
+ sentence.m_Words.Count() > 0 )
+ {
+ vprint( 0, " skip '%s', already has phonemes\n", wavname );
+ return;
+ }
+
+ if ( forceextract )
+ {
+ sentence.Reset();
+ }
+
+ if ( formatsize == 0 )
+ {
+ vprint( 0, " skip '%s', not WAVE_FMT parsed\n", wavname );
+ return;
+ }
+
+ const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)formatbuffer;
+
+ int format = pHeader->wFormatTag;
+
+ int bits = pHeader->wBitsPerSample;
+ int rate = pHeader->nSamplesPerSec;
+ int channels = pHeader->nChannels;
+
+ int sampleSize = (bits * channels) / 8;
+
+ // this can never be zero -- other functions divide by this.
+ // This should never happen, but avoid crashing
+ if ( sampleSize <= 0 )
+ sampleSize = 1;
+
+ int sampleCount = 0;
+ float truesamplesize = sampleSize;
+
+ if ( format == WAVE_FORMAT_ADPCM )
+ {
+ sampleSize = 1;
+
+ ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)formatbuffer;
+ int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2;
+ blockSize += 7 * pFormat->wfx.nChannels;
+
+ int blockCount = sampleCount / blockSize;
+ int blockRem = sampleCount % blockSize;
+
+ // total samples in complete blocks
+ sampleCount = blockCount * pFormat->wSamplesPerBlock;
+
+ // add remaining in a short block
+ if ( blockRem )
+ {
+ sampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / channels);
+ }
+
+ truesamplesize = 0.5f;
+ }
+ else
+ {
+ sampleCount = dataSize / sampleSize;
+ }
+
+ // Do extraction
+ // Current set of tags
+ CSentence outsentence;
+
+ char filename[ 512 ];
+ Q_snprintf( filename, sizeof( filename ), "%s", wavname );
+
+ int result = extractor->Extract(
+ filename,
+ dataSize, // (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize() ),
+ Msg,
+ sentence,
+ outsentence );
+
+ if ( result != SR_RESULT_SUCCESS )
+ {
+ vprint( 0, " failed to analyze '%s', skipping\n", wavname );
+ return;
+ }
+
+
+ float bytespersecond = rate * truesamplesize;
+
+ // Now convert byte offsets to times
+ int i;
+ for ( i = 0; i < outsentence.m_Words.Size(); i++ )
+ {
+ CWordTag *tag = outsentence.m_Words[ i ];
+ Assert( tag );
+ if ( !tag )
+ continue;
+
+ tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond;
+ tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond;
+
+ for ( int j = 0; j < tag->m_Phonemes.Size(); j++ )
+ {
+ CPhonemeTag *ptag = tag->m_Phonemes[ j ];
+ Assert( ptag );
+ if ( !ptag )
+ continue;
+
+ ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond );
+ ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond );
+ }
+ }
+
+ sentence = outsentence;
+
+ outsentence.Reset();
+
+ // Resave it
+ SaveWave( filename, sentence );
+}
+
+struct Extractor
+{
+ PE_APITYPE apitype;
+ CSysModule *module;
+ IPhonemeExtractor *extractor;
+};
+
+CUtlVector< Extractor > g_Extractors;
+
+void UnloadPhonemeConverters()
+{
+ int c = g_Extractors.Count();
+ for ( int i = c - 1; i >= 0; i-- )
+ {
+ Extractor *e = &g_Extractors[ i ];
+ g_pFullFileSystem->UnloadModule( e->module );
+ }
+
+ g_Extractors.RemoveAll();
+}
+
+int LoadPhonemeExtractors()
+{
+ // Enumerate modules under bin folder of exe
+ FileFindHandle_t findHandle;
+ const char *pFilename = g_pFullFileSystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle );
+ int useextractor = -1;
+ while ( pFilename )
+ {
+ char fullpath[ 512 ];
+ Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename );
+
+ pFilename = g_pFullFileSystem->FindNext( findHandle );
+
+ Con_Printf( "Loading extractor from %s\n", fullpath );
+
+ Extractor e;
+ e.module = Sys_LoadModule( fullpath );
+ if ( !e.module )
+ {
+ Warning( "Unable to Sys_LoadModule %s\n", fullpath );
+ continue;
+ }
+
+ CreateInterfaceFn factory = Sys_GetFactory( e.module );
+ if ( !factory )
+ {
+ Warning( "Unable to get factory from %s\n", fullpath );
+ continue;
+ }
+
+ e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL );
+ if ( !e.extractor )
+ {
+ Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath );
+ continue;
+ }
+
+ e.apitype = e.extractor->GetAPIType();
+ if ( e.apitype == SPEECH_API_LIPSINC )
+ {
+ useextractor = g_Extractors.Count();
+ }
+
+ g_Extractors.AddToTail( e );
+ }
+
+ g_pFullFileSystem->FindClose( findHandle );
+
+ return useextractor;
+}
+
+void ExtractPhonemes( CUtlVector< CUtlSymbol >& wavfiles )
+{
+ int index = LoadPhonemeExtractors();
+ if ( index == -1 )
+ return;
+
+ if ( index == 0 )
+ {
+ vprint( 0, "Couldn't find suitable extractor\n" );
+ return;
+ }
+
+ IPhonemeExtractor *extractor = g_Extractors[ index ].extractor;
+ Assert( extractor );
+
+ vprint( 0, "Using %s\n", extractor->GetName() );
+
+ int c = wavfiles.Count();
+
+ vprint( 0, "Performing '%i' extractions (might take a while...)\n", c );
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] );
+ ExtractPhonemesForWave( extractor, wavname );
+
+ if ( !(i % 50 ) )
+ {
+ vprint( 0, "Finished %i/%i\n", i, c );
+ }
+ }
+
+ UnloadPhonemeConverters();
+}
+
+void CheckWavForLoops( char const *wavname )
+{
+ InFileRIFF riff( wavname, io_in );
+
+ // UNDONE: Don't use printf to handle errors
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ return;
+ }
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ while ( walk.ChunkAvailable( ) )
+ {
+ switch( walk.ChunkName() )
+ {
+ case WAVE_CUE:
+ vprint( 0, "'%s' has a CUE chunk\n", wavname );
+ return;
+ default:
+ break;
+ }
+ walk.ChunkNext();
+ }
+}
+
+void CheckForLoops( CUtlVector< CUtlSymbol >& wavfiles )
+{
+ int c = wavfiles.Count();
+
+ vprint( 0, "Performing '%i' extractions (might take a while...)\n", c );
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] );
+ CheckWavForLoops( wavname );
+ if ( !(i % 50 ) )
+ {
+ vprint( 0, "Finished %i/%i\n", i, c );
+ }
+ }
+}
+
+#define MAX_LOCALIZED_CHARS 2048
+//-----------------------------------------------------------------------------
+// Purpose: converts an unicode string to an english string
+//-----------------------------------------------------------------------------
+int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize)
+{
+ int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
+ ansi[ansiBufferSize - 1] = 0;
+ return result;
+}
+
+struct OrderedCaption_t
+{
+ OrderedCaption_t() :
+ sym( UTL_INVAL_SYMBOL ),
+ commands( NULL ),
+ english( NULL ),
+ blankenglish( false )
+ {
+ }
+
+ OrderedCaption_t( const OrderedCaption_t& src )
+ {
+ sym = src.sym;
+
+ if ( src.commands )
+ {
+ int len = wcslen( src.commands ) + 1;
+ commands = new wchar_t[ len ];
+ wcscpy( commands, src.commands );
+ }
+ else
+ {
+ commands = NULL;
+ }
+
+ if ( src.english )
+ {
+ int len = wcslen( src.english ) + 1;
+ english = new wchar_t[ len ];
+ wcscpy( english, src.english );
+ }
+ else
+ {
+ english = NULL;
+ }
+ blankenglish = src.blankenglish;
+ }
+
+ ~OrderedCaption_t()
+ {
+ delete[] commands;
+ delete[] english;
+ }
+
+ CUtlSymbol sym;
+ wchar_t *commands; // any <cmd:arg> stuff at the beginning of the US captions
+ wchar_t *english;
+ bool blankenglish;
+};
+
+bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args )
+{
+ const wchar_t *in = *ppIn;
+ const wchar_t *oldin = in;
+
+ if ( in[0] != L'<' )
+ {
+ *ppIn += ( oldin - in );
+ return false;
+ }
+
+ args[ 0 ] = 0;
+ cmd[ 0 ]= 0;
+ wchar_t *out = cmd;
+ in++;
+ while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) )
+ {
+ *out++ = *in++;
+ }
+ *out = L'\0';
+
+ if ( *in != L':' )
+ {
+ *ppIn += ( in - oldin );
+ return true;
+ }
+
+ in++;
+ out = args;
+ while ( *in != L'\0' && *in != L'>' )
+ {
+ *out++ = *in++;
+ }
+ *out = L'\0';
+
+ //if ( *in == L'>' )
+ // in++;
+
+ *ppIn += ( in - oldin );
+ return true;
+}
+
+wchar_t *GetStartupCommands( const wchar_t *str )
+{
+ const wchar_t *curpos = str;
+
+ for ( ; curpos && *curpos != L'\0'; ++curpos )
+ {
+ wchar_t cmd[ 256 ];
+ wchar_t args[ 256 ];
+
+ if ( SplitCommand( &curpos, cmd, args ) )
+ {
+ continue;
+ }
+
+ // Got to first non-command character
+ break;
+ }
+
+ if ( curpos - str >= 1 )
+ {
+ int len = curpos - str;
+ wchar_t *cmds = new wchar_t[ len + 1 ];
+ wcsncpy( cmds, str, len );
+ cmds[ len ] = L'\0';
+ return cmds;
+ }
+
+ return NULL;
+}
+
+wchar_t *CopyUnicode( const wchar_t *in )
+{
+ int len = wcslen( in ) + 1;
+ wchar_t *out = new wchar_t[ len ];
+ wcsncpy( out, in, len );
+ out[ len - 1 ] = L'\0';
+ return out;
+}
+
+void BuildOrderedCaptionList( CUtlVector< OrderedCaption_t >& list )
+{
+ // parse out the file
+ FileHandle_t file = g_pFullFileSystem->Open( "resource/closecaption_english.txt", "rb");
+ if ( file == FILESYSTEM_INVALID_HANDLE )
+ {
+ // assert(!("CLocalizedStringTable::AddFile() failed to load file"));
+ return;
+ }
+
+ // read into a memory block
+ int fileSize = g_pFullFileSystem->Size(file) ;
+ wchar_t *memBlock = (wchar_t *)malloc(fileSize + sizeof(wchar_t));
+ wchar_t *data = memBlock;
+ g_pFullFileSystem->Read(memBlock, fileSize, file);
+
+ // null-terminate the stream
+ memBlock[fileSize / sizeof(wchar_t)] = 0x0000;
+
+ // check the first character, make sure this a little-endian unicode file
+ if (data[0] != 0xFEFF)
+ {
+ g_pFullFileSystem->Close(file);
+ free(memBlock);
+ return;
+ }
+ data++;
+
+ // parse out a token at a time
+ enum states_e
+ {
+ STATE_BASE, // looking for base settings
+ STATE_TOKENS, // reading in unicode tokens
+ };
+
+ bool bQuoted;
+ bool bEnglishFile = true;
+
+ states_e state = STATE_BASE;
+ while (1)
+ {
+ // read the key and the value
+ wchar_t keytoken[128];
+ data = ReadUnicodeToken(data, keytoken, 128, bQuoted);
+ if (!keytoken[0])
+ break; // we've hit the null terminator
+
+ // convert the token to a string
+ char key[128];
+ ConvertUnicodeToANSI(keytoken, key, sizeof(key));
+
+ // if we have a C++ style comment, read to end of line and continue
+ if (!strnicmp(key, "//", 2))
+ {
+ data = ReadToEndOfLine(data);
+ continue;
+ }
+
+ wchar_t valuetoken[ MAX_LOCALIZED_CHARS ];
+ data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted);
+ if (!valuetoken[0] && !bQuoted)
+ break; // we've hit the null terminator
+
+ if (state == STATE_BASE)
+ {
+ if (!stricmp(key, "Language"))
+ {
+ // copy out our language setting
+ /*
+ char value[MAX_LOCALIZED_CHARS];
+ ConvertUnicodeToANSI(valuetoken, value, sizeof(value));
+ strncpy(m_szLanguage, value, sizeof(m_szLanguage) - 1);
+ */
+ }
+ else if (!stricmp(key, "Tokens"))
+ {
+ state = STATE_TOKENS;
+ }
+ else if (!stricmp(key, "}"))
+ {
+ // we've hit the end
+ break;
+ }
+ }
+ else if (state == STATE_TOKENS)
+ {
+ if (!stricmp(key, "}"))
+ {
+ // end of tokens
+ state = STATE_BASE;
+ }
+ else
+ {
+ // skip our [english] beginnings (in non-english files)
+ if ( (bEnglishFile) || (!bEnglishFile && strnicmp(key, "[english]", 9)))
+ {
+ // add the string to the table
+ //AddString(key, valuetoken, NULL);
+ CUtlSymbol sym = g_Analysis.symbols.AddString( key );
+
+ OrderedCaption_t cap;
+ cap.sym = sym;
+ cap.commands = GetStartupCommands( valuetoken );
+ cap.english = CopyUnicode( valuetoken );
+ cap.blankenglish = IsAllSpaces( valuetoken );
+
+ list.AddToTail( cap );
+ }
+ }
+ }
+ }
+
+ g_pFullFileSystem->Close(file);
+ free(memBlock);
+
+ vprint( 0, "Loaded %i captionnames from closecaption_english.txt\n", list.Count() );
+}
+
+struct LookupData_t
+{
+ LookupData_t() :
+ unicode( 0 ),
+ caption( 0 )
+ {
+ }
+
+ wchar_t *unicode;
+ char *caption;
+};
+
+void LoadImportData( char const *filename, CUtlDict< LookupData_t, int >& lookup )
+{
+// parse out the file
+ FileHandle_t file = g_pFullFileSystem->Open( filename, "rb");
+ if ( file == FILESYSTEM_INVALID_HANDLE )
+ {
+ // assert(!("CLocalizedStringTable::AddFile() failed to load file"));
+ return;
+ }
+
+ // read into a memory block
+ int fileSize = g_pFullFileSystem->Size(file) ;
+ wchar_t *memBlock = (wchar_t *)malloc(fileSize + sizeof(wchar_t));
+ wchar_t *data = memBlock;
+ g_pFullFileSystem->Read(memBlock, fileSize, file);
+
+ // null-terminate the stream
+ memBlock[fileSize / sizeof(wchar_t)] = 0x0000;
+
+ // check the first character, make sure this a little-endian unicode file
+ if (data[0] != 0xFEFF)
+ {
+ g_pFullFileSystem->Close(file);
+ free(memBlock);
+ return;
+ }
+ data++;
+
+ bool bQuoted;
+
+ while (1)
+ {
+ // read the key and the value
+ wchar_t keytoken[128];
+ data = ReadUnicodeTokenNoSpecial(data, keytoken, 128, bQuoted);
+ if (!keytoken[0])
+ break; // we've hit the null terminator
+
+ // convert the token to a string
+ char key[128];
+ ConvertUnicodeToANSI(keytoken, key, sizeof(key));
+
+ // vprint( 0, "keyname %s\n", key );
+
+ // if we have a C++ style comment, read to end of line and continue
+ if (!strnicmp(key, "//", 2))
+ {
+ data = ReadToEndOfLine(data);
+ continue;
+ }
+
+ wchar_t valuetoken[ MAX_LOCALIZED_CHARS ];
+ data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted);
+ if (!valuetoken[0] && !bQuoted)
+ break; // we've hit the null terminator
+
+ wchar_t *vcopy = new wchar_t[ wcslen( valuetoken ) + 1 ];
+ wcscpy( vcopy, valuetoken );
+
+ LookupData_t ld;
+ ld.unicode = vcopy;
+ ld.caption = NULL;
+
+ lookup.Insert( key, ld );
+ }
+
+ g_pFullFileSystem->Close(file);
+ free(memBlock);
+
+ vprint( 0, "Loaded %i wav/captions from %s\n", lookup.Count(), filename );
+}
+
+#define CAPTION_OUT_FILE "resource/closecaption_test.txt"
+
+//-----------------------------------------------------------------------------
+// Purpose: importfile is a unicode file contains pairs of .wav names and caption strings
+// we need to read in the closecaption_english.txt file
+// and then build a reverse lookup of .wav to caption name and build a new
+// closecaption_test.txt file based on the unicode caption strings
+// Input : *importfile -
+//-----------------------------------------------------------------------------
+void ImportCaptions( char const *pchImportfile )
+{
+ CUtlVector< OrderedCaption_t > captionlist;
+ BuildOrderedCaptionList( captionlist );
+
+ CUtlDict< LookupData_t, int > newCaptions;
+ LoadImportData( pchImportfile, newCaptions );
+
+ // Now build a .wav to caption name lookup
+ CUtlDict< CUtlSymbol, int > wavtosound;
+ BuildReverseSoundLookup( wavtosound );
+
+ CUtlDict< wchar_t *, int > captionToUnicode;
+
+ // Now walk the import data, and try to figure out the caption name for each one
+ int c = newCaptions.Count();
+ for ( int i = 0; i < c ; ++i )
+ {
+ char const *wavname = newCaptions.GetElementName( i );
+ LookupData_t& data = newCaptions[ i ];
+
+ char fn[ 512 ];
+ Q_strncpy( fn, wavname, sizeof( fn ) );
+ Q_strlower( fn );
+ Q_FixSlashes( fn );
+
+ // See if we can find the wavname in the reverse lookup
+ int idx = wavtosound.Find( fn );
+ if ( idx != wavtosound.InvalidIndex() )
+ {
+ data.caption = strdup( g_Analysis.symbols.String( wavtosound[ idx ] ) );
+
+ captionToUnicode.Insert( data.caption, data.unicode );
+ }
+ else
+ {
+ vprint( 0, "unable to find caption matching '%s'\n", wavname );
+ }
+ }
+
+ CUtlBuffer buf( 0, 0 );
+
+ c = captionlist.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *captionname = g_Analysis.symbols.String( captionlist[ i ].sym );
+
+ // Find it in the captionToUnicodeFolder
+ int idx = captionToUnicode.Find( captionname );
+ if ( idx != captionToUnicode.InvalidIndex() )
+ {
+ // Skip blank english entries
+ if ( captionlist[ i ].blankenglish )
+ {
+ vprint( 0, "skipping %s, english caption is blank\n", captionname );
+ continue;
+ }
+
+ wchar_t *u = captionToUnicode[ idx ];
+
+ wchar_t *prefix = captionlist[ i ].commands;
+
+ wchar_t composed[ MAX_LOCALIZED_CHARS ];
+
+ int maxlen = ( sizeof( composed ) / sizeof( wchar_t ) ) - 1;
+
+ if ( prefix )
+ {
+ _snwprintf( composed, maxlen, L"%s%s", prefix, u );
+ }
+ else
+ {
+ wcsncpy( composed, u, maxlen );
+ }
+
+ composed[ maxlen ] = L'\0';
+
+ // Write to buffer
+ WriteAsciiStringAsUnicode( buf, captionname, true );
+ WriteAsciiStringAsUnicode( buf, "\t", false );
+ WriteUnicodeString( buf, composed, true );
+ WriteAsciiStringAsUnicode( buf, "\r\n", false );
+
+ // Now write the "[ENGLISH]" entry
+ char engcap[ 512 ];
+ Q_snprintf( engcap, sizeof( engcap ), "[english]%s", captionname );
+
+ WriteAsciiStringAsUnicode( buf, engcap, true );
+ WriteAsciiStringAsUnicode( buf, "\t", false );
+ WriteUnicodeString( buf, captionlist[ i ].english ? captionlist[ i ].english : L"???", true );
+ WriteAsciiStringAsUnicode( buf, "\r\n", false );
+ }
+ else
+ {
+ vprint( 0, "no lookup for cc token '%s'\n", captionname );
+ }
+ }
+
+ // Now try and spit out a file like the cc english file, but with the new data
+ FileHandle_t fh = g_pFullFileSystem->Open( CAPTION_OUT_FILE , "wb" );
+ if ( FILESYSTEM_INVALID_HANDLE != fh )
+ {
+ g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
+ g_pFullFileSystem->Close( fh );
+ }
+ else
+ {
+ vprint( 0, "Unable to open %s for writing\n", CAPTION_OUT_FILE );
+ }
+
+ // Cleanup memory
+ c = newCaptions.Count();
+ for ( int i = 0 ; i < c; ++i )
+ {
+ LookupData_t& data = newCaptions[ i ];
+ delete[] data.unicode;
+ free( data.caption );
+ }
+
+ newCaptions.Purge();
+}
+
+bool IsWavFileDucked( char const *name )
+{
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( name , sentence ) )
+ {
+ return sentence.GetVoiceDuck();
+ }
+
+ vprint( 0, "IsWavFileDucked: Missing .wav %s!!!\n", name );
+ return false;
+}
+
+void SetWavFileDucking( char const *name, bool ducking )
+{
+ CSentence sentence;
+ if ( LoadSentenceFromWavFile( name , sentence ) )
+ {
+ Assert( sentence.GetVoiceDuck() != ducking );
+
+ sentence.SetVoiceDuck( ducking );
+
+ // Save it back out
+ SaveWave( name, sentence );
+
+ vprint( 1, "duck(%s): %s\n", ducking ? "true" : "false", name );
+ return;
+ }
+
+ vprint( 0, "SetWavFileDucking: Missing .wav %s!!!\n", name );
+}
+
+void SyncDucking( CUtlVector< CUtlSymbol >& english, CUtlVector< CUtlSymbol >& localized )
+{
+ int i, c;
+
+ CUtlRBTree< CUtlSymbol > englishducked( 0, 0, DefLessFunc( CUtlSymbol ) );
+ CUtlRBTree< CUtlSymbol > englishunducked( 0, 0, DefLessFunc( CUtlSymbol ) );
+
+ int fromoffset = Q_strlen( fromdir ) + 1;
+ int tooffset = Q_strlen( todir ) + 1;
+
+ c = english.Count();
+ for ( i = 0; i < c; ++i )
+ {
+ CUtlSymbol& sym = english[ i ];
+ char fn[ 512 ];
+ Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
+
+ if ( !( i % 1000 ) )
+ {
+ vprint( 1, "analyzed %i / %i (%.1f %%)\n", i, c, 100.0f * (float)i/(float)c );
+ }
+
+ bool ducked = IsWavFileDucked( fn );
+
+ CUtlSymbol croppedSym = g_Analysis.symbols.AddString( &fn[ fromoffset ] );
+
+ if ( ducked )
+ {
+ englishducked.Insert( croppedSym );
+ }
+ else
+ {
+ englishunducked.Insert( croppedSym );
+ }
+ }
+
+
+ int updated = 0;
+
+ // Now walk the localized tree and sync it to the english version
+ c = localized.Count();
+ for ( i = 0; i < c; ++i )
+ {
+ CUtlSymbol& sym = localized[ i ];
+ char fn[ 512 ];
+ Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
+
+ bool ducked = IsWavFileDucked( fn );
+
+ CUtlSymbol croppedSym = g_Analysis.symbols.AddString( &fn[ tooffset ] );
+
+ bool inenglishducked = englishducked.Find( croppedSym ) != englishducked.InvalidIndex() ? true : false;
+ bool inenglishunducked = englishunducked.Find( croppedSym ) != englishunducked.InvalidIndex() ? true : false;
+
+ if ( !( i % 100 ) )
+ {
+ vprint( 1, "sync'd %i / %i (%.1f %%)\n", i, c, 100.0f * (float)i/(float)c );
+ }
+
+ if ( ducked && inenglishducked )
+ continue;
+ if ( !ducked && inenglishunducked )
+ continue;
+
+ if ( !inenglishducked && !inenglishunducked )
+ {
+ vprint( 0, "Warning: %s is in localized tree, missing from english tree!!\n", fn );
+ continue;
+ }
+
+ Assert( inenglishducked ^ inenglishunducked );
+
+ SetWavFileDucking( fn, inenglishducked );
+ ++updated;
+ }
+
+ vprint( 0, "finished, updated %i / %i (%.1f %%) localized .wavs\n", updated, c, 100.0f * (float)updated/(float)c );
+}
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+class CLocalizationCheckApp : public CTier3SteamApp
+{
+ typedef CTier3SteamApp BaseClass;
+
+public:
+ // Methods of IApplication
+ virtual bool Create();
+ virtual bool PreInit( );
+ virtual int Main();
+ virtual void PostShutdown( );
+ virtual void Destroy() {}
+};
+
+DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CLocalizationCheckApp );
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+bool CLocalizationCheckApp::Create()
+{
+ SpewOutputFunc( SpewFunc );
+ SpewActivate( "localization_check", 2 );
+
+ AppSystemInfo_t appSystems[] =
+ {
+ { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION },
+ { "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION },
+ { "", "" } // Required to terminate the list
+ };
+
+ return AddSystems( appSystems );
+}
+
+
+//-----------------------------------------------------------------------------
+// Init, shutdown
+//-----------------------------------------------------------------------------
+bool CLocalizationCheckApp::PreInit( )
+{
+ if ( !BaseClass::PreInit() )
+ return false;
+
+ g_pFileSystem = filesystem = g_pFullFileSystem;
+ if ( !g_pFullFileSystem || !g_pSoundEmitterSystem || !g_pVGuiLocalize )
+ {
+ Error( "Unable to load required library interface!\n" );
+ return false;
+ }
+
+ char workingdir[ 256 ];
+ workingdir[0] = 0;
+ Q_getwd( workingdir, sizeof( workingdir ) );
+
+ // If they didn't specify -game on the command line, use VPROJECT.
+ if ( !SetupSearchPaths( workingdir, false, true ) )
+ {
+ Warning( "Unable to set up the file system!\n" );
+ return false;
+ }
+
+ // work out of the root directory (same as the reslists)
+ g_pFullFileSystem->AddSearchPath(".", "root");
+
+ return true;
+}
+
+
+void CLocalizationCheckApp::PostShutdown( )
+{
+ g_pFileSystem = filesystem = NULL;
+ BaseClass::PostShutdown();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : argc -
+// argv[] -
+// Output : int
+//-----------------------------------------------------------------------------
+int CLocalizationCheckApp::Main()
+{
+ char language[ 256 ];
+ memset( language, 0, sizeof( language ) );
+
+ bool extractenglish = false;
+ bool forceducking = false;
+
+ int iArg = 1;
+ int argc = CommandLine()->ParmCount();
+ for (; iArg<argc ; iArg++)
+ {
+ char const *pArg = CommandLine()->GetParm( iArg );
+ if ( pArg[ 0 ] == '-' )
+ {
+ switch( pArg[ 1 ] )
+ {
+ case 'p':
+ extractenglish = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'r':
+ regenerate = true;
+ break;
+ case 'q':
+ regenerate_quiet = true;
+ break;
+ case 'u':
+ generate_usage = true;
+ break;
+ case 'b':
+ nuke = true;
+ break;
+ case 's':
+ checkscriptsounds = true;
+ break;
+ case 'c':
+ build_cc = true;
+ break;
+ case 'x':
+ build_script = true;
+ break;
+ case 'w':
+ wavcheck = true;
+ Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) );
+ iArg++;
+ break;
+ case 'e':
+ extractphonemes = true;
+ Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) );
+ iArg++;
+ break;
+ case 'l':
+ if ( !Q_stricmp( &pArg[1], "loop" ) )
+ {
+ checkforloops = true;
+ Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) );
+ iArg++;
+ }
+ else
+ {
+ uselogfile = true;
+ }
+ break;
+ case 'f':
+ if ( !Q_stricmp( pArg, "-forceduck" ))
+ {
+ forceducking = true;
+ break;
+ }
+ forceextract = true;
+ break;
+ case 'd':
+ checkfordups = true;
+ break;
+ case 'i':
+ {
+ importcaptions = true;
+ Q_strncpy( importfile, CommandLine()->GetParm( iArg + 1 ), sizeof( importfile ) );
+ iArg++;
+ }
+ break;
+ case 'm':
+ {
+ makecopybatch = true;
+ Q_strncpy( fromdir, CommandLine()->GetParm( iArg + 1 ), sizeof( fromdir ) );
+ Q_strncpy( todir, CommandLine()->GetParm( iArg + 2 ), sizeof( todir ) );
+ iArg += 2;
+ }
+ break;
+ case 'a':
+ {
+ syncducking = true;
+ Q_strncpy( fromdir, CommandLine()->GetParm( iArg + 1 ), sizeof( fromdir ) );
+ Q_strncpy( todir, CommandLine()->GetParm( iArg + 2 ), sizeof( todir ) );
+ iArg += 2;
+ }
+ break;
+ default:
+ printusage();
+ break;
+ }
+ }
+ }
+
+ if ( argc < 2 || (iArg != argc ) )
+ {
+ PrintHeader();
+ printusage();
+ }
+
+ Q_strncpy( language, CommandLine()->GetParm( argc - 1 ), sizeof( language ) );
+
+ // If it's english, turn off checks.
+ if ( !Q_stricmp( language, "english" ) )
+ {
+ language[ 0 ] = 0;
+ }
+
+ if ( !forceducking && !checkforloops && !syncducking && !extractphonemes && !importcaptions && language[0] != 0 )
+ {
+ // See if it's a valid language
+ int idx = CSentence::LanguageForName( language );
+ if ( idx == -1 )
+ {
+ vprint( 0, "\nSkipping language check, '%s' is not a valid language\n", language );
+
+ vprint( 0, "Valid Language Names:\n" );
+ for ( int j = 0; j < CC_NUM_LANGUAGES; ++j )
+ {
+ vprint( 2, "%s\n", CSentence::NameForLanguage( j ) );
+ }
+
+ printusage();
+ }
+ }
+
+ CheckLogFile();
+
+ PrintHeader();
+
+ vprint( 0, " Looking for localization inconsistencies...\n" );
+
+ if ( !checkforloops&& !syncducking && !extractphonemes && !importcaptions && language[0] != 0 )
+ {
+ vprint( 0, "\nLanguage: %s\n", language );
+ }
+
+ vprint( 0, "Initializing stub sound system\n" );
+
+ sound->Init();
+
+ vprint( 0, "Initializing localization database system\n" );
+
+ // Always start with english
+ g_pVGuiLocalize->AddFile( "resource/closecaption_english.txt" );
+
+ // Todo add language specific file
+
+ Q_FixSlashes( gamedir );
+ Q_strlower( gamedir );
+
+ char vcddir[ 512 ];
+ Q_snprintf( vcddir, sizeof( vcddir ), "%sscenes", gamedir );
+ char ccdir[ 512 ];
+ Q_snprintf( ccdir, sizeof( ccdir ), "%ssound/combined", gamedir );
+
+ vprint( 0, "game dir %s\nvcd dir %s\n\n",
+ gamedir,
+ vcddir );
+
+ Q_StripTrailingSlash( sounddir );
+ Q_StripTrailingSlash( vcddir );
+
+ //
+ //ProcessMaterialsDirectory( vmtdir );
+
+ vprint( 0, "Initializing sound emitter system\n" );
+ g_pSoundEmitterSystem->ModInit();
+
+ vprint( 0, "Loaded %i sounds\n", g_pSoundEmitterSystem->GetSoundCount() );
+
+ if ( forceducking )
+ {
+ CUtlVector< CUtlSymbol > wavefiles;
+
+ char workingdir[ 256 ];
+ workingdir[0] = 0;
+ Q_getwd( workingdir, sizeof( workingdir ) );
+
+ BuildFileList( wavefiles, workingdir, ".wav" );
+ vprint( 0, "forcing ducking on %i .wav files in %s\n\n", wavefiles.Count(), workingdir );
+ for ( int i = 0; i < wavefiles.Count(); i++ )
+ {
+ CUtlSymbol& sym = wavefiles[ i ];
+ char fn[ 512 ];
+ Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
+ SetWavFileDucking( fn, true );
+ }
+ }
+ else
+ {
+ vprint( 0, "Building list of .vcd files\n" );
+ CUtlVector< CUtlSymbol > vcdfiles;
+
+ BuildFileList( vcdfiles, vcddir, ".vcd" );
+ vprint( 0, "found %i .vcd files\n\n", vcdfiles.Count() );
+
+ if ( extractenglish && !language[0] )
+ {
+ vprint( 0, "extractenglish: pulling raw english txt from file\n" );
+ ExtractEnglish();
+ }
+ else if ( wavcheck )
+ {
+ vprint( 0, "wavcheck: building list of known .wav files\n" );
+ CUtlVector< CUtlSymbol > wavfiles;
+ BuildFileList( wavfiles, va( "%ssound/%s", gamedir, sounddir ), ".wav" );
+
+ vprint( 0, "found %i .wav files\n\n", wavfiles.Count() );
+
+ WavCheck( wavfiles );
+ }
+ else if ( makecopybatch )
+ {
+ vprint( 0, "makecopybatch: building list of known .wav files\n" );
+ CUtlVector< CUtlSymbol > wavfiles;
+ BuildFileList( wavfiles, va( "%ssound/%s", gamedir, sounddir ), ".wav" );
+
+ vprint( 0, "found %i .wav files\n\n", wavfiles.Count() );
+
+ MakeBatchFile( wavfiles, fromdir, todir );
+ }
+ else if ( extractphonemes )
+ {
+ vprint( 0, "extractphonemes: building list of known .wav files\n" );
+ CUtlVector< CUtlSymbol > wavfiles;
+ BuildFileList( wavfiles, sounddir, ".wav" );
+
+ vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", wavfiles.Count(), sounddir );
+
+ ExtractPhonemes( wavfiles );
+ }
+ else if ( checkforloops )
+ {
+ vprint( 0, "checkforloops: building list of known .wav files\n" );
+ CUtlVector< CUtlSymbol > wavfiles;
+ BuildFileList( wavfiles, sounddir, ".wav" );
+
+ vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", wavfiles.Count(), sounddir );
+
+ CheckForLoops( wavfiles );
+ }
+ else if ( importcaptions )
+ {
+ vprint( 0, "importcaptions: importing captions from '%s'\n", importfile );
+ ImportCaptions( importfile );
+ }
+ else if ( checkfordups )
+ {
+ vprint( 0, "checkfordups: checking for duplicate captions\n" );
+ CheckDuplcatedText();
+ }
+ else if ( syncducking )
+ {
+ vprint( 0, "syncducking: building list of known .wav files in\n %s\n %s\n", fromdir, todir );
+ CUtlVector< CUtlSymbol > englishfiles;
+ BuildFileList( englishfiles, fromdir, ".wav" );
+ vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", englishfiles.Count(), fromdir );
+
+ CUtlVector< CUtlSymbol > localized_files;
+ BuildFileList( localized_files, todir, ".wav" );
+ vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", localized_files.Count(), todir );
+
+ SyncDucking( englishfiles, localized_files );
+ }
+ else
+ {
+
+ CUtlVector< CUtlSymbol > vcdsinreslist;
+
+ BuildVCDAndMapNameListsFromReslists( vcdsinreslist );
+
+ // Check for missing localization data for all speak events not marked CC_DISABLED
+ CUtlRBTree< CUtlSymbol, int > referencedcaptionwaves( 0, 0, SymbolLessFunc );
+
+ vprint( 0, "\nValidating close caption tokens and combined .wav file checksums\n\n" );
+
+
+ CheckLocalizationEntries( vcdfiles, referencedcaptionwaves );
+
+ vprint( 0, "\nChecking for orphaned combined .wav files\n\n" );
+
+ CUtlVector< CUtlSymbol > combinedwavfiles;
+ BuildFileList( combinedwavfiles, ccdir, ".wav" );
+
+ CheckForOrphanedCombinedWavs( combinedwavfiles, referencedcaptionwaves );
+
+ if ( language[0] != 0 )
+ {
+ vprint( 0, "\nChecking for missing or out of date localized combined .wav files\n\n" );
+ ValidateForeignLanguageWaves( language, combinedwavfiles );
+ }
+
+ if ( generate_usage )
+ {
+ // Figure out which .vcds are unused
+ CheckUnusedVcds( vcdsinreslist, vcdfiles );
+ }
+
+ if ( checkscriptsounds )
+ {
+ CheckUnusedSounds();
+ }
+ }
+ }
+
+ vprint( 0, "\nCleaning up...\n" );
+
+ g_pSoundEmitterSystem->ModShutdown();
+
+ // Unload localization system
+ g_pVGuiLocalize->RemoveAll();
+
+ sound->Shutdown();
+
+ FileSystem_Term();
+
+ g_Analysis.symbols.RemoveAll();
+
+ return 0;
+}