Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3638 lines
88 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: localization_check.cpp : Defines the entry point for the console application.
  4. //
  5. //
  6. // What the tool does:
  7. //
  8. // Load g_pSoundEmitterSystem, vgui::localize, soundcombiner system, vcd system
  9. //
  10. // Catalog all files in closecaption/ folder
  11. // Iterate all known vcds
  12. //
  13. // 1) For each sound emitted by a vcd not marked as CC_DISABLED, verify that there's an entry in the localization table
  14. // 2) For each combined sound in a vcd, make sure there's a valid entry in the localization table
  15. // 3) For each combined sound, verify that the english version combined .wav file has the proper checksum
  16. // 4) Note any files in the closecaption folder which are orphaned after parsing the above
  17. // 5) If hl2_french directories etc. exist, then compare combined .wav files with localized versions and
  18. // see if localized checksum tag differs from US one, or warn if tag missing, but complain if .wav duration is different.
  19. //
  20. // UNDONE:re-create combined .wav files in english to the extent that the checksums mismatch?
  21. //
  22. //===========================================================================//
  23. #include "cbase.h"
  24. #include <stdio.h>
  25. #include <conio.h>
  26. #include <windows.h>
  27. #include <mmreg.h>
  28. #include <direct.h>
  29. #include "tier0/dbg.h"
  30. #include "utldict.h"
  31. #include "filesystem.h"
  32. #include "KeyValues.h"
  33. #include "cmdlib.h"
  34. #include "scriplib.h"
  35. #include "appframework/tier3app.h"
  36. #include "vstdlib/random.h"
  37. #include "SoundEmitterSystem/isoundemittersystembase.h"
  38. #include "choreoscene.h"
  39. #include "choreoevent.h"
  40. #include "choreochannel.h"
  41. #include "choreoactor.h"
  42. #include "iscenetokenprocessor.h"
  43. #include "ifaceposersound.h"
  44. #include "snd_audio_source.h"
  45. #include "snd_wave_source.h"
  46. #include "AudioWaveOutput.h"
  47. #include "isoundcombiner.h"
  48. #include "tier0/icommandline.h"
  49. #include <vgui/ILocalize.h>
  50. #include "vgui/ivgui.h"
  51. #include "soundchars.h"
  52. #include "sentence.h"
  53. #include "tier2/riff.h"
  54. #include "utlbuffer.h"
  55. #include "FileSystem_Helpers.h"
  56. #include "pacifier.h"
  57. #include "phonemeextractor/PhonemeExtractor.h"
  58. #include "UnicodeFileHelpers.h"
  59. using namespace vgui;
  60. bool uselogfile = false;
  61. bool regenerate = false;
  62. bool regenerate_quiet = false;
  63. bool regenerate_all = false; // user hit a to y/n/all prompt
  64. bool generate_usage = false;
  65. bool nuke = false;
  66. bool checkscriptsounds = false;
  67. bool build_cc = false;
  68. bool build_script = false;
  69. bool wavcheck = false;
  70. bool extractphonemes = false;
  71. bool checkforloops = false;
  72. bool importcaptions = false;
  73. bool checkfordups = false;
  74. bool makecopybatch = false;
  75. bool syncducking = false;
  76. char sounddir[ 512 ];
  77. char importfile[ 512 ]; // for -i processing
  78. char fromdir[ 512 ];
  79. char todir[ 512 ];
  80. struct AnalysisData
  81. {
  82. CUtlSymbolTable symbols;
  83. };
  84. IFileSystem *filesystem = NULL;
  85. static AnalysisData g_Analysis;
  86. static CUniformRandomStream g_Random;
  87. IUniformRandomStream *random = &g_Random;
  88. static bool spewed = false;
  89. static bool forceextract = false;
  90. #define SOUND_DURATION_TOLERANCE 0.1f // 100 msec of slop
  91. void vprint( int depth, const char *fmt, ... );
  92. //-----------------------------------------------------------------------------
  93. // Purpose:
  94. //-----------------------------------------------------------------------------
  95. void printusage( void )
  96. {
  97. vprint( 0, "usage: localization_check <opts> languagename\n\
  98. \t-v = verbose output\n\
  99. \t-l = log to file log.txt\n\
  100. \t-u = generate usage data for .vcds based on -makereslists maplist.txt files\n\
  101. \t-b = generate nuke.bat which will nuke all of the unreferenced .vcds from your tree\n\
  102. \t-s = generate list of unused sounds.txt entries\n\
  103. \t-c = build cc fixed/fixed2.txt files\n\
  104. \t-r = regenerate missing/mismatched combined wav files\n\
  105. \t\t-q = quiet mode during regenerate\n\
  106. languagename = check combined language files for existence and duration, 'english' for no extra checks\n\
  107. \t-x = build script of dialog from .vcds\n\
  108. \t-w sounddir = spew csv of wave files in directory, including sound and cc info\n\
  109. \t-e sounddir = do textless phoneme processing on files in dir and subdirs\n\
  110. \t-f with above, forces extraction even if wav already has phonemes (danger!)\n\
  111. \t-i = import unicode wavename/caption into a new closecaption_test.txt file\n\
  112. \t-d = check for duplicated unicode strings\n\
  113. \t-p = pull raw english txt out of closecaption document, for spellchecking\n\
  114. \t-m = given a directory of .wav files finds the full directory path they should live in based on english\n\
  115. \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\
  116. \t-loop = warn on any sound files in specified directory having loop markers in the .wav\n\
  117. \ne.g.: localization_check -l -w npc/metropolice/vo -r french\n" );
  118. // Exit app
  119. exit( 1 );
  120. }
  121. //-----------------------------------------------------------------------------
  122. // Purpose: Helper for parsing scene data file
  123. //-----------------------------------------------------------------------------
  124. class CSceneTokenProcessor : public ISceneTokenProcessor
  125. {
  126. public:
  127. const char *CurrentToken( void );
  128. bool GetToken( bool crossline );
  129. bool TokenAvailable( void );
  130. void Error( const char *fmt, ... );
  131. };
  132. //-----------------------------------------------------------------------------
  133. // Purpose:
  134. // Output : const char
  135. //-----------------------------------------------------------------------------
  136. const char *CSceneTokenProcessor::CurrentToken( void )
  137. {
  138. return token;
  139. }
  140. //-----------------------------------------------------------------------------
  141. // Purpose:
  142. // Input : crossline -
  143. // Output : Returns true on success, false on failure.
  144. //-----------------------------------------------------------------------------
  145. bool CSceneTokenProcessor::GetToken( bool crossline )
  146. {
  147. return ::GetToken( crossline ) ? true : false;
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose:
  151. // Output : Returns true on success, false on failure.
  152. //-----------------------------------------------------------------------------
  153. bool CSceneTokenProcessor::TokenAvailable( void )
  154. {
  155. return ::TokenAvailable() ? true : false;
  156. }
  157. static char *va( char const *fmt, ... )
  158. {
  159. static char string[ 2048 ];
  160. va_list argptr;
  161. va_start( argptr, fmt );
  162. Q_vsnprintf( string, sizeof(string), fmt, argptr );
  163. va_end( argptr );
  164. return string;
  165. }
  166. void cleanquotes( char *text )
  167. {
  168. char *out = text;
  169. while ( *out )
  170. {
  171. if ( *out == '\"' )
  172. {
  173. *out++ = '\'';
  174. }
  175. else
  176. {
  177. ++out;
  178. }
  179. }
  180. }
  181. //-----------------------------------------------------------------------------
  182. // Purpose:
  183. // Input : *fmt -
  184. // ... -
  185. //-----------------------------------------------------------------------------
  186. void CSceneTokenProcessor::Error( const char *fmt, ... )
  187. {
  188. char string[ 2048 ];
  189. va_list argptr;
  190. va_start( argptr, fmt );
  191. Q_vsnprintf( string, sizeof(string), fmt, argptr );
  192. va_end( argptr );
  193. Warning( "%s", string );
  194. Assert(0);
  195. }
  196. static CSceneTokenProcessor g_TokenProcessor;
  197. SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg )
  198. {
  199. spewed = true;
  200. printf( "%s", pMsg );
  201. OutputDebugString( pMsg );
  202. if ( type == SPEW_ERROR )
  203. {
  204. printf( "\n" );
  205. OutputDebugString( "\n" );
  206. }
  207. return SPEW_CONTINUE;
  208. }
  209. void logprint( char const *logfile, const char *fmt, ... )
  210. {
  211. char string[ 8192 ];
  212. va_list va;
  213. va_start( va, fmt );
  214. vsprintf( string, fmt, va );
  215. va_end( va );
  216. FILE *fp = NULL;
  217. static bool first = true;
  218. if ( first )
  219. {
  220. first = false;
  221. fp = fopen( logfile, "wb" );
  222. }
  223. else
  224. {
  225. fp = fopen( logfile, "ab" );
  226. }
  227. if ( fp )
  228. {
  229. char *p = string;
  230. while ( *p )
  231. {
  232. if ( *p == '\n' )
  233. {
  234. fputc( '\r', fp );
  235. }
  236. fputc( *p, fp );
  237. p++;
  238. }
  239. fclose( fp );
  240. }
  241. }
  242. void nuke_print( int depth, const char *fmt, ... )
  243. {
  244. char string[ 8192 ];
  245. va_list va;
  246. va_start( va, fmt );
  247. vsprintf( string, fmt, va );
  248. va_end( va );
  249. static bool first = false;
  250. FILE *fp = NULL;
  251. char const *nukefile = "nuke.bat";
  252. if ( first )
  253. {
  254. first = false;
  255. fp = fopen( nukefile, "wb" );
  256. }
  257. else
  258. {
  259. fp = fopen( nukefile, "ab" );
  260. }
  261. while ( depth-- > 0 )
  262. {
  263. fprintf( fp, " " );
  264. }
  265. char *p = string;
  266. while ( *p )
  267. {
  268. if ( *p == '\n' )
  269. {
  270. fputc( '\r', fp );
  271. }
  272. fputc( *p, fp );
  273. p++;
  274. }
  275. fclose( fp );
  276. }
  277. //-----------------------------------------------------------------------------
  278. // Purpose:
  279. // Input : depth -
  280. // *fmt -
  281. // ... -
  282. //-----------------------------------------------------------------------------
  283. void vprint( int depth, const char *fmt, ... )
  284. {
  285. char string[ 8192 ];
  286. va_list va;
  287. va_start( va, fmt );
  288. vsprintf( string, fmt, va );
  289. va_end( va );
  290. FILE *fp = NULL;
  291. if ( uselogfile )
  292. {
  293. fp = fopen( "log.txt", "ab" );
  294. }
  295. while ( depth-- > 0 )
  296. {
  297. printf( " " );
  298. OutputDebugString( " " );
  299. if ( fp )
  300. {
  301. fprintf( fp, " " );
  302. }
  303. }
  304. ::printf( "%s", string );
  305. OutputDebugString( string );
  306. if ( fp )
  307. {
  308. char *p = string;
  309. while ( *p )
  310. {
  311. if ( *p == '\n' )
  312. {
  313. fputc( '\r', fp );
  314. }
  315. fputc( *p, fp );
  316. p++;
  317. }
  318. fclose( fp );
  319. }
  320. }
  321. void Con_Printf( const char *fmt, ... )
  322. {
  323. va_list args;
  324. static char output[1024];
  325. va_start( args, fmt );
  326. Q_vsnprintf( output, sizeof( output ), fmt, args );
  327. va_end( args );
  328. vprint( 0, output );
  329. }
  330. void BuildFileList_R( CUtlVector< CUtlSymbol >& files, char const *dir, char const *extension )
  331. {
  332. WIN32_FIND_DATA wfd;
  333. char directory[ 256 ];
  334. char filename[ MAX_PATH ];
  335. HANDLE ff;
  336. sprintf( directory, "%s\\*.*", dir );
  337. if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE )
  338. return;
  339. int extlen = strlen( extension );
  340. do
  341. {
  342. if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
  343. {
  344. if ( wfd.cFileName[ 0 ] == '.' )
  345. continue;
  346. // Recurse down directory
  347. sprintf( filename, "%s\\%s", dir, wfd.cFileName );
  348. BuildFileList_R( files, filename, extension );
  349. }
  350. else
  351. {
  352. int len = strlen( wfd.cFileName );
  353. if ( len > extlen )
  354. {
  355. if ( !stricmp( &wfd.cFileName[ len - extlen ], extension ) )
  356. {
  357. Q_snprintf( filename, sizeof( filename ), "%s\\%s", dir, wfd.cFileName );
  358. _strlwr( filename );
  359. Q_FixSlashes( filename );
  360. CUtlSymbol sym = g_Analysis.symbols.AddString( filename );
  361. files.AddToTail( sym );
  362. if ( !( files.Count() % 3000 ) )
  363. {
  364. vprint( 0, "...found %i .%s files\n", files.Count(), extension );
  365. }
  366. }
  367. }
  368. }
  369. } while ( FindNextFile( ff, &wfd ) );
  370. }
  371. void BuildFileList( CUtlVector< CUtlSymbol >& files, char const *rootdir, char const *extension )
  372. {
  373. files.RemoveAll();
  374. BuildFileList_R( files, rootdir, extension );
  375. }
  376. //-----------------------------------------------------------------------------
  377. // Purpose:
  378. //-----------------------------------------------------------------------------
  379. void CheckLogFile( void )
  380. {
  381. if ( uselogfile )
  382. {
  383. _unlink( "log.txt" );
  384. vprint( 0, " Outputting to log.txt\n" );
  385. }
  386. }
  387. void PrintHeader()
  388. {
  389. vprint( 0, "Valve Software - localization_check.exe (%s)\n", __DATE__ );
  390. vprint( 0, "--- Voice Wav File .vcd Checker ---\n" );
  391. }
  392. char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender )
  393. {
  394. if ( Q_stristr( soundname, ".wav" ) )
  395. return PSkipSoundChars( soundname );
  396. return PSkipSoundChars( g_pSoundEmitterSystem->GetWavFileForSound( soundname, gender ) );
  397. }
  398. //-----------------------------------------------------------------------------
  399. // Purpose: Implements the RIFF i/o interface on stdio
  400. //-----------------------------------------------------------------------------
  401. class StdIOReadBinary : public IFileReadBinary
  402. {
  403. public:
  404. int open( const char *pFileName )
  405. {
  406. return (int)g_pFullFileSystem->Open( pFileName, "rb" );
  407. }
  408. int read( void *pOutput, int size, int file )
  409. {
  410. if ( !file )
  411. return 0;
  412. return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file );
  413. }
  414. void seek( int file, int pos )
  415. {
  416. if ( !file )
  417. return;
  418. g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
  419. }
  420. unsigned int tell( int file )
  421. {
  422. if ( !file )
  423. return 0;
  424. return g_pFullFileSystem->Tell( (FileHandle_t)file );
  425. }
  426. unsigned int size( int file )
  427. {
  428. if ( !file )
  429. return 0;
  430. return g_pFullFileSystem->Size( (FileHandle_t)file );
  431. }
  432. void close( int file )
  433. {
  434. if ( !file )
  435. return;
  436. g_pFullFileSystem->Close( (FileHandle_t)file );
  437. }
  438. };
  439. class StdIOWriteBinary : public IFileWriteBinary
  440. {
  441. public:
  442. int create( const char *pFileName )
  443. {
  444. g_pFullFileSystem->SetFileWritable( pFileName, true, "GAME" );
  445. return (int)g_pFullFileSystem->Open( pFileName, "wb" );
  446. }
  447. int write( void *pData, int size, int file )
  448. {
  449. return g_pFullFileSystem->Write( pData, size, (FileHandle_t)file );
  450. }
  451. void close( int file )
  452. {
  453. g_pFullFileSystem->Close( (FileHandle_t)file );
  454. }
  455. void seek( int file, int pos )
  456. {
  457. g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
  458. }
  459. unsigned int tell( int file )
  460. {
  461. return g_pFullFileSystem->Tell( (FileHandle_t)file );
  462. }
  463. };
  464. static StdIOWriteBinary io_out;
  465. static StdIOReadBinary io_in;
  466. #define RIFF_WAVE MAKEID('W','A','V','E')
  467. #define WAVE_FMT MAKEID('f','m','t',' ')
  468. #define WAVE_DATA MAKEID('d','a','t','a')
  469. #define WAVE_FACT MAKEID('f','a','c','t')
  470. #define WAVE_CUE MAKEID('c','u','e',' ')
  471. //-----------------------------------------------------------------------------
  472. // Purpose:
  473. // Input : &walk -
  474. //-----------------------------------------------------------------------------
  475. static void ParseSentence( CSentence& sentence, IterateRIFF &walk )
  476. {
  477. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  478. buf.EnsureCapacity( walk.ChunkSize() );
  479. walk.ChunkRead( buf.Base() );
  480. buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
  481. sentence.InitFromDataChunk( buf.Base(), buf.TellPut() );
  482. }
  483. bool LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io, void *formatbuffer = NULL, int* formatsize = NULL, int *datasize = NULL )
  484. {
  485. int insize = 0;
  486. if ( formatsize )
  487. {
  488. insize = *formatsize;
  489. *formatsize = 0;
  490. }
  491. if ( datasize )
  492. {
  493. *datasize = 0;
  494. }
  495. sentence.Reset();
  496. InFileRIFF riff( wavfile, io );
  497. // UNDONE: Don't use printf to handle errors
  498. if ( riff.RIFFName() != RIFF_WAVE )
  499. {
  500. return false;
  501. }
  502. // set up the iterator for the whole file (root RIFF is a chunk)
  503. IterateRIFF walk( riff, riff.RIFFSize() );
  504. // This chunk must be first as it contains the wave's format
  505. // break out when we've parsed it
  506. bool found = false;
  507. while ( walk.ChunkAvailable( ) )
  508. {
  509. switch( walk.ChunkName() )
  510. {
  511. case WAVE_FMT:
  512. {
  513. if ( formatbuffer && formatsize )
  514. {
  515. if ( walk.ChunkSize() <= insize )
  516. {
  517. *formatsize = walk.ChunkSize();
  518. walk.ChunkRead( formatbuffer );
  519. }
  520. else
  521. {
  522. Error( "oops, format tag too big!!!" );
  523. }
  524. }
  525. }
  526. break;
  527. case WAVE_VALVEDATA:
  528. {
  529. found = true;
  530. ParseSentence( sentence, walk );
  531. }
  532. break;
  533. case WAVE_DATA:
  534. {
  535. if ( datasize )
  536. {
  537. *datasize = walk.ChunkSize();
  538. }
  539. }
  540. break;
  541. }
  542. walk.ChunkNext();
  543. }
  544. return true;
  545. }
  546. bool LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence, void *formatbuffer = NULL, int* formatsize = NULL, int *dataSize = NULL )
  547. {
  548. return LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in, formatbuffer, formatsize, dataSize );
  549. }
  550. bool ValidateCombinedFileCheckSum( char const *outfilename, char const *cctoken, gender_t gender, CUtlRBTree< CChoreoEvent * >& sorted, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves )
  551. {
  552. CUtlVector< CombinerEntry > work;
  553. char actualfile[ 512 ];
  554. g_pSoundEmitterSystem->GenderExpandString( gender, outfilename, actualfile, sizeof( actualfile ) );
  555. if ( Q_strlen( actualfile ) <= 0 )
  556. {
  557. return false;
  558. }
  559. int i = sorted.FirstInorder();
  560. if ( i != sorted.InvalidIndex() )
  561. {
  562. CChoreoEvent *e = sorted[ i ];
  563. float startoffset = e->GetStartTime();
  564. do
  565. {
  566. e = sorted[ i ];
  567. float curoffset = e->GetStartTime();
  568. CombinerEntry ce;
  569. Q_snprintf( ce.wavefile, sizeof( ce.wavefile ), "sound/%s", FacePoser_TranslateSoundNameGender( e->GetParameters(), gender ) );
  570. ce.startoffset = curoffset - startoffset;
  571. work.AddToTail( ce );
  572. i = sorted.NextInorder( i );
  573. }
  574. while ( i != sorted.InvalidIndex() );
  575. int c = work.Count();
  576. char worklist[ 2048 ];
  577. worklist[ 0 ] = 0;
  578. for ( i = 0; i < c; ++i )
  579. {
  580. CombinerEntry &item = work[ i ];
  581. Q_strncat( worklist, item.wavefile, sizeof( worklist ), COPY_ALL_CHARACTERS );
  582. if ( i != c - 1 )
  583. {
  584. Q_strncat( worklist, ", ", sizeof( worklist ), COPY_ALL_CHARACTERS );
  585. }
  586. }
  587. logprint( "cc_combined.txt", "combined .wav '%s': %s\n", actualfile, worklist );
  588. }
  589. bool valid = soundcombiner->IsCombinedFileChecksumValid( g_pFullFileSystem, actualfile, work );
  590. if ( !valid )
  591. {
  592. vprint( 0, "combined file (%s) checksum mismatch for '%s'\n event '%s' of scene '%s'\n", actualfile, cctoken,
  593. sorted[0]->GetName(), sorted[0]->GetScene()->GetFilename() );
  594. if ( regenerate )
  595. {
  596. bool dothisfile = false;
  597. if ( !regenerate_all )
  598. {
  599. vprint( 0, "Regenerate '%s'? (Yes/No/All)", actualfile );
  600. char ch = getch();
  601. if ( ch == 'y' || ch == 'Y' )
  602. {
  603. dothisfile = true;
  604. }
  605. if ( ch == 'a' || ch == 'A' )
  606. {
  607. regenerate_all = true;
  608. dothisfile = true;
  609. }
  610. vprint( 0, "\n" );
  611. }
  612. else
  613. {
  614. dothisfile = true;
  615. }
  616. if ( dothisfile )
  617. {
  618. bool success = soundcombiner->CombineSoundFiles( g_pFullFileSystem, actualfile, work );
  619. vprint( 1, "%s: %s\n", actualfile, success ? "succeeded" : "FAILED" );
  620. }
  621. }
  622. }
  623. else
  624. {
  625. if ( regenerate )
  626. {
  627. vprint( 0, "combined file (%s) checksum still matches for %s, skipping rebuild...\n", actualfile, cctoken );
  628. }
  629. }
  630. // Mark the file as referenced
  631. //
  632. char fn[ 512 ];
  633. Q_snprintf( fn, sizeof( fn ), "%s%s", gamedir, actualfile );
  634. _strlwr( fn );
  635. Q_FixSlashes( fn );
  636. CUtlSymbol sym = g_Analysis.symbols.AddString( fn );
  637. if ( referencedcaptionwaves.Find( sym ) == referencedcaptionwaves.InvalidIndex() )
  638. {
  639. referencedcaptionwaves.Insert( sym );
  640. }
  641. return valid;
  642. }
  643. static bool EventStartTimeLessFunc( CChoreoEvent * const &p1, CChoreoEvent * const &p2 )
  644. {
  645. CChoreoEvent *w1;
  646. CChoreoEvent *w2;
  647. w1 = const_cast< CChoreoEvent * >( p1 );
  648. w2 = const_cast< CChoreoEvent * >( p2 );
  649. return w1->GetStartTime() < w2->GetStartTime();
  650. }
  651. static bool SymbolLessFunc( const CUtlSymbol & p1, const CUtlSymbol &p2 )
  652. {
  653. if ( Q_stricmp( g_Analysis.symbols.String( p1 ), g_Analysis.symbols.String( p2 ) ) < 0 )
  654. return true;
  655. return false;
  656. }
  657. bool ValidateCombinedSoundCheckSum( CChoreoEvent *e, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves )
  658. {
  659. if ( !e || e->GetType() != CChoreoEvent::SPEAK )
  660. return false;
  661. bool genderwildcard = e->IsCombinedUsingGenderToken();
  662. char outfilename[ 512 ];
  663. Q_memset( outfilename, 0, sizeof( outfilename ) );
  664. if ( !e->ComputeCombinedBaseFileName( outfilename, sizeof( outfilename ), genderwildcard ) )
  665. {
  666. vprint( 0, "Unable to regenerate wav file name for combined sound (%s)\n", e->GetCloseCaptionToken() );
  667. return false;
  668. }
  669. bool checksumvalid = false;
  670. CUtlRBTree< CChoreoEvent * > eventList( 0, 0, EventStartTimeLessFunc );
  671. if ( !e->GetChannel()->GetSortedCombinedEventList( e->GetCloseCaptionToken(), eventList ) )
  672. {
  673. vprint( 0, "Unable to generated combined event list (%s)\n", e->GetCloseCaptionToken() );
  674. return false;
  675. }
  676. if ( genderwildcard )
  677. {
  678. checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_MALE, eventList, referencedcaptionwaves );
  679. checksumvalid &= ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_FEMALE, eventList, referencedcaptionwaves );
  680. }
  681. else
  682. {
  683. checksumvalid = ValidateCombinedFileCheckSum( outfilename, e->GetCloseCaptionToken(), GENDER_NONE, eventList, referencedcaptionwaves );
  684. }
  685. return checksumvalid;
  686. }
  687. struct PerMapVCDS
  688. {
  689. PerMapVCDS()
  690. {
  691. }
  692. PerMapVCDS( const PerMapVCDS& src )
  693. {
  694. int i = src.vcds.FirstInorder();
  695. while ( i != src.vcds.InvalidIndex() )
  696. {
  697. vcds.Insert( src.vcds[ i ] );
  698. i = src.vcds.NextInorder( i );
  699. }
  700. }
  701. class CTree : public CUtlRBTree< CUtlSymbol >
  702. {
  703. public:
  704. CTree()
  705. : CUtlRBTree< CUtlSymbol >( 0, 0, DefLessFunc( CUtlSymbol ) )
  706. {
  707. }
  708. CTree &operator=( const CTree &from )
  709. {
  710. CopyFrom( from );
  711. return *this;
  712. }
  713. };
  714. CTree vcds;
  715. };
  716. CUtlDict< PerMapVCDS, int > g_PerMapVCDS;
  717. CUtlDict< CUtlSymbol, int > g_FirstMapForVCD;
  718. void ParseVCDFilesFromResList( CUtlVector< CUtlSymbol >& vcdsinreslist, char const *resfile )
  719. {
  720. char gd[ 256 ];
  721. Q_strncpy( gd, gamedir, sizeof( gd ) );
  722. Q_StripTrailingSlash( gd );
  723. _strlwr( gd );
  724. Q_FixSlashes( gd );
  725. int gdlen = strlen( gd );
  726. char resbase[ 512 ];
  727. Q_FileBase( resfile, resbase, sizeof( resbase ) );
  728. int addedStrings = 0;
  729. int resourcesConsidered = 0;
  730. FileHandle_t resfilehandle;
  731. resfilehandle = g_pFullFileSystem->Open( resfile, "rb" );
  732. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  733. {
  734. // Read in the entire file
  735. int length = g_pFullFileSystem->Size(resfilehandle);
  736. if ( length > 0 )
  737. {
  738. char *pStart = (char *)new char[ length + 1 ];
  739. if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) )
  740. )
  741. {
  742. pStart[ length ] = 0;
  743. char *pFileList = pStart;
  744. char tokenFile[512];
  745. while ( 1 )
  746. {
  747. pFileList = ParseFile( pFileList, tokenFile, NULL );
  748. if ( !pFileList )
  749. break;
  750. if ( strlen( tokenFile ) > 0 )
  751. {
  752. char szFileName[ 256 ];
  753. Q_strncpy( szFileName, tokenFile, sizeof( szFileName ) );
  754. _strlwr( szFileName );
  755. Q_FixSlashes( szFileName );
  756. while ( szFileName[ strlen( szFileName ) - 1 ] == '\n' ||
  757. szFileName[ strlen( szFileName ) - 1 ] == '\r' )
  758. {
  759. szFileName[ strlen( szFileName ) - 1 ] = 0;
  760. }
  761. char *pFile = szFileName;
  762. if ( !Q_strnicmp( szFileName, gd, gdlen ) )
  763. {
  764. pFile = szFileName + gdlen + 1;
  765. }
  766. else
  767. {
  768. // Ack
  769. //vprint( 1, "File %s not under game directory but in reslist, skipping!!!\n", szFileName );
  770. pFileList = ParseFile( pFileList, tokenFile, NULL );
  771. continue;
  772. }
  773. ++resourcesConsidered;
  774. // Is it a .vcd?
  775. if ( !Q_stristr( pFile, ".vcd" ) )
  776. continue;
  777. char symname[ 512 ];
  778. Q_snprintf( symname, sizeof( symname ), "%s%s", gamedir, pFile );
  779. _strlwr( symname );
  780. Q_FixSlashes( symname );
  781. CUtlSymbol sym = g_Analysis.symbols.AddString( symname );
  782. int idx = vcdsinreslist.Find( sym );
  783. if ( idx == vcdsinreslist.InvalidIndex() )
  784. {
  785. ++addedStrings;
  786. // This is the first time this vcd was encountered, remember which map we are in
  787. PerMapVCDS e;
  788. e.vcds.Insert( sym );
  789. g_PerMapVCDS.Insert( resbase, e );
  790. CUtlSymbol mapsym = g_Analysis.symbols.AddString( resbase );
  791. g_FirstMapForVCD.Insert( symname, mapsym );
  792. vcdsinreslist.AddToTail( sym );
  793. }
  794. }
  795. }
  796. }
  797. delete[] pStart;
  798. }
  799. g_pFullFileSystem->Close(resfilehandle);
  800. }
  801. // int filesFound = addedStrings;
  802. // vprint( 1, "\rFound %i new resources (%7i total) in %64s", filesFound, resourcesConsidered, resfile );
  803. }
  804. #define MAPLIST_FILE "maplist.txt"
  805. void AddFileToList( CUtlVector< CUtlSymbol >& list, char const *filename )
  806. {
  807. char fn[ 512 ];
  808. Q_strncpy( fn, filename, sizeof( fn ) );
  809. _strlwr( fn );
  810. Q_FixSlashes( fn );
  811. CUtlSymbol sym = g_Analysis.symbols.AddString( fn );
  812. list.AddToTail( sym );
  813. }
  814. void BuildVCDAndMapNameListsFromReslists( CUtlVector< CUtlSymbol >& vcdsinreslist )
  815. {
  816. // Load all .rst files in the reslists folder
  817. CUtlVector< CUtlSymbol > reslists;
  818. // If maplist.txt exists, use it, otherwise
  819. bool loaded = false;
  820. if ( g_pFullFileSystem->FileExists( MAPLIST_FILE ) )
  821. {
  822. // Parse the true list from the maplist.txt file
  823. // and add engine.lst and all.lst at the very end
  824. // Load them in
  825. FileHandle_t resfilehandle;
  826. resfilehandle = g_pFullFileSystem->Open( MAPLIST_FILE, "rb" );
  827. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  828. {
  829. // Read in and parse mapcycle.txt
  830. int length = g_pFullFileSystem->Size(resfilehandle);
  831. if ( length > 0 )
  832. {
  833. char *pStart = (char *)new char[ length + 1 ];
  834. if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) )
  835. )
  836. {
  837. pStart[ length ] = 0;
  838. const char *pFileList = pStart;
  839. while ( 1 )
  840. {
  841. char szMap[ 512 ];
  842. pFileList = ParseFile( pFileList, com_token, NULL );
  843. if ( strlen( com_token ) <= 0 )
  844. break;
  845. Q_strncpy(szMap, com_token, sizeof(szMap));
  846. // Any more tokens on this line?
  847. //while ( TokenWaiting( pFileList ) )
  848. //{
  849. // pFileList = ParseFile( pFileList, com_token, NULL );
  850. //}
  851. char fn[ 512 ];
  852. Q_snprintf( fn, sizeof( fn ), "%sreslists/%s.lst", gamedir, szMap );
  853. AddFileToList( reslists, fn );
  854. }
  855. }
  856. delete[] pStart;
  857. AddFileToList( reslists, va( "%sreslists/engine.lst", gamedir ) );
  858. AddFileToList( reslists, va( "%sreslists/all.lst", gamedir ) );
  859. loaded = true;
  860. }
  861. g_pFullFileSystem->Close(resfilehandle);
  862. }
  863. }
  864. if ( !loaded )
  865. {
  866. char reslistdir[ 512 ];
  867. Q_snprintf( reslistdir, sizeof( reslistdir ), "%sreslists", gamedir );
  868. BuildFileList_R( reslists, reslistdir, ".lst" );
  869. }
  870. int c = reslists.Count();
  871. StartPacifier( "ParseVCDFilesFromResList: " );
  872. for ( int i = 0; i < c; ++i )
  873. {
  874. UpdatePacifier( (float)( i + 1 ) / (float)c );
  875. ParseVCDFilesFromResList( vcdsinreslist, g_Analysis.symbols.String( reslists[ i ] ) );
  876. }
  877. EndPacifier( true );
  878. }
  879. void CheckUnusedVcds( CUtlVector< CUtlSymbol >& vcdsinreslist, CUtlVector< CUtlSymbol >& vcdfiles )
  880. {
  881. vprint( 1, "Checking for orphaned vcd files\n" );
  882. // For each reslist, load in the filenames, looking for .vcds
  883. vprint( 1, "Found %i .vcd files referenced (%i total)\n", vcdsinreslist.Count(), vcdfiles.Count() );
  884. // For each vcd in the min list, see if it's in the sublist
  885. int i;
  886. int c = vcdfiles.Count();
  887. int invalid_index = vcdsinreslist.InvalidIndex();
  888. int unrefcount = 0;
  889. for ( i = 0; i < c; ++i )
  890. {
  891. CUtlSymbol& sym = vcdfiles[ i ];
  892. if ( vcdsinreslist.Find( sym ) == invalid_index )
  893. {
  894. ++unrefcount;
  895. vprint( 1, " unref .vcd: %s\n", g_Analysis.symbols.String( sym ) );
  896. if ( nuke )
  897. {
  898. nuke_print( 0, "del %s /f\n", g_Analysis.symbols.String( sym ) );
  899. }
  900. }
  901. }
  902. // For each reslist, load in the filenames, looking for .vcds
  903. vprint( 1, "Found %i unreferenced vcds (%i total)\n", unrefcount, vcdfiles.Count() );
  904. }
  905. void ParseUsedSoundsFromSndFile( CUtlRBTree< int, int >& usedsounds, char const *sndfile )
  906. {
  907. char gd[ 256 ];
  908. Q_strncpy( gd, gamedir, sizeof( gd ) );
  909. Q_StripTrailingSlash( gd );
  910. _strlwr( gd );
  911. Q_FixSlashes( gd );
  912. int addedStrings = 0;
  913. int resourcesConsidered = 0;
  914. FileHandle_t resfilehandle;
  915. resfilehandle = g_pFullFileSystem->Open( sndfile, "rb" );
  916. if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
  917. {
  918. // Read in the entire file
  919. int length = g_pFullFileSystem->Size(resfilehandle);
  920. if ( length > 0 )
  921. {
  922. char *pStart = (char *)new char[ length + 1 ];
  923. if ( pStart && ( length == g_pFullFileSystem->Read(pStart, length, resfilehandle) )
  924. )
  925. {
  926. pStart[ length ] = 0;
  927. char *pFileList = pStart;
  928. char tokenFile[512];
  929. while ( 1 )
  930. {
  931. pFileList = ParseFile( pFileList, tokenFile, NULL );
  932. if ( !pFileList )
  933. break;
  934. if ( strlen( tokenFile ) > 0 )
  935. {
  936. char soundname[ 256 ];
  937. Q_strncpy( soundname, tokenFile, sizeof( soundname ) );
  938. _strlwr( soundname );
  939. ++resourcesConsidered;
  940. int index = g_pSoundEmitterSystem->GetSoundIndex( soundname );
  941. if ( !g_pSoundEmitterSystem->IsValidIndex( index ) )
  942. {
  943. vprint( 1, "---> Sound %s doesn't exist in g_pSoundEmitterSystemsystem!!!\n", soundname );
  944. continue;
  945. }
  946. int idx = usedsounds.Find( index );
  947. if ( idx == usedsounds.InvalidIndex() )
  948. {
  949. ++addedStrings;
  950. usedsounds.Insert( index );
  951. }
  952. }
  953. }
  954. }
  955. delete[] pStart;
  956. }
  957. g_pFullFileSystem->Close(resfilehandle);
  958. }
  959. vprint( 1, "Found %i new resources (%i total) in %s\n", addedStrings, resourcesConsidered, sndfile );
  960. }
  961. bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args );
  962. void SpewDuplicatedText( char const *lang, const char *entry, const wchar_t *str )
  963. {
  964. const wchar_t *curpos = str;
  965. wchar_t cleaned[ 4096 ];
  966. wchar_t *out = cleaned;
  967. for ( ; curpos && *curpos != L'\0'; ++curpos )
  968. {
  969. wchar_t cmd[ 256 ];
  970. wchar_t args[ 256 ];
  971. if ( SplitCommand( &curpos, cmd, args ) )
  972. {
  973. continue;
  974. }
  975. // Only copy non command, non-whitespace characters
  976. if ( iswspace( *curpos ) )
  977. {
  978. continue;
  979. }
  980. *out++ = *curpos;
  981. }
  982. *out = L'\0';
  983. int len = wcslen( cleaned );
  984. if ( len < 5 )
  985. return;
  986. // Now see how many characters from the first 50% of the text are also in the second 50%
  987. int halflen = len / 2;
  988. int foundcount = 0;
  989. for ( int i = 0; i < halflen; ++i )
  990. {
  991. wchar_t ch[3];
  992. ch[0] = cleaned[ i ];
  993. ch[1] = cleaned[ i + 1 ];
  994. ch[2] = L'\0';
  995. if ( wcsstr( &cleaned[ halflen ], ch ) )
  996. {
  997. ++foundcount;
  998. }
  999. }
  1000. if ( foundcount > 0.7 * halflen )
  1001. {
  1002. logprint( "cc_duplicatedtext.txt", "%s: Suspect token %s\n", lang, entry );
  1003. }
  1004. }
  1005. void CheckDuplcatedText( void )
  1006. {
  1007. g_pFullFileSystem->RemoveFile( "cc_duplicatedtext.txt", "GAME" );
  1008. for ( int lang = 0; lang < CC_NUM_LANGUAGES; ++lang )
  1009. {
  1010. char language[ 256 ];
  1011. Q_strncpy( language, CSentence::NameForLanguage( lang ), sizeof( language ) );
  1012. vprint( 0, "adding langauge file for '%s'\n", language );
  1013. g_pVGuiLocalize->AddFile( "resource/closecaption_english.txt" );
  1014. char fn[ 256 ];
  1015. Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", language );
  1016. g_pVGuiLocalize->AddFile( fn );
  1017. // Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them
  1018. StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex();
  1019. while ( str != INVALID_LOCALIZE_STRING_INDEX )
  1020. {
  1021. char const *keyname = g_pVGuiLocalize->GetNameByIndex( str );
  1022. if ( keyname )
  1023. {
  1024. const wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str );
  1025. SpewDuplicatedText( language, keyname, value );
  1026. }
  1027. str = g_pVGuiLocalize->GetNextStringIndex( str );
  1028. }
  1029. }
  1030. }
  1031. static bool IsAllSpaces( const wchar_t *stream )
  1032. {
  1033. const wchar_t *p = stream;
  1034. while ( *p != L'\0' )
  1035. {
  1036. if ( !iswspace( *p ) )
  1037. return false;
  1038. p++;
  1039. }
  1040. return true;
  1041. }
  1042. void SpewEnglishText( const wchar_t *str )
  1043. {
  1044. const wchar_t *curpos = str;
  1045. wchar_t cleaned[ 4096 ];
  1046. wchar_t *out = cleaned;
  1047. for ( ; curpos && *curpos != L'\0'; ++curpos )
  1048. {
  1049. wchar_t cmd[ 256 ];
  1050. wchar_t args[ 256 ];
  1051. if ( SplitCommand( &curpos, cmd, args ) )
  1052. {
  1053. continue;
  1054. }
  1055. *out++ = *curpos;
  1056. }
  1057. *out = L'\0';
  1058. if ( IsAllSpaces( cleaned ) )
  1059. return;
  1060. char ansi[ 4096 ];
  1061. g_pVGuiLocalize->ConvertUnicodeToANSI( cleaned, ansi, sizeof( ansi ) );
  1062. logprint( "cc_english.txt", "\"%s\"\n", ansi );
  1063. }
  1064. void ExtractEnglish()
  1065. {
  1066. g_pFullFileSystem->RemoveFile( "cc_english.txt", "GAME" );
  1067. // Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them
  1068. StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex();
  1069. while ( str != INVALID_LOCALIZE_STRING_INDEX )
  1070. {
  1071. char const *keyname = g_pVGuiLocalize->GetNameByIndex( str );
  1072. if ( keyname )
  1073. {
  1074. const wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str );
  1075. SpewEnglishText( value );
  1076. }
  1077. str = g_pVGuiLocalize->GetNextStringIndex( str );
  1078. }
  1079. }
  1080. void CheckUnusedSounds()
  1081. {
  1082. vprint( 1, "Checking for unused sounds.txt entries\n" );
  1083. CUtlRBTree< int, int > usedsounds( 0, 0, DefLessFunc(int) );
  1084. // Load all .snd files in the reslists folder
  1085. CUtlVector< CUtlSymbol > sndlists;
  1086. char reslistdir[ 512 ];
  1087. Q_snprintf( reslistdir, sizeof( reslistdir ), "%sreslists", gamedir );
  1088. BuildFileList_R( sndlists, reslistdir, ".snd" );
  1089. int c = sndlists.Count();
  1090. for ( int i = 0; i < c; ++i )
  1091. {
  1092. ParseUsedSoundsFromSndFile( usedsounds, g_Analysis.symbols.String( sndlists[ i ] ) );
  1093. }
  1094. // For each reslist, load in the filenames, looking for .vcds
  1095. vprint( 1, "Found %i unique sounds referenced\n", usedsounds.Count() );
  1096. // For each vcd in the min list, see if it's in the sublist
  1097. c = g_pSoundEmitterSystem->GetSoundCount();
  1098. int unrefcount = 0;
  1099. int invalidindex = usedsounds.InvalidIndex();
  1100. CUtlRBTree< int, int > usedscripts( 0, 0, DefLessFunc(int) );
  1101. for ( int i = 0; i < c; ++i )
  1102. {
  1103. int slot = usedsounds.Find( i );
  1104. if ( invalidindex == slot )
  1105. {
  1106. ++unrefcount;
  1107. char const *soundname = g_pSoundEmitterSystem->GetSoundName( i );
  1108. vprint( 1, " unref: %s : %s\n", soundname, g_pSoundEmitterSystem->GetSourceFileForSound( i ) );
  1109. }
  1110. else
  1111. {
  1112. int scriptindex = g_pSoundEmitterSystem->FindSoundScript( g_pSoundEmitterSystem->GetSourceFileForSound( i ) );
  1113. if ( scriptindex != -1 )
  1114. {
  1115. slot = usedscripts.Find( scriptindex );
  1116. if ( usedscripts.InvalidIndex() == slot )
  1117. {
  1118. usedscripts.Insert( scriptindex );
  1119. }
  1120. }
  1121. }
  1122. }
  1123. // For each reslist, load in the filenames, looking for .vcds
  1124. vprint( 1, "Found %i unreferenced sounds (%i total)\n", unrefcount, c );
  1125. c = g_pSoundEmitterSystem->GetNumSoundScripts();
  1126. for ( int i = 0; i < c; ++i )
  1127. {
  1128. char const *scriptname = g_pSoundEmitterSystem->GetSoundScriptName( i );
  1129. int slot = usedscripts.Find( i );
  1130. if ( usedscripts.InvalidIndex() == slot )
  1131. {
  1132. vprint( 1, " No sounds fron script %s are being used, should delete from manifest!!!\n", scriptname );
  1133. }
  1134. }
  1135. if ( !build_cc )
  1136. return;
  1137. g_pFullFileSystem->RemoveFile( "fixed.txt", "GAME" );
  1138. g_pFullFileSystem->RemoveFile( "fixed2.txt", "GAME" );
  1139. g_pFullFileSystem->RemoveFile( "todo.csv", "GAME" );
  1140. g_pFullFileSystem->RemoveFile( "cc_add.txt", "GAME" );
  1141. g_pFullFileSystem->RemoveFile( "cc_delete.txt", "GAME" );
  1142. g_pFullFileSystem->RemoveFile( "cc_foundphonemes.txt", "GAME" );
  1143. g_pFullFileSystem->RemoveFile( "cc_combined.txt", "GAME" );
  1144. logprint( "todo.csv", "\"CC_TOKEN\",\"TEXT\",\"WAVE FILE\"\n" );
  1145. // Now check for closecaption_xxx.txt entries which are orphaned because there isn't an existing sound script entry in use for them
  1146. StringIndex_t str = g_pVGuiLocalize->GetFirstStringIndex();
  1147. while ( str != INVALID_LOCALIZE_STRING_INDEX )
  1148. {
  1149. char const *keyname = g_pVGuiLocalize->GetNameByIndex( str );
  1150. if ( keyname )
  1151. {
  1152. wchar_t *value = g_pVGuiLocalize->GetValueByIndex( str );
  1153. char ansi[ 512 ];
  1154. g_pVGuiLocalize->ConvertUnicodeToANSI( value, ansi, sizeof( ansi ) );
  1155. // See if key exists in g_pSoundEmitterSystem system
  1156. int soundindex = g_pSoundEmitterSystem->GetSoundIndex( keyname );
  1157. if( soundindex == -1 )
  1158. {
  1159. vprint( 1, " cc token %s not in current g_pSoundEmitterSystem scripts\n", keyname );
  1160. // Just write it back out as is...
  1161. logprint( "fixed2.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
  1162. }
  1163. else
  1164. {
  1165. // See if it's referenced
  1166. int slot = usedsounds.Find( soundindex );
  1167. if ( usedsounds.InvalidIndex() == slot )
  1168. {
  1169. vprint( 1, " cc token %s exists, but the sound is not used by the game\n", keyname );
  1170. logprint( "cc_delete.txt", "\"%s\"\n", keyname );
  1171. }
  1172. else
  1173. {
  1174. // Now try to find a better bit of text
  1175. CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex );
  1176. if ( internal && internal->NumSoundNames() > 0 )
  1177. {
  1178. CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
  1179. char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave );
  1180. if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) )
  1181. {
  1182. // See if 1) it's marked as !!! and try to figure out the text from .wav files...
  1183. if ( !Q_strnicmp( ansi, "!!!", 3 ) )
  1184. {
  1185. CSentence sentence;
  1186. if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
  1187. {
  1188. if ( Q_strlen( sentence.GetText() ) > 0 )
  1189. {
  1190. Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() );
  1191. cleanquotes( ansi );
  1192. logprint( "cc_foundphonemes.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
  1193. }
  1194. }
  1195. }
  1196. logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
  1197. for ( int w = 0; w < internal->NumSoundNames() ; ++w )
  1198. {
  1199. wavname = g_pSoundEmitterSystem->GetWaveName( internal->GetSoundNames()[ w ].symbol );
  1200. logprint( "todo.csv", "\"%s\",\"%s\",\"%s\"\n",
  1201. keyname, ansi, va( "sound/%s", PSkipSoundChars( wavname ) ) );
  1202. }
  1203. }
  1204. }
  1205. else
  1206. {
  1207. logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", keyname, ansi );
  1208. }
  1209. }
  1210. }
  1211. }
  1212. str = g_pVGuiLocalize->GetNextStringIndex( str );
  1213. }
  1214. // Now walk through all of the sounds that were used, but not in the localization file and and those, too
  1215. c = g_pSoundEmitterSystem->GetSoundCount();
  1216. for ( int i = 0; i < c; ++i )
  1217. {
  1218. int slot = usedsounds.Find( i );
  1219. if ( usedsounds.InvalidIndex() == slot )
  1220. continue;
  1221. char const *soundname = g_pSoundEmitterSystem->GetSoundName( i );
  1222. // See if it exists in the localization file
  1223. wchar_t *text = g_pVGuiLocalize->Find( soundname );
  1224. if ( text )
  1225. {
  1226. continue;
  1227. }
  1228. else
  1229. {
  1230. char ansi[ 512 ];
  1231. Q_snprintf( ansi, sizeof( ansi ), "!!!%s", soundname );
  1232. // Now try to find a better bit of text
  1233. CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( i );
  1234. if ( internal && internal->NumSoundNames() > 0 )
  1235. {
  1236. CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
  1237. char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave );
  1238. if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) )
  1239. {
  1240. CSentence sentence;
  1241. if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
  1242. {
  1243. if ( Q_strlen( sentence.GetText() ) > 0 )
  1244. {
  1245. Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() );
  1246. cleanquotes( ansi );
  1247. }
  1248. }
  1249. // Add an entry for stuff in vo/
  1250. logprint( "fixed.txt", "\t\"%s\"\t\t\"%s\"\n", soundname, ansi );
  1251. for ( int w = 0; w < internal->NumSoundNames() ; ++w )
  1252. {
  1253. wavname = g_pSoundEmitterSystem->GetWaveName( internal->GetSoundNames()[ w ].symbol );
  1254. logprint( "todo.csv", "\"%s\",\"%s\",\"%s\"\n",
  1255. soundname, ansi, va( "sound/%s", PSkipSoundChars( wavname ) ) );
  1256. }
  1257. logprint( "cc_add.txt", "\"%s\"\n", soundname );
  1258. }
  1259. }
  1260. }
  1261. }
  1262. }
  1263. // Removes commas from text
  1264. void RemoveCommas( char *in )
  1265. {
  1266. char *out = in;
  1267. while ( out && *out )
  1268. {
  1269. if ( *in == ',' )
  1270. {
  1271. *out++ = ';';
  1272. in++;
  1273. }
  1274. else
  1275. {
  1276. *out++ = *in++;
  1277. }
  1278. }
  1279. *out = 0;
  1280. }
  1281. void SpewScript( char const *vcdname, CUtlRBTree< CChoreoEvent *, int >& list )
  1282. {
  1283. if ( !build_script )
  1284. return;
  1285. if ( list.Count() == 0 )
  1286. return;
  1287. logprint( "script.txt", "VCD( %s )\n\n", vcdname );
  1288. for ( int i = list.FirstInorder(); i != list.InvalidIndex(); i = list.NextInorder( i ) )
  1289. {
  1290. CChoreoEvent *e = list[ i ];
  1291. if ( e->GetCloseCaptionType() != CChoreoEvent::CC_MASTER )
  1292. {
  1293. continue;
  1294. }
  1295. char actorname[ 512 ];
  1296. if ( e->GetActor() )
  1297. {
  1298. Q_strncpy( actorname, e->GetActor()->GetName(), sizeof( actorname ) );
  1299. _strupr( actorname );
  1300. }
  1301. else
  1302. {
  1303. Q_strncpy( actorname, "(NULL ACTOR)", sizeof( actorname ) );
  1304. }
  1305. logprint( "script.txt", "\t\t\t%s\n", actorname);
  1306. // Now try to find a better bit of text
  1307. char wavname[ 512 ];
  1308. wavname[ 0 ] = 0;
  1309. char sentence_text[ 1024 ];
  1310. sentence_text[ 0 ] = 0;
  1311. int soundindex = g_pSoundEmitterSystem->GetSoundIndex( e->GetParameters() );
  1312. if ( soundindex != -1 )
  1313. {
  1314. CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex );
  1315. if ( internal && internal->NumSoundNames() > 0 )
  1316. {
  1317. CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
  1318. char const *pname = g_pSoundEmitterSystem->GetWaveName( symwave );
  1319. if ( pname && ( Q_stristr( pname, "vo/" ) || Q_stristr( pname, "vo\\" ) || Q_stristr( pname, "combined" ) ) )
  1320. {
  1321. Q_strncpy( wavname, pname, sizeof( wavname ) );
  1322. // Convert to regular text
  1323. logprint( "script.txt", "\t\t\t\twav(%s)\n", wavname );
  1324. CSentence sentence;
  1325. if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
  1326. {
  1327. if ( Q_strlen( sentence.GetText() ) > 0 )
  1328. {
  1329. Q_snprintf( sentence_text, sizeof( sentence_text ), "%s", sentence.GetText() );
  1330. cleanquotes( sentence_text );
  1331. }
  1332. }
  1333. }
  1334. }
  1335. }
  1336. char tok[ 256 ];
  1337. Q_strncpy( tok, e->GetParameters(), sizeof( tok ) );
  1338. char ansi[ 2048 ];
  1339. ansi[ 0 ] = 0;
  1340. wchar_t *str = g_pVGuiLocalize->Find( tok );
  1341. if ( !str )
  1342. {
  1343. logprint( "script.txt", "\t\tMissing token '%s' event '%s'\n\n", tok, e->GetName() );
  1344. Q_snprintf( ansi, sizeof( ansi ), "missing '%s' for '%s'", tok, e->GetName() );
  1345. }
  1346. else
  1347. {
  1348. if ( !wcsncmp( str, L"!!!", wcslen( L"!!!" ) ) )
  1349. {
  1350. logprint( "script.txt", "\t\t'%s': event '%s'\n\n", tok, e->GetName() );
  1351. Q_snprintf( ansi, sizeof( ansi ), "!!! '%s' for '%s'", tok, e->GetName() );
  1352. }
  1353. else
  1354. {
  1355. g_pVGuiLocalize->ConvertUnicodeToANSI( str, ansi, sizeof( ansi ) );
  1356. // Convert to regular text
  1357. logprint( "script.txt", "\t\t\t\tcc_token(%s)\n\n\t\t\"%s\"\n\n", tok, ansi );
  1358. }
  1359. }
  1360. // Now spit out the CSV version...
  1361. RemoveCommas( actorname );
  1362. RemoveCommas( tok );
  1363. RemoveCommas( ansi );
  1364. char mapname[ 512 ];
  1365. mapname[ 0 ] = 0;
  1366. int idx = g_FirstMapForVCD.Find( vcdname );
  1367. if ( idx != g_FirstMapForVCD.InvalidIndex() )
  1368. {
  1369. Q_strncpy( mapname, g_Analysis.symbols.String( g_FirstMapForVCD[ idx ] ), sizeof( mapname ) );
  1370. }
  1371. static unsigned int sortindex = 0;
  1372. logprint( "script.csv", "%u,%s,%s,%s,%6.3f,%s,%s,\"%s\",\"%s\"\n",
  1373. sortindex++, mapname, vcdname, actorname, e->GetStartTime(), tok, wavname, ansi, sentence_text );
  1374. }
  1375. }
  1376. void CheckLocalizationEntries( CUtlVector< CUtlSymbol >& vcdfiles, CUtlRBTree< CUtlSymbol, int >& referencedcaptionwaves )
  1377. {
  1378. int disabledcount = 0;
  1379. int validcount = 0;
  1380. int missingcount = 0;
  1381. int wavfile = 0;
  1382. int gamedirskip = Q_strlen( gamedir );
  1383. if ( build_script )
  1384. {
  1385. g_pFullFileSystem->RemoveFile( "script.txt", "GAME" );
  1386. g_pFullFileSystem->RemoveFile( "script.csv", "GAME" );
  1387. }
  1388. int c = vcdfiles.Count();
  1389. for ( int i = 0; i < c; ++i )
  1390. {
  1391. CUtlSymbol& vcdname = vcdfiles[ i ];
  1392. CUtlRBTree< CChoreoEvent *, int > sortedSpeakEvents( 0, 0, EventStartTimeLessFunc );
  1393. // Load the .vcd
  1394. char fullname[ 512 ];
  1395. Q_snprintf( fullname, sizeof( fullname ), "%s", g_Analysis.symbols.String( vcdname ) );
  1396. LoadScriptFile( fullname );
  1397. CChoreoScene *scene = ChoreoLoadScene( fullname, NULL, &g_TokenProcessor, Con_Printf );
  1398. if ( !scene )
  1399. {
  1400. vprint( 0, "Warning: Unable to load %s\n", fullname );
  1401. continue;
  1402. }
  1403. // Now iterate the events looking for speak events
  1404. int numevents = scene->GetNumEvents();
  1405. for ( int j = 0; j < numevents; j++ )
  1406. {
  1407. CChoreoEvent *e = scene->GetEvent( j );
  1408. if ( e->GetType() != CChoreoEvent::SPEAK )
  1409. continue;
  1410. if ( e->GetCloseCaptionType() == CChoreoEvent::CC_DISABLED )
  1411. {
  1412. ++disabledcount;
  1413. continue;
  1414. }
  1415. if ( build_script )
  1416. {
  1417. if ( sortedSpeakEvents.Find( e ) == sortedSpeakEvents.InvalidIndex() )
  1418. {
  1419. sortedSpeakEvents.Insert( e );
  1420. }
  1421. }
  1422. char tok[ 256 ];
  1423. for ( int pass = 0; pass <= 1; ++pass )
  1424. {
  1425. bool iscombined = false;
  1426. if ( pass == 0 )
  1427. {
  1428. Q_strncpy( tok, e->GetParameters(), sizeof( tok ) );
  1429. }
  1430. else
  1431. {
  1432. if ( e->GetCloseCaptionType() != CChoreoEvent::CC_MASTER )
  1433. continue;
  1434. if ( e->GetNumSlaves() <= 0 )
  1435. continue;
  1436. if ( !e->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) )
  1437. {
  1438. ++missingcount;
  1439. continue;;
  1440. }
  1441. iscombined = true;
  1442. }
  1443. // Look it up
  1444. wchar_t *str = g_pVGuiLocalize->Find( tok );
  1445. if ( !str )
  1446. {
  1447. char fn[ 256 ];
  1448. //Q_FileBase( g_Analysis.symbols.String( vcdname ), fn, sizeof( fn ) );
  1449. Q_strncpy( fn, &g_Analysis.symbols.String( vcdname )[ gamedirskip ], sizeof( fn ) );
  1450. if ( Q_stristr( tok, ".wav" ) )
  1451. {
  1452. if ( verbose )
  1453. {
  1454. if ( !regenerate_quiet )
  1455. {
  1456. vprint( 0, "(OBSOLETE???)missing cc token '%s' (!.wav file): vcd (%s), event (%s)\n",
  1457. tok, fn, e->GetName() );
  1458. }
  1459. }
  1460. ++wavfile;
  1461. }
  1462. else
  1463. {
  1464. if ( !regenerate_quiet )
  1465. {
  1466. vprint( 0, "missing %s cc token '%s': vcd (%s), event (%s)\n",
  1467. pass == 0 ? "normal" : "combined",
  1468. tok,
  1469. fn,
  1470. e->GetName() );
  1471. }
  1472. // Add the "!!!entry" to a temp file
  1473. if ( verbose )
  1474. {
  1475. char suggested[ 4096 ];
  1476. Q_snprintf( suggested, sizeof( suggested ), "!!!%s", tok );
  1477. int soundindex = g_pSoundEmitterSystem->GetSoundIndex( tok );
  1478. if ( soundindex != -1 )
  1479. {
  1480. // Now try to find a better bit of text
  1481. CSoundParametersInternal *internal = g_pSoundEmitterSystem->InternalGetParametersForSound( soundindex );
  1482. if ( internal && internal->NumSoundNames() > 0 )
  1483. {
  1484. CUtlSymbol &symwave = internal->GetSoundNames()[ 0 ].symbol;
  1485. char const *wavname = g_pSoundEmitterSystem->GetWaveName( symwave );
  1486. if ( wavname && ( Q_stristr( wavname, "vo/" ) || Q_stristr( wavname, "vo\\" ) ) )
  1487. {
  1488. CSentence sentence;
  1489. if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
  1490. {
  1491. if ( Q_strlen( sentence.GetText() ) > 0 )
  1492. {
  1493. Q_snprintf( suggested, sizeof( suggested ), "%s", sentence.GetText() );
  1494. cleanquotes( suggested );
  1495. }
  1496. }
  1497. }
  1498. }
  1499. }
  1500. logprint( "missing.txt", "\t\"%s\"\t\t\"%s\"\n", tok, suggested );
  1501. }
  1502. ++missingcount;
  1503. }
  1504. }
  1505. else
  1506. {
  1507. if ( verbose )
  1508. {
  1509. if ( !wcsncmp( str, L"!!!", wcslen( L"!!!" ) ) )
  1510. {
  1511. if ( !regenerate_quiet )
  1512. {
  1513. vprint( 0, "Autogenerated closecaption token '%s' not edited\n", tok );
  1514. }
  1515. }
  1516. }
  1517. ++validcount;
  1518. }
  1519. // Verify checksum
  1520. if ( iscombined )
  1521. {
  1522. ValidateCombinedSoundCheckSum( e, referencedcaptionwaves );
  1523. }
  1524. }
  1525. }
  1526. SpewScript( fullname, sortedSpeakEvents );
  1527. sortedSpeakEvents.RemoveAll();
  1528. delete scene;
  1529. }
  1530. int total = validcount + missingcount + wavfile + disabledcount;
  1531. if ( total != 0 )
  1532. {
  1533. vprint( 0, "\n%.2f %%%% invalid (%i valid, %i missing, %i wavfile(OBSOLETE), %i disabled - total %i)\n",
  1534. 100.0f * (float)missingcount / (float)total,
  1535. validcount,
  1536. missingcount,
  1537. wavfile,
  1538. disabledcount,
  1539. total );
  1540. }
  1541. }
  1542. void CheckForOrphanedCombinedWavs( CUtlVector< CUtlSymbol >& diskwaves, CUtlRBTree< CUtlSymbol, int >& captionsused )
  1543. {
  1544. if ( g_pFullFileSystem->FileExists( "orphaned.bat", "GAME" ) )
  1545. {
  1546. g_pFullFileSystem->RemoveFile( "orphaned.bat", "GAME" );
  1547. }
  1548. int orphans = 0;
  1549. int c = diskwaves.Count();
  1550. for ( int i = 0; i < c; ++i )
  1551. {
  1552. CUtlSymbol &sym = diskwaves[ i ];
  1553. if ( captionsused.Find( sym ) != captionsused.InvalidIndex() )
  1554. continue;
  1555. char fn[ 256 ];
  1556. Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
  1557. vprint( 1, "Orphaned wav file '%s'\n", fn );
  1558. logprint( "orphaned.bat", "del \"%s\" /f\n", fn );
  1559. ++orphans;
  1560. }
  1561. if ( orphans != 0 )
  1562. {
  1563. vprint( 0, "\n%.2f %%%% (%i/%i), orphaned combined .wav files in sound/combined/... folder\n",
  1564. 100.0f * (float)orphans / (float)c,
  1565. orphans, c );
  1566. vprint( 0, "created orphaned.bat file\n" );
  1567. }
  1568. else
  1569. {
  1570. vprint( 0, "\nNo orphaned files found among %d possible disk waves\n", c );
  1571. }
  1572. }
  1573. float GetWaveDuration( char const *wavname )
  1574. {
  1575. if ( !g_pFullFileSystem->FileExists( wavname ) )
  1576. {
  1577. return 0.0f;
  1578. }
  1579. CAudioSource *wave = sound->LoadSound( wavname );
  1580. if ( !wave )
  1581. {
  1582. //vprint( 0, "unable to load %s\n", wavname );
  1583. return 0.0f;
  1584. }
  1585. CAudioMixer *pMixer = wave->CreateMixer();
  1586. if ( !pMixer )
  1587. {
  1588. vprint( 0, "unable to create mixer for %s\n", wavname );
  1589. delete wave;
  1590. return 0.0f;
  1591. }
  1592. float duration = wave->GetRunningLength();
  1593. return duration;
  1594. }
  1595. void GetWaveSentence( char const *wavname, CSentence& sentence )
  1596. {
  1597. sentence.Reset();
  1598. if ( !g_pFullFileSystem->FileExists( wavname ) )
  1599. {
  1600. return;
  1601. }
  1602. CAudioSource *wave = sound->LoadSound( wavname );
  1603. if ( !wave )
  1604. {
  1605. //vprint( 0, "unable to load %s\n", wavname );
  1606. return;
  1607. }
  1608. sentence = *wave->GetSentence();
  1609. }
  1610. void ValidateForeignLanguageWaves( char const *language, CUtlVector< CUtlSymbol >& combinedwavfiles )
  1611. {
  1612. // Need to compute the gamedir to the specified language
  1613. char langdir[ 512 ];
  1614. char strippedgamedir[ 512 ];
  1615. Q_strncpy( langdir, gamedir, sizeof( langdir ) );
  1616. Q_StripTrailingSlash( langdir );
  1617. Q_strncpy( strippedgamedir, langdir, sizeof( strippedgamedir ) );
  1618. Q_strcat( langdir, "_", sizeof(langdir) );
  1619. Q_strcat( langdir, language, sizeof(langdir) );
  1620. int skipchars = Q_strlen( strippedgamedir );
  1621. // Need to add this to the file system
  1622. int missing = 0;
  1623. int outdated = 0;
  1624. int c = combinedwavfiles.Count();
  1625. for ( int i = 0; i < c; ++i )
  1626. {
  1627. CUtlSymbol& sym = combinedwavfiles[ i ];
  1628. char wavname[ 512 ];
  1629. Q_strncpy( wavname, g_Analysis.symbols.String( sym ), sizeof( wavname ) );
  1630. // Now get language specific wav name
  1631. char localizedwavename[ 512 ];
  1632. Q_snprintf( localizedwavename, sizeof( localizedwavename ), "%s%s",
  1633. langdir,
  1634. &wavname[ skipchars ] );
  1635. float duration_english = GetWaveDuration( wavname );
  1636. if ( !duration_english )
  1637. {
  1638. continue;
  1639. }
  1640. // Now see if the localized file exists
  1641. float duration_localized = GetWaveDuration( localizedwavename );
  1642. if ( !duration_localized )
  1643. {
  1644. ++missing;
  1645. vprint( 0, "Missing localized file %s\n", localizedwavename );
  1646. continue;
  1647. }
  1648. CSentence sentence_english;
  1649. GetWaveSentence( wavname, sentence_english );
  1650. CSentence sentence_localized;
  1651. GetWaveSentence( localizedwavename, sentence_localized );
  1652. if ( sentence_english.GetText() &&
  1653. sentence_english.GetText()[0] )
  1654. {
  1655. if ( !sentence_localized.GetText() || !sentence_localized.GetText()[0] )
  1656. {
  1657. vprint( 0, "--> Localized combined file for '%s' doesn't have sentence data '%s'\n",
  1658. language, localizedwavename );
  1659. }
  1660. else if ( !Q_stricmp( sentence_english.GetText(), sentence_localized.GetText()) )
  1661. {
  1662. vprint( 0, "--> Localized combined file for '%s' still using english phoneme and text data '%s'\n",
  1663. language, localizedwavename );
  1664. }
  1665. }
  1666. if ( fabs( duration_localized - duration_english ) > SOUND_DURATION_TOLERANCE )
  1667. {
  1668. ++outdated;
  1669. vprint( 0, "--> Mismatched localized file %s (english %.2f s./%s %.2f s.)\n", localizedwavename,
  1670. duration_english, language, duration_localized );
  1671. continue;
  1672. }
  1673. }
  1674. if ( c != 0 )
  1675. {
  1676. vprint( 0, "%.2f %%%% missing(%i)+outdated(%i)/total(%i), combined .wav files in %s closecaption/ folder\n",
  1677. 100.0f * (float)(missing + outdated ) / (float)c,
  1678. missing, outdated, c, language );
  1679. }
  1680. }
  1681. void BuildReverseSoundLookup( CUtlDict< CUtlSymbol, int >& wavtosound )
  1682. {
  1683. // Build a dictionary of wav names to sound names
  1684. int c = g_pSoundEmitterSystem->GetSoundCount();
  1685. for ( int i = 0; i < c; ++i )
  1686. {
  1687. char const *soundname = g_pSoundEmitterSystem->GetSoundName( i );
  1688. CUtlSymbol soundSymbol = g_Analysis.symbols.AddString( soundname );
  1689. CSoundParametersInternal* params = g_pSoundEmitterSystem->InternalGetParametersForSound( i );
  1690. if ( soundname && params )
  1691. {
  1692. int soundcount = params->NumSoundNames();
  1693. for ( int j = 0; j < soundcount; ++j )
  1694. {
  1695. SoundFile& sf = params->GetSoundNames()[ j ];
  1696. char const *pwavname = g_pSoundEmitterSystem->GetWaveName( sf.symbol );
  1697. char fixed[ 512 ];
  1698. Q_strncpy( fixed, PSkipSoundChars( pwavname ), sizeof( fixed ) );
  1699. _strlwr( fixed );
  1700. Q_FixSlashes( fixed );
  1701. int curidx = wavtosound.Find( fixed );
  1702. if ( curidx == wavtosound.InvalidIndex() )
  1703. {
  1704. wavtosound.Insert( fixed, soundSymbol );
  1705. //vprint( 0, "entry %s == %s\n", fixed, soundname );
  1706. }
  1707. }
  1708. }
  1709. }
  1710. vprint( 0, "Reverse lookup has %i entries from %i available sounds\n", wavtosound.Count(), c );
  1711. }
  1712. #define UNK_SOUND_ENTRY "<nosoundentry>"
  1713. char const *FindSoundEntry( CUtlDict< CUtlSymbol, int >& wavtosound, char const *wavname )
  1714. {
  1715. char fixed[ 512 ];
  1716. Q_strncpy( fixed, PSkipSoundChars( wavname ), sizeof( fixed ) );
  1717. _strlwr( fixed );
  1718. Q_FixSlashes( fixed );
  1719. int idx = wavtosound.Find( fixed );
  1720. if ( idx != wavtosound.InvalidIndex() )
  1721. {
  1722. CUtlSymbol snd = wavtosound[ idx ];
  1723. return g_Analysis.symbols.String( snd );
  1724. }
  1725. return UNK_SOUND_ENTRY;
  1726. }
  1727. void CheckWaveFile( CUtlDict< CUtlSymbol, int >& wavtosound, char const *wavname )
  1728. {
  1729. // vprint( 0, "%s\n", wavname );
  1730. char const *soundname = FindSoundEntry( wavtosound, wavname );
  1731. char ansi[ 512 ];
  1732. ansi[ 0 ] = 0;
  1733. CSentence sentence;
  1734. if ( LoadSentenceFromWavFile( va( "sound/%s", PSkipSoundChars( wavname ) ), sentence ) )
  1735. {
  1736. if ( Q_strlen( sentence.GetText() ) > 0 )
  1737. {
  1738. Q_snprintf( ansi, sizeof( ansi ), "%s", sentence.GetText() );
  1739. cleanquotes( ansi );
  1740. }
  1741. }
  1742. // Now look up cc token
  1743. wchar_t *text = g_pVGuiLocalize->Find( soundname );
  1744. char caption[ 1024 ];
  1745. Q_strncpy( caption, "!!!", sizeof( caption ) );
  1746. if ( text )
  1747. {
  1748. g_pVGuiLocalize->ConvertUnicodeToANSI( text, caption, sizeof( caption ) );
  1749. }
  1750. else
  1751. {
  1752. if ( !Q_stricmp( soundname, UNK_SOUND_ENTRY ) )
  1753. {
  1754. Q_snprintf( caption, sizeof( caption ), "!!!%s", soundname );
  1755. }
  1756. }
  1757. logprint( "wavcheck.csv",
  1758. "\"%s\",\"%s\",\"%s\",\"%s\"\n",
  1759. wavname,
  1760. soundname,
  1761. caption,
  1762. ansi );
  1763. }
  1764. void WavCheck( CUtlVector< CUtlSymbol >& wavfiles )
  1765. {
  1766. g_pFullFileSystem->RemoveFile( "wavcheck.csv", "GAME" );
  1767. vprint( 0, "Building reverse lookup\n" );
  1768. logprint( "wavcheck.csv",
  1769. "\"%s\",\"%s\",\"%s\",\"%s\"\n",
  1770. "WaveName",
  1771. "Sound Script Name",
  1772. "Close Caption",
  1773. "Phoneme Data String" );
  1774. CUtlDict< CUtlSymbol, int > wavtosound;
  1775. BuildReverseSoundLookup( wavtosound );
  1776. vprint( 0, "Performing wavcheck\n" );
  1777. int c = wavfiles.Count();
  1778. int offset = Q_strlen( gamedir ) + Q_strlen( "sound/" );
  1779. for ( int i = 0; i < c; ++i )
  1780. {
  1781. char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] );
  1782. CheckWaveFile( wavtosound, wavname + offset );
  1783. if ( !(i % 100 ) )
  1784. {
  1785. vprint( 0, "Finished %i/%i\n", i, c );
  1786. }
  1787. }
  1788. }
  1789. void BuildWavFileToFullPathLookup( CUtlVector< CUtlSymbol >& wavfile, CUtlDict< int, int >& wavtofullpath )
  1790. {
  1791. int c = wavfile.Count();
  1792. for ( int i = 0; i < c; ++i )
  1793. {
  1794. CUtlSymbol &sym = wavfile[ i ];
  1795. char shortname[ 512 ];
  1796. Q_FileBase( g_Analysis.symbols.String( sym ), shortname, sizeof( shortname ) );
  1797. Q_SetExtension( shortname, ".wav", sizeof( shortname ) );
  1798. Q_FixSlashes( shortname );
  1799. Q_strlower( shortname );
  1800. int idx = wavtofullpath.Find( shortname );
  1801. if ( idx == wavtofullpath.InvalidIndex() )
  1802. {
  1803. wavtofullpath.Insert( shortname, i );
  1804. }
  1805. }
  1806. }
  1807. static void COM_CreatePath (const char *path)
  1808. {
  1809. char temppath[512];
  1810. Q_strncpy( temppath, path, sizeof(temppath) );
  1811. for (char *ofs = temppath+1 ; *ofs ; ofs++)
  1812. {
  1813. if (*ofs == '/' || *ofs == '\\')
  1814. { // create the directory
  1815. char old = *ofs;
  1816. *ofs = 0;
  1817. mkdir (temppath);
  1818. *ofs = old;
  1819. }
  1820. }
  1821. }
  1822. void MakeBatchFile( CUtlVector< CUtlSymbol >& wavfiles, char const *pchFromdir, char const *pchTodir )
  1823. {
  1824. g_pFullFileSystem->RemoveFile( "copywaves.bat", "GAME" );
  1825. vprint( 0, "Building reverse lookup\n" );
  1826. CUtlDict< int, int > wavtofullpath;
  1827. BuildWavFileToFullPathLookup( wavfiles, wavtofullpath );
  1828. CUtlVector< CUtlSymbol > files;
  1829. BuildFileList( files, pchFromdir, ".wav" );
  1830. int gamedirskip = Q_strlen( gamedir ) + Q_strlen( "sound//" );
  1831. int c = files.Count();
  1832. for ( int i = 0; i < c; ++i )
  1833. {
  1834. char const *sname = g_Analysis.symbols.String( files[ i ] );
  1835. if ( !sname )
  1836. continue;
  1837. char shortname[ 512 ];
  1838. Q_strncpy( shortname, sname, sizeof( shortname ) );
  1839. char fn[ 512 ];
  1840. Q_FileBase( shortname, fn, sizeof( fn ) );
  1841. Q_SetExtension( fn, ".wav", sizeof( fn ) );
  1842. Q_strlower( fn );
  1843. Q_FixSlashes( fn );
  1844. int slot = wavtofullpath.Find( fn );
  1845. if ( slot == wavtofullpath.InvalidIndex() )
  1846. {
  1847. vprint( 0, "Couldn't find slot for '%s'\n", sname );
  1848. continue;
  1849. }
  1850. char fullname[ 512 ];
  1851. Q_snprintf( fullname, sizeof( fullname ), "%s/%s", pchTodir, &g_Analysis.symbols.String( wavfiles[ wavtofullpath[ slot ] ] )[ gamedirskip ] );
  1852. Q_strlower( fullname );
  1853. Q_FixSlashes( fullname );
  1854. //logprint( "copywaves.bat", "xcopy \"%s\" \"%s\"\n",
  1855. //shortname,
  1856. //fullname );
  1857. COM_CreatePath( fullname );
  1858. CopyFile( shortname, fullname, TRUE );
  1859. }
  1860. }
  1861. //-----------------------------------------------------------------------------
  1862. // Purpose:
  1863. // Input : store -
  1864. //-----------------------------------------------------------------------------
  1865. void StoreValveDataChunk( CSentence& sentence, IterateOutputRIFF& store )
  1866. {
  1867. // Buffer and dump data
  1868. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1869. sentence.SaveToBuffer( buf );
  1870. // Copy into store
  1871. store.ChunkWriteData( buf.Base(), buf.TellPut() );
  1872. }
  1873. void SaveWave( char const *filename, CSentence& s )
  1874. {
  1875. char infile[ 512 ];
  1876. Q_strncpy( infile, filename, sizeof( infile ) );
  1877. Q_SetExtension( infile, ".tmp", sizeof( infile ) );
  1878. // Rename infile
  1879. MoveFile( filename, infile );
  1880. SetFileAttributes( filename, FILE_ATTRIBUTE_NORMAL );
  1881. {
  1882. InFileRIFF riff( infile, io_in );
  1883. Assert( riff.RIFFName() == RIFF_WAVE );
  1884. // set up the iterator for the whole file (root RIFF is a chunk)
  1885. IterateRIFF walk( riff, riff.RIFFSize() );
  1886. OutFileRIFF riffout( filename, io_out );
  1887. IterateOutputRIFF store( riffout );
  1888. bool wordtrackwritten = false;
  1889. // Walk input chunks and copy to output
  1890. while ( walk.ChunkAvailable() )
  1891. {
  1892. unsigned int originalPos = store.ChunkGetPosition();
  1893. store.ChunkStart( walk.ChunkName() );
  1894. bool skipchunk = false;
  1895. switch ( walk.ChunkName() )
  1896. {
  1897. case WAVE_VALVEDATA:
  1898. // Overwrite data
  1899. StoreValveDataChunk( s, store );
  1900. wordtrackwritten = true;
  1901. break;
  1902. default:
  1903. store.CopyChunkData( walk );
  1904. break;
  1905. }
  1906. store.ChunkFinish();
  1907. if ( skipchunk )
  1908. {
  1909. store.ChunkSetPosition( originalPos );
  1910. }
  1911. walk.ChunkNext();
  1912. }
  1913. if ( !wordtrackwritten )
  1914. {
  1915. store.ChunkStart( WAVE_VALVEDATA );
  1916. StoreValveDataChunk( s, store );
  1917. store.ChunkFinish();
  1918. }
  1919. }
  1920. SetFileAttributes( infile, FILE_ATTRIBUTE_NORMAL );
  1921. DeleteFile( infile );
  1922. }
  1923. void ExtractPhonemesForWave( IPhonemeExtractor *extractor, char const *wavname )
  1924. {
  1925. char formatbuffer[ 1024 ];
  1926. int formatsize = sizeof( formatbuffer );
  1927. int dataSize = 0;
  1928. CSentence sentence;
  1929. if ( !LoadSentenceFromWavFile( wavname, sentence, formatbuffer, &formatsize, &dataSize ) )
  1930. {
  1931. vprint( 0, " skip '%s' missing\n", wavname );
  1932. return;
  1933. }
  1934. if ( !forceextract &&
  1935. sentence.m_Words.Count() > 0 )
  1936. {
  1937. vprint( 0, " skip '%s', already has phonemes\n", wavname );
  1938. return;
  1939. }
  1940. if ( forceextract )
  1941. {
  1942. sentence.Reset();
  1943. }
  1944. if ( formatsize == 0 )
  1945. {
  1946. vprint( 0, " skip '%s', not WAVE_FMT parsed\n", wavname );
  1947. return;
  1948. }
  1949. const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)formatbuffer;
  1950. int format = pHeader->wFormatTag;
  1951. int bits = pHeader->wBitsPerSample;
  1952. int rate = pHeader->nSamplesPerSec;
  1953. int channels = pHeader->nChannels;
  1954. int sampleSize = (bits * channels) / 8;
  1955. // this can never be zero -- other functions divide by this.
  1956. // This should never happen, but avoid crashing
  1957. if ( sampleSize <= 0 )
  1958. sampleSize = 1;
  1959. int sampleCount = 0;
  1960. float truesamplesize = sampleSize;
  1961. if ( format == WAVE_FORMAT_ADPCM )
  1962. {
  1963. sampleSize = 1;
  1964. ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)formatbuffer;
  1965. int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2;
  1966. blockSize += 7 * pFormat->wfx.nChannels;
  1967. int blockCount = sampleCount / blockSize;
  1968. int blockRem = sampleCount % blockSize;
  1969. // total samples in complete blocks
  1970. sampleCount = blockCount * pFormat->wSamplesPerBlock;
  1971. // add remaining in a short block
  1972. if ( blockRem )
  1973. {
  1974. sampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / channels);
  1975. }
  1976. truesamplesize = 0.5f;
  1977. }
  1978. else
  1979. {
  1980. sampleCount = dataSize / sampleSize;
  1981. }
  1982. // Do extraction
  1983. // Current set of tags
  1984. CSentence outsentence;
  1985. char filename[ 512 ];
  1986. Q_snprintf( filename, sizeof( filename ), "%s", wavname );
  1987. int result = extractor->Extract(
  1988. filename,
  1989. dataSize, // (int)( m_pWaveFile->GetRunningLength() * m_pWaveFile->SampleRate() * m_pWaveFile->TrueSampleSize() ),
  1990. Msg,
  1991. sentence,
  1992. outsentence );
  1993. if ( result != SR_RESULT_SUCCESS )
  1994. {
  1995. vprint( 0, " failed to analyze '%s', skipping\n", wavname );
  1996. return;
  1997. }
  1998. float bytespersecond = rate * truesamplesize;
  1999. // Now convert byte offsets to times
  2000. int i;
  2001. for ( i = 0; i < outsentence.m_Words.Size(); i++ )
  2002. {
  2003. CWordTag *tag = outsentence.m_Words[ i ];
  2004. Assert( tag );
  2005. if ( !tag )
  2006. continue;
  2007. tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond;
  2008. tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond;
  2009. for ( int j = 0; j < tag->m_Phonemes.Size(); j++ )
  2010. {
  2011. CPhonemeTag *ptag = tag->m_Phonemes[ j ];
  2012. Assert( ptag );
  2013. if ( !ptag )
  2014. continue;
  2015. ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond );
  2016. ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond );
  2017. }
  2018. }
  2019. sentence = outsentence;
  2020. outsentence.Reset();
  2021. // Resave it
  2022. SaveWave( filename, sentence );
  2023. }
  2024. struct Extractor
  2025. {
  2026. PE_APITYPE apitype;
  2027. CSysModule *module;
  2028. IPhonemeExtractor *extractor;
  2029. };
  2030. CUtlVector< Extractor > g_Extractors;
  2031. void UnloadPhonemeConverters()
  2032. {
  2033. int c = g_Extractors.Count();
  2034. for ( int i = c - 1; i >= 0; i-- )
  2035. {
  2036. Extractor *e = &g_Extractors[ i ];
  2037. g_pFullFileSystem->UnloadModule( e->module );
  2038. }
  2039. g_Extractors.RemoveAll();
  2040. }
  2041. int LoadPhonemeExtractors()
  2042. {
  2043. // Enumerate modules under bin folder of exe
  2044. FileFindHandle_t findHandle;
  2045. const char *pFilename = g_pFullFileSystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle );
  2046. int useextractor = -1;
  2047. while ( pFilename )
  2048. {
  2049. char fullpath[ 512 ];
  2050. Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename );
  2051. pFilename = g_pFullFileSystem->FindNext( findHandle );
  2052. Con_Printf( "Loading extractor from %s\n", fullpath );
  2053. Extractor e;
  2054. e.module = Sys_LoadModule( fullpath );
  2055. if ( !e.module )
  2056. {
  2057. Warning( "Unable to Sys_LoadModule %s\n", fullpath );
  2058. continue;
  2059. }
  2060. CreateInterfaceFn factory = Sys_GetFactory( e.module );
  2061. if ( !factory )
  2062. {
  2063. Warning( "Unable to get factory from %s\n", fullpath );
  2064. continue;
  2065. }
  2066. e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL );
  2067. if ( !e.extractor )
  2068. {
  2069. Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath );
  2070. continue;
  2071. }
  2072. e.apitype = e.extractor->GetAPIType();
  2073. if ( e.apitype == SPEECH_API_LIPSINC )
  2074. {
  2075. useextractor = g_Extractors.Count();
  2076. }
  2077. g_Extractors.AddToTail( e );
  2078. }
  2079. g_pFullFileSystem->FindClose( findHandle );
  2080. return useextractor;
  2081. }
  2082. void ExtractPhonemes( CUtlVector< CUtlSymbol >& wavfiles )
  2083. {
  2084. int index = LoadPhonemeExtractors();
  2085. if ( index == -1 )
  2086. return;
  2087. if ( index == 0 )
  2088. {
  2089. vprint( 0, "Couldn't find suitable extractor\n" );
  2090. return;
  2091. }
  2092. IPhonemeExtractor *extractor = g_Extractors[ index ].extractor;
  2093. Assert( extractor );
  2094. vprint( 0, "Using %s\n", extractor->GetName() );
  2095. int c = wavfiles.Count();
  2096. vprint( 0, "Performing '%i' extractions (might take a while...)\n", c );
  2097. for ( int i = 0; i < c; ++i )
  2098. {
  2099. char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] );
  2100. ExtractPhonemesForWave( extractor, wavname );
  2101. if ( !(i % 50 ) )
  2102. {
  2103. vprint( 0, "Finished %i/%i\n", i, c );
  2104. }
  2105. }
  2106. UnloadPhonemeConverters();
  2107. }
  2108. void CheckWavForLoops( char const *wavname )
  2109. {
  2110. InFileRIFF riff( wavname, io_in );
  2111. // UNDONE: Don't use printf to handle errors
  2112. if ( riff.RIFFName() != RIFF_WAVE )
  2113. {
  2114. return;
  2115. }
  2116. // set up the iterator for the whole file (root RIFF is a chunk)
  2117. IterateRIFF walk( riff, riff.RIFFSize() );
  2118. while ( walk.ChunkAvailable( ) )
  2119. {
  2120. switch( walk.ChunkName() )
  2121. {
  2122. case WAVE_CUE:
  2123. vprint( 0, "'%s' has a CUE chunk\n", wavname );
  2124. return;
  2125. default:
  2126. break;
  2127. }
  2128. walk.ChunkNext();
  2129. }
  2130. }
  2131. void CheckForLoops( CUtlVector< CUtlSymbol >& wavfiles )
  2132. {
  2133. int c = wavfiles.Count();
  2134. vprint( 0, "Performing '%i' extractions (might take a while...)\n", c );
  2135. for ( int i = 0; i < c; ++i )
  2136. {
  2137. char const *wavname = g_Analysis.symbols.String( wavfiles[ i ] );
  2138. CheckWavForLoops( wavname );
  2139. if ( !(i % 50 ) )
  2140. {
  2141. vprint( 0, "Finished %i/%i\n", i, c );
  2142. }
  2143. }
  2144. }
  2145. #define MAX_LOCALIZED_CHARS 2048
  2146. //-----------------------------------------------------------------------------
  2147. // Purpose: converts an unicode string to an english string
  2148. //-----------------------------------------------------------------------------
  2149. int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize)
  2150. {
  2151. int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
  2152. ansi[ansiBufferSize - 1] = 0;
  2153. return result;
  2154. }
  2155. struct OrderedCaption_t
  2156. {
  2157. OrderedCaption_t() :
  2158. sym( UTL_INVAL_SYMBOL ),
  2159. commands( NULL ),
  2160. english( NULL ),
  2161. blankenglish( false )
  2162. {
  2163. }
  2164. OrderedCaption_t( const OrderedCaption_t& src )
  2165. {
  2166. sym = src.sym;
  2167. if ( src.commands )
  2168. {
  2169. int len = wcslen( src.commands ) + 1;
  2170. commands = new wchar_t[ len ];
  2171. wcscpy( commands, src.commands );
  2172. }
  2173. else
  2174. {
  2175. commands = NULL;
  2176. }
  2177. if ( src.english )
  2178. {
  2179. int len = wcslen( src.english ) + 1;
  2180. english = new wchar_t[ len ];
  2181. wcscpy( english, src.english );
  2182. }
  2183. else
  2184. {
  2185. english = NULL;
  2186. }
  2187. blankenglish = src.blankenglish;
  2188. }
  2189. ~OrderedCaption_t()
  2190. {
  2191. delete[] commands;
  2192. delete[] english;
  2193. }
  2194. CUtlSymbol sym;
  2195. wchar_t *commands; // any <cmd:arg> stuff at the beginning of the US captions
  2196. wchar_t *english;
  2197. bool blankenglish;
  2198. };
  2199. bool SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args )
  2200. {
  2201. const wchar_t *in = *ppIn;
  2202. const wchar_t *oldin = in;
  2203. if ( in[0] != L'<' )
  2204. {
  2205. *ppIn += ( oldin - in );
  2206. return false;
  2207. }
  2208. args[ 0 ] = 0;
  2209. cmd[ 0 ]= 0;
  2210. wchar_t *out = cmd;
  2211. in++;
  2212. while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) )
  2213. {
  2214. *out++ = *in++;
  2215. }
  2216. *out = L'\0';
  2217. if ( *in != L':' )
  2218. {
  2219. *ppIn += ( in - oldin );
  2220. return true;
  2221. }
  2222. in++;
  2223. out = args;
  2224. while ( *in != L'\0' && *in != L'>' )
  2225. {
  2226. *out++ = *in++;
  2227. }
  2228. *out = L'\0';
  2229. //if ( *in == L'>' )
  2230. // in++;
  2231. *ppIn += ( in - oldin );
  2232. return true;
  2233. }
  2234. wchar_t *GetStartupCommands( const wchar_t *str )
  2235. {
  2236. const wchar_t *curpos = str;
  2237. for ( ; curpos && *curpos != L'\0'; ++curpos )
  2238. {
  2239. wchar_t cmd[ 256 ];
  2240. wchar_t args[ 256 ];
  2241. if ( SplitCommand( &curpos, cmd, args ) )
  2242. {
  2243. continue;
  2244. }
  2245. // Got to first non-command character
  2246. break;
  2247. }
  2248. if ( curpos - str >= 1 )
  2249. {
  2250. int len = curpos - str;
  2251. wchar_t *cmds = new wchar_t[ len + 1 ];
  2252. wcsncpy( cmds, str, len );
  2253. cmds[ len ] = L'\0';
  2254. return cmds;
  2255. }
  2256. return NULL;
  2257. }
  2258. wchar_t *CopyUnicode( const wchar_t *in )
  2259. {
  2260. int len = wcslen( in ) + 1;
  2261. wchar_t *out = new wchar_t[ len ];
  2262. wcsncpy( out, in, len );
  2263. out[ len - 1 ] = L'\0';
  2264. return out;
  2265. }
  2266. void BuildOrderedCaptionList( CUtlVector< OrderedCaption_t >& list )
  2267. {
  2268. // parse out the file
  2269. FileHandle_t file = g_pFullFileSystem->Open( "resource/closecaption_english.txt", "rb");
  2270. if ( file == FILESYSTEM_INVALID_HANDLE )
  2271. {
  2272. // assert(!("CLocalizedStringTable::AddFile() failed to load file"));
  2273. return;
  2274. }
  2275. // read into a memory block
  2276. int fileSize = g_pFullFileSystem->Size(file) ;
  2277. wchar_t *memBlock = (wchar_t *)malloc(fileSize + sizeof(wchar_t));
  2278. wchar_t *data = memBlock;
  2279. g_pFullFileSystem->Read(memBlock, fileSize, file);
  2280. // null-terminate the stream
  2281. memBlock[fileSize / sizeof(wchar_t)] = 0x0000;
  2282. // check the first character, make sure this a little-endian unicode file
  2283. if (data[0] != 0xFEFF)
  2284. {
  2285. g_pFullFileSystem->Close(file);
  2286. free(memBlock);
  2287. return;
  2288. }
  2289. data++;
  2290. // parse out a token at a time
  2291. enum states_e
  2292. {
  2293. STATE_BASE, // looking for base settings
  2294. STATE_TOKENS, // reading in unicode tokens
  2295. };
  2296. bool bQuoted;
  2297. bool bEnglishFile = true;
  2298. states_e state = STATE_BASE;
  2299. while (1)
  2300. {
  2301. // read the key and the value
  2302. wchar_t keytoken[128];
  2303. data = ReadUnicodeToken(data, keytoken, 128, bQuoted);
  2304. if (!keytoken[0])
  2305. break; // we've hit the null terminator
  2306. // convert the token to a string
  2307. char key[128];
  2308. ConvertUnicodeToANSI(keytoken, key, sizeof(key));
  2309. // if we have a C++ style comment, read to end of line and continue
  2310. if (!strnicmp(key, "//", 2))
  2311. {
  2312. data = ReadToEndOfLine(data);
  2313. continue;
  2314. }
  2315. wchar_t valuetoken[ MAX_LOCALIZED_CHARS ];
  2316. data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted);
  2317. if (!valuetoken[0] && !bQuoted)
  2318. break; // we've hit the null terminator
  2319. if (state == STATE_BASE)
  2320. {
  2321. if (!stricmp(key, "Language"))
  2322. {
  2323. // copy out our language setting
  2324. /*
  2325. char value[MAX_LOCALIZED_CHARS];
  2326. ConvertUnicodeToANSI(valuetoken, value, sizeof(value));
  2327. strncpy(m_szLanguage, value, sizeof(m_szLanguage) - 1);
  2328. */
  2329. }
  2330. else if (!stricmp(key, "Tokens"))
  2331. {
  2332. state = STATE_TOKENS;
  2333. }
  2334. else if (!stricmp(key, "}"))
  2335. {
  2336. // we've hit the end
  2337. break;
  2338. }
  2339. }
  2340. else if (state == STATE_TOKENS)
  2341. {
  2342. if (!stricmp(key, "}"))
  2343. {
  2344. // end of tokens
  2345. state = STATE_BASE;
  2346. }
  2347. else
  2348. {
  2349. // skip our [english] beginnings (in non-english files)
  2350. if ( (bEnglishFile) || (!bEnglishFile && strnicmp(key, "[english]", 9)))
  2351. {
  2352. // add the string to the table
  2353. //AddString(key, valuetoken, NULL);
  2354. CUtlSymbol sym = g_Analysis.symbols.AddString( key );
  2355. OrderedCaption_t cap;
  2356. cap.sym = sym;
  2357. cap.commands = GetStartupCommands( valuetoken );
  2358. cap.english = CopyUnicode( valuetoken );
  2359. cap.blankenglish = IsAllSpaces( valuetoken );
  2360. list.AddToTail( cap );
  2361. }
  2362. }
  2363. }
  2364. }
  2365. g_pFullFileSystem->Close(file);
  2366. free(memBlock);
  2367. vprint( 0, "Loaded %i captionnames from closecaption_english.txt\n", list.Count() );
  2368. }
  2369. struct LookupData_t
  2370. {
  2371. LookupData_t() :
  2372. unicode( 0 ),
  2373. caption( 0 )
  2374. {
  2375. }
  2376. wchar_t *unicode;
  2377. char *caption;
  2378. };
  2379. void LoadImportData( char const *filename, CUtlDict< LookupData_t, int >& lookup )
  2380. {
  2381. // parse out the file
  2382. FileHandle_t file = g_pFullFileSystem->Open( filename, "rb");
  2383. if ( file == FILESYSTEM_INVALID_HANDLE )
  2384. {
  2385. // assert(!("CLocalizedStringTable::AddFile() failed to load file"));
  2386. return;
  2387. }
  2388. // read into a memory block
  2389. int fileSize = g_pFullFileSystem->Size(file) ;
  2390. wchar_t *memBlock = (wchar_t *)malloc(fileSize + sizeof(wchar_t));
  2391. wchar_t *data = memBlock;
  2392. g_pFullFileSystem->Read(memBlock, fileSize, file);
  2393. // null-terminate the stream
  2394. memBlock[fileSize / sizeof(wchar_t)] = 0x0000;
  2395. // check the first character, make sure this a little-endian unicode file
  2396. if (data[0] != 0xFEFF)
  2397. {
  2398. g_pFullFileSystem->Close(file);
  2399. free(memBlock);
  2400. return;
  2401. }
  2402. data++;
  2403. bool bQuoted;
  2404. while (1)
  2405. {
  2406. // read the key and the value
  2407. wchar_t keytoken[128];
  2408. data = ReadUnicodeTokenNoSpecial(data, keytoken, 128, bQuoted);
  2409. if (!keytoken[0])
  2410. break; // we've hit the null terminator
  2411. // convert the token to a string
  2412. char key[128];
  2413. ConvertUnicodeToANSI(keytoken, key, sizeof(key));
  2414. // vprint( 0, "keyname %s\n", key );
  2415. // if we have a C++ style comment, read to end of line and continue
  2416. if (!strnicmp(key, "//", 2))
  2417. {
  2418. data = ReadToEndOfLine(data);
  2419. continue;
  2420. }
  2421. wchar_t valuetoken[ MAX_LOCALIZED_CHARS ];
  2422. data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted);
  2423. if (!valuetoken[0] && !bQuoted)
  2424. break; // we've hit the null terminator
  2425. wchar_t *vcopy = new wchar_t[ wcslen( valuetoken ) + 1 ];
  2426. wcscpy( vcopy, valuetoken );
  2427. LookupData_t ld;
  2428. ld.unicode = vcopy;
  2429. ld.caption = NULL;
  2430. lookup.Insert( key, ld );
  2431. }
  2432. g_pFullFileSystem->Close(file);
  2433. free(memBlock);
  2434. vprint( 0, "Loaded %i wav/captions from %s\n", lookup.Count(), filename );
  2435. }
  2436. #define CAPTION_OUT_FILE "resource/closecaption_test.txt"
  2437. //-----------------------------------------------------------------------------
  2438. // Purpose: importfile is a unicode file contains pairs of .wav names and caption strings
  2439. // we need to read in the closecaption_english.txt file
  2440. // and then build a reverse lookup of .wav to caption name and build a new
  2441. // closecaption_test.txt file based on the unicode caption strings
  2442. // Input : *importfile -
  2443. //-----------------------------------------------------------------------------
  2444. void ImportCaptions( char const *pchImportfile )
  2445. {
  2446. CUtlVector< OrderedCaption_t > captionlist;
  2447. BuildOrderedCaptionList( captionlist );
  2448. CUtlDict< LookupData_t, int > newCaptions;
  2449. LoadImportData( pchImportfile, newCaptions );
  2450. // Now build a .wav to caption name lookup
  2451. CUtlDict< CUtlSymbol, int > wavtosound;
  2452. BuildReverseSoundLookup( wavtosound );
  2453. CUtlDict< wchar_t *, int > captionToUnicode;
  2454. // Now walk the import data, and try to figure out the caption name for each one
  2455. int c = newCaptions.Count();
  2456. for ( int i = 0; i < c ; ++i )
  2457. {
  2458. char const *wavname = newCaptions.GetElementName( i );
  2459. LookupData_t& data = newCaptions[ i ];
  2460. char fn[ 512 ];
  2461. Q_strncpy( fn, wavname, sizeof( fn ) );
  2462. Q_strlower( fn );
  2463. Q_FixSlashes( fn );
  2464. // See if we can find the wavname in the reverse lookup
  2465. int idx = wavtosound.Find( fn );
  2466. if ( idx != wavtosound.InvalidIndex() )
  2467. {
  2468. data.caption = strdup( g_Analysis.symbols.String( wavtosound[ idx ] ) );
  2469. captionToUnicode.Insert( data.caption, data.unicode );
  2470. }
  2471. else
  2472. {
  2473. vprint( 0, "unable to find caption matching '%s'\n", wavname );
  2474. }
  2475. }
  2476. CUtlBuffer buf( 0, 0 );
  2477. c = captionlist.Count();
  2478. for ( int i = 0; i < c; ++i )
  2479. {
  2480. char const *captionname = g_Analysis.symbols.String( captionlist[ i ].sym );
  2481. // Find it in the captionToUnicodeFolder
  2482. int idx = captionToUnicode.Find( captionname );
  2483. if ( idx != captionToUnicode.InvalidIndex() )
  2484. {
  2485. // Skip blank english entries
  2486. if ( captionlist[ i ].blankenglish )
  2487. {
  2488. vprint( 0, "skipping %s, english caption is blank\n", captionname );
  2489. continue;
  2490. }
  2491. wchar_t *u = captionToUnicode[ idx ];
  2492. wchar_t *prefix = captionlist[ i ].commands;
  2493. wchar_t composed[ MAX_LOCALIZED_CHARS ];
  2494. int maxlen = ( sizeof( composed ) / sizeof( wchar_t ) ) - 1;
  2495. if ( prefix )
  2496. {
  2497. _snwprintf( composed, maxlen, L"%s%s", prefix, u );
  2498. }
  2499. else
  2500. {
  2501. wcsncpy( composed, u, maxlen );
  2502. }
  2503. composed[ maxlen ] = L'\0';
  2504. // Write to buffer
  2505. WriteAsciiStringAsUnicode( buf, captionname, true );
  2506. WriteAsciiStringAsUnicode( buf, "\t", false );
  2507. WriteUnicodeString( buf, composed, true );
  2508. WriteAsciiStringAsUnicode( buf, "\r\n", false );
  2509. // Now write the "[ENGLISH]" entry
  2510. char engcap[ 512 ];
  2511. Q_snprintf( engcap, sizeof( engcap ), "[english]%s", captionname );
  2512. WriteAsciiStringAsUnicode( buf, engcap, true );
  2513. WriteAsciiStringAsUnicode( buf, "\t", false );
  2514. WriteUnicodeString( buf, captionlist[ i ].english ? captionlist[ i ].english : L"???", true );
  2515. WriteAsciiStringAsUnicode( buf, "\r\n", false );
  2516. }
  2517. else
  2518. {
  2519. vprint( 0, "no lookup for cc token '%s'\n", captionname );
  2520. }
  2521. }
  2522. // Now try and spit out a file like the cc english file, but with the new data
  2523. FileHandle_t fh = g_pFullFileSystem->Open( CAPTION_OUT_FILE , "wb" );
  2524. if ( FILESYSTEM_INVALID_HANDLE != fh )
  2525. {
  2526. g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
  2527. g_pFullFileSystem->Close( fh );
  2528. }
  2529. else
  2530. {
  2531. vprint( 0, "Unable to open %s for writing\n", CAPTION_OUT_FILE );
  2532. }
  2533. // Cleanup memory
  2534. c = newCaptions.Count();
  2535. for ( int i = 0 ; i < c; ++i )
  2536. {
  2537. LookupData_t& data = newCaptions[ i ];
  2538. delete[] data.unicode;
  2539. free( data.caption );
  2540. }
  2541. newCaptions.Purge();
  2542. }
  2543. bool IsWavFileDucked( char const *name )
  2544. {
  2545. CSentence sentence;
  2546. if ( LoadSentenceFromWavFile( name , sentence ) )
  2547. {
  2548. return sentence.GetVoiceDuck();
  2549. }
  2550. vprint( 0, "IsWavFileDucked: Missing .wav %s!!!\n", name );
  2551. return false;
  2552. }
  2553. void SetWavFileDucking( char const *name, bool ducking )
  2554. {
  2555. CSentence sentence;
  2556. if ( LoadSentenceFromWavFile( name , sentence ) )
  2557. {
  2558. Assert( sentence.GetVoiceDuck() != ducking );
  2559. sentence.SetVoiceDuck( ducking );
  2560. // Save it back out
  2561. SaveWave( name, sentence );
  2562. vprint( 1, "duck(%s): %s\n", ducking ? "true" : "false", name );
  2563. return;
  2564. }
  2565. vprint( 0, "SetWavFileDucking: Missing .wav %s!!!\n", name );
  2566. }
  2567. void SyncDucking( CUtlVector< CUtlSymbol >& english, CUtlVector< CUtlSymbol >& localized )
  2568. {
  2569. int i, c;
  2570. CUtlRBTree< CUtlSymbol > englishducked( 0, 0, DefLessFunc( CUtlSymbol ) );
  2571. CUtlRBTree< CUtlSymbol > englishunducked( 0, 0, DefLessFunc( CUtlSymbol ) );
  2572. int fromoffset = Q_strlen( fromdir ) + 1;
  2573. int tooffset = Q_strlen( todir ) + 1;
  2574. c = english.Count();
  2575. for ( i = 0; i < c; ++i )
  2576. {
  2577. CUtlSymbol& sym = english[ i ];
  2578. char fn[ 512 ];
  2579. Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
  2580. if ( !( i % 1000 ) )
  2581. {
  2582. vprint( 1, "analyzed %i / %i (%.1f %%)\n", i, c, 100.0f * (float)i/(float)c );
  2583. }
  2584. bool ducked = IsWavFileDucked( fn );
  2585. CUtlSymbol croppedSym = g_Analysis.symbols.AddString( &fn[ fromoffset ] );
  2586. if ( ducked )
  2587. {
  2588. englishducked.Insert( croppedSym );
  2589. }
  2590. else
  2591. {
  2592. englishunducked.Insert( croppedSym );
  2593. }
  2594. }
  2595. int updated = 0;
  2596. // Now walk the localized tree and sync it to the english version
  2597. c = localized.Count();
  2598. for ( i = 0; i < c; ++i )
  2599. {
  2600. CUtlSymbol& sym = localized[ i ];
  2601. char fn[ 512 ];
  2602. Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
  2603. bool ducked = IsWavFileDucked( fn );
  2604. CUtlSymbol croppedSym = g_Analysis.symbols.AddString( &fn[ tooffset ] );
  2605. bool inenglishducked = englishducked.Find( croppedSym ) != englishducked.InvalidIndex() ? true : false;
  2606. bool inenglishunducked = englishunducked.Find( croppedSym ) != englishunducked.InvalidIndex() ? true : false;
  2607. if ( !( i % 100 ) )
  2608. {
  2609. vprint( 1, "sync'd %i / %i (%.1f %%)\n", i, c, 100.0f * (float)i/(float)c );
  2610. }
  2611. if ( ducked && inenglishducked )
  2612. continue;
  2613. if ( !ducked && inenglishunducked )
  2614. continue;
  2615. if ( !inenglishducked && !inenglishunducked )
  2616. {
  2617. vprint( 0, "Warning: %s is in localized tree, missing from english tree!!\n", fn );
  2618. continue;
  2619. }
  2620. Assert( inenglishducked ^ inenglishunducked );
  2621. SetWavFileDucking( fn, inenglishducked );
  2622. ++updated;
  2623. }
  2624. vprint( 0, "finished, updated %i / %i (%.1f %%) localized .wavs\n", updated, c, 100.0f * (float)updated/(float)c );
  2625. }
  2626. //-----------------------------------------------------------------------------
  2627. // The application object
  2628. //-----------------------------------------------------------------------------
  2629. class CLocalizationCheckApp : public CTier3SteamApp
  2630. {
  2631. typedef CTier3SteamApp BaseClass;
  2632. public:
  2633. // Methods of IApplication
  2634. virtual bool Create();
  2635. virtual bool PreInit( );
  2636. virtual int Main();
  2637. virtual void PostShutdown( );
  2638. virtual void Destroy() {}
  2639. };
  2640. DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CLocalizationCheckApp );
  2641. //-----------------------------------------------------------------------------
  2642. // The application object
  2643. //-----------------------------------------------------------------------------
  2644. bool CLocalizationCheckApp::Create()
  2645. {
  2646. SpewOutputFunc( SpewFunc );
  2647. SpewActivate( "localization_check", 2 );
  2648. AppSystemInfo_t appSystems[] =
  2649. {
  2650. { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION },
  2651. { "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION },
  2652. { "", "" } // Required to terminate the list
  2653. };
  2654. return AddSystems( appSystems );
  2655. }
  2656. //-----------------------------------------------------------------------------
  2657. // Init, shutdown
  2658. //-----------------------------------------------------------------------------
  2659. bool CLocalizationCheckApp::PreInit( )
  2660. {
  2661. if ( !BaseClass::PreInit() )
  2662. return false;
  2663. g_pFileSystem = filesystem = g_pFullFileSystem;
  2664. if ( !g_pFullFileSystem || !g_pSoundEmitterSystem || !g_pVGuiLocalize )
  2665. {
  2666. Error( "Unable to load required library interface!\n" );
  2667. return false;
  2668. }
  2669. char workingdir[ 256 ];
  2670. workingdir[0] = 0;
  2671. Q_getwd( workingdir, sizeof( workingdir ) );
  2672. // If they didn't specify -game on the command line, use VPROJECT.
  2673. if ( !SetupSearchPaths( workingdir, false, true ) )
  2674. {
  2675. Warning( "Unable to set up the file system!\n" );
  2676. return false;
  2677. }
  2678. // work out of the root directory (same as the reslists)
  2679. g_pFullFileSystem->AddSearchPath(".", "root");
  2680. return true;
  2681. }
  2682. void CLocalizationCheckApp::PostShutdown( )
  2683. {
  2684. g_pFileSystem = filesystem = NULL;
  2685. BaseClass::PostShutdown();
  2686. }
  2687. //-----------------------------------------------------------------------------
  2688. // Purpose:
  2689. // Input : argc -
  2690. // argv[] -
  2691. // Output : int
  2692. //-----------------------------------------------------------------------------
  2693. int CLocalizationCheckApp::Main()
  2694. {
  2695. char language[ 256 ];
  2696. memset( language, 0, sizeof( language ) );
  2697. bool extractenglish = false;
  2698. bool forceducking = false;
  2699. int iArg = 1;
  2700. int argc = CommandLine()->ParmCount();
  2701. for (; iArg<argc ; iArg++)
  2702. {
  2703. char const *pArg = CommandLine()->GetParm( iArg );
  2704. if ( pArg[ 0 ] == '-' )
  2705. {
  2706. switch( pArg[ 1 ] )
  2707. {
  2708. case 'p':
  2709. extractenglish = true;
  2710. break;
  2711. case 'v':
  2712. verbose = true;
  2713. break;
  2714. case 'r':
  2715. regenerate = true;
  2716. break;
  2717. case 'q':
  2718. regenerate_quiet = true;
  2719. break;
  2720. case 'u':
  2721. generate_usage = true;
  2722. break;
  2723. case 'b':
  2724. nuke = true;
  2725. break;
  2726. case 's':
  2727. checkscriptsounds = true;
  2728. break;
  2729. case 'c':
  2730. build_cc = true;
  2731. break;
  2732. case 'x':
  2733. build_script = true;
  2734. break;
  2735. case 'w':
  2736. wavcheck = true;
  2737. Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) );
  2738. iArg++;
  2739. break;
  2740. case 'e':
  2741. extractphonemes = true;
  2742. Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) );
  2743. iArg++;
  2744. break;
  2745. case 'l':
  2746. if ( !Q_stricmp( &pArg[1], "loop" ) )
  2747. {
  2748. checkforloops = true;
  2749. Q_strncpy( sounddir, CommandLine()->GetParm( iArg + 1 ), sizeof( sounddir ) );
  2750. iArg++;
  2751. }
  2752. else
  2753. {
  2754. uselogfile = true;
  2755. }
  2756. break;
  2757. case 'f':
  2758. if ( !Q_stricmp( pArg, "-forceduck" ))
  2759. {
  2760. forceducking = true;
  2761. break;
  2762. }
  2763. forceextract = true;
  2764. break;
  2765. case 'd':
  2766. checkfordups = true;
  2767. break;
  2768. case 'i':
  2769. {
  2770. importcaptions = true;
  2771. Q_strncpy( importfile, CommandLine()->GetParm( iArg + 1 ), sizeof( importfile ) );
  2772. iArg++;
  2773. }
  2774. break;
  2775. case 'm':
  2776. {
  2777. makecopybatch = true;
  2778. Q_strncpy( fromdir, CommandLine()->GetParm( iArg + 1 ), sizeof( fromdir ) );
  2779. Q_strncpy( todir, CommandLine()->GetParm( iArg + 2 ), sizeof( todir ) );
  2780. iArg += 2;
  2781. }
  2782. break;
  2783. case 'a':
  2784. {
  2785. syncducking = true;
  2786. Q_strncpy( fromdir, CommandLine()->GetParm( iArg + 1 ), sizeof( fromdir ) );
  2787. Q_strncpy( todir, CommandLine()->GetParm( iArg + 2 ), sizeof( todir ) );
  2788. iArg += 2;
  2789. }
  2790. break;
  2791. default:
  2792. printusage();
  2793. break;
  2794. }
  2795. }
  2796. }
  2797. if ( argc < 2 || (iArg != argc ) )
  2798. {
  2799. PrintHeader();
  2800. printusage();
  2801. }
  2802. Q_strncpy( language, CommandLine()->GetParm( argc - 1 ), sizeof( language ) );
  2803. // If it's english, turn off checks.
  2804. if ( !Q_stricmp( language, "english" ) )
  2805. {
  2806. language[ 0 ] = 0;
  2807. }
  2808. if ( !forceducking && !checkforloops && !syncducking && !extractphonemes && !importcaptions && language[0] != 0 )
  2809. {
  2810. // See if it's a valid language
  2811. int idx = CSentence::LanguageForName( language );
  2812. if ( idx == -1 )
  2813. {
  2814. vprint( 0, "\nSkipping language check, '%s' is not a valid language\n", language );
  2815. vprint( 0, "Valid Language Names:\n" );
  2816. for ( int j = 0; j < CC_NUM_LANGUAGES; ++j )
  2817. {
  2818. vprint( 2, "%s\n", CSentence::NameForLanguage( j ) );
  2819. }
  2820. printusage();
  2821. }
  2822. }
  2823. CheckLogFile();
  2824. PrintHeader();
  2825. vprint( 0, " Looking for localization inconsistencies...\n" );
  2826. if ( !checkforloops&& !syncducking && !extractphonemes && !importcaptions && language[0] != 0 )
  2827. {
  2828. vprint( 0, "\nLanguage: %s\n", language );
  2829. }
  2830. vprint( 0, "Initializing stub sound system\n" );
  2831. sound->Init();
  2832. vprint( 0, "Initializing localization database system\n" );
  2833. // Always start with english
  2834. g_pVGuiLocalize->AddFile( "resource/closecaption_english.txt" );
  2835. // Todo add language specific file
  2836. Q_FixSlashes( gamedir );
  2837. Q_strlower( gamedir );
  2838. char vcddir[ 512 ];
  2839. Q_snprintf( vcddir, sizeof( vcddir ), "%sscenes", gamedir );
  2840. char ccdir[ 512 ];
  2841. Q_snprintf( ccdir, sizeof( ccdir ), "%ssound/combined", gamedir );
  2842. vprint( 0, "game dir %s\nvcd dir %s\n\n",
  2843. gamedir,
  2844. vcddir );
  2845. Q_StripTrailingSlash( sounddir );
  2846. Q_StripTrailingSlash( vcddir );
  2847. //
  2848. //ProcessMaterialsDirectory( vmtdir );
  2849. vprint( 0, "Initializing sound emitter system\n" );
  2850. g_pSoundEmitterSystem->ModInit();
  2851. vprint( 0, "Loaded %i sounds\n", g_pSoundEmitterSystem->GetSoundCount() );
  2852. if ( forceducking )
  2853. {
  2854. CUtlVector< CUtlSymbol > wavefiles;
  2855. char workingdir[ 256 ];
  2856. workingdir[0] = 0;
  2857. Q_getwd( workingdir, sizeof( workingdir ) );
  2858. BuildFileList( wavefiles, workingdir, ".wav" );
  2859. vprint( 0, "forcing ducking on %i .wav files in %s\n\n", wavefiles.Count(), workingdir );
  2860. for ( int i = 0; i < wavefiles.Count(); i++ )
  2861. {
  2862. CUtlSymbol& sym = wavefiles[ i ];
  2863. char fn[ 512 ];
  2864. Q_strncpy( fn, g_Analysis.symbols.String( sym ), sizeof( fn ) );
  2865. SetWavFileDucking( fn, true );
  2866. }
  2867. }
  2868. else
  2869. {
  2870. vprint( 0, "Building list of .vcd files\n" );
  2871. CUtlVector< CUtlSymbol > vcdfiles;
  2872. BuildFileList( vcdfiles, vcddir, ".vcd" );
  2873. vprint( 0, "found %i .vcd files\n\n", vcdfiles.Count() );
  2874. if ( extractenglish && !language[0] )
  2875. {
  2876. vprint( 0, "extractenglish: pulling raw english txt from file\n" );
  2877. ExtractEnglish();
  2878. }
  2879. else if ( wavcheck )
  2880. {
  2881. vprint( 0, "wavcheck: building list of known .wav files\n" );
  2882. CUtlVector< CUtlSymbol > wavfiles;
  2883. BuildFileList( wavfiles, va( "%ssound/%s", gamedir, sounddir ), ".wav" );
  2884. vprint( 0, "found %i .wav files\n\n", wavfiles.Count() );
  2885. WavCheck( wavfiles );
  2886. }
  2887. else if ( makecopybatch )
  2888. {
  2889. vprint( 0, "makecopybatch: building list of known .wav files\n" );
  2890. CUtlVector< CUtlSymbol > wavfiles;
  2891. BuildFileList( wavfiles, va( "%ssound/%s", gamedir, sounddir ), ".wav" );
  2892. vprint( 0, "found %i .wav files\n\n", wavfiles.Count() );
  2893. MakeBatchFile( wavfiles, fromdir, todir );
  2894. }
  2895. else if ( extractphonemes )
  2896. {
  2897. vprint( 0, "extractphonemes: building list of known .wav files\n" );
  2898. CUtlVector< CUtlSymbol > wavfiles;
  2899. BuildFileList( wavfiles, sounddir, ".wav" );
  2900. vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", wavfiles.Count(), sounddir );
  2901. ExtractPhonemes( wavfiles );
  2902. }
  2903. else if ( checkforloops )
  2904. {
  2905. vprint( 0, "checkforloops: building list of known .wav files\n" );
  2906. CUtlVector< CUtlSymbol > wavfiles;
  2907. BuildFileList( wavfiles, sounddir, ".wav" );
  2908. vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", wavfiles.Count(), sounddir );
  2909. CheckForLoops( wavfiles );
  2910. }
  2911. else if ( importcaptions )
  2912. {
  2913. vprint( 0, "importcaptions: importing captions from '%s'\n", importfile );
  2914. ImportCaptions( importfile );
  2915. }
  2916. else if ( checkfordups )
  2917. {
  2918. vprint( 0, "checkfordups: checking for duplicate captions\n" );
  2919. CheckDuplcatedText();
  2920. }
  2921. else if ( syncducking )
  2922. {
  2923. vprint( 0, "syncducking: building list of known .wav files in\n %s\n %s\n", fromdir, todir );
  2924. CUtlVector< CUtlSymbol > englishfiles;
  2925. BuildFileList( englishfiles, fromdir, ".wav" );
  2926. vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", englishfiles.Count(), fromdir );
  2927. CUtlVector< CUtlSymbol > localized_files;
  2928. BuildFileList( localized_files, todir, ".wav" );
  2929. vprint( 0, "found %i .wav files in %s (and subdirs)\n\n", localized_files.Count(), todir );
  2930. SyncDucking( englishfiles, localized_files );
  2931. }
  2932. else
  2933. {
  2934. CUtlVector< CUtlSymbol > vcdsinreslist;
  2935. BuildVCDAndMapNameListsFromReslists( vcdsinreslist );
  2936. // Check for missing localization data for all speak events not marked CC_DISABLED
  2937. CUtlRBTree< CUtlSymbol, int > referencedcaptionwaves( 0, 0, SymbolLessFunc );
  2938. vprint( 0, "\nValidating close caption tokens and combined .wav file checksums\n\n" );
  2939. CheckLocalizationEntries( vcdfiles, referencedcaptionwaves );
  2940. vprint( 0, "\nChecking for orphaned combined .wav files\n\n" );
  2941. CUtlVector< CUtlSymbol > combinedwavfiles;
  2942. BuildFileList( combinedwavfiles, ccdir, ".wav" );
  2943. CheckForOrphanedCombinedWavs( combinedwavfiles, referencedcaptionwaves );
  2944. if ( language[0] != 0 )
  2945. {
  2946. vprint( 0, "\nChecking for missing or out of date localized combined .wav files\n\n" );
  2947. ValidateForeignLanguageWaves( language, combinedwavfiles );
  2948. }
  2949. if ( generate_usage )
  2950. {
  2951. // Figure out which .vcds are unused
  2952. CheckUnusedVcds( vcdsinreslist, vcdfiles );
  2953. }
  2954. if ( checkscriptsounds )
  2955. {
  2956. CheckUnusedSounds();
  2957. }
  2958. }
  2959. }
  2960. vprint( 0, "\nCleaning up...\n" );
  2961. g_pSoundEmitterSystem->ModShutdown();
  2962. // Unload localization system
  2963. g_pVGuiLocalize->RemoveAll();
  2964. sound->Shutdown();
  2965. FileSystem_Term();
  2966. g_Analysis.symbols.RemoveAll();
  2967. return 0;
  2968. }