Counter Strike : Global Offensive Source Code
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.

727 lines
18 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //===========================================================================//
  8. #include "cbase.h"
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <sys/stat.h>
  12. #include "filesystem.h"
  13. #include "mxtk/mx.h"
  14. #include "mxStatusWindow.h"
  15. #include "FileSystem.h"
  16. #include "StudioModel.h"
  17. #include "ControlPanel.h"
  18. #include "MDLViewer.h"
  19. #include "mxExpressionTray.H"
  20. #include "viewersettings.h"
  21. #include "tier1/strtools.h"
  22. #include "faceposer_models.h"
  23. #include "expressions.h"
  24. #include "choreoview.h"
  25. #include "choreoscene.h"
  26. #include "vstdlib/random.h"
  27. #include "SoundEmitterSystem/isoundemittersystembase.h"
  28. #include "soundchars.h"
  29. #include "sentence.h"
  30. #include "PhonemeEditor.h"
  31. #include <vgui/ILocalize.h>
  32. #include "filesystem_init.h"
  33. #include "tier2/p4helpers.h"
  34. StudioModel *FindAssociatedModel( CChoreoScene *scene, CChoreoActor *a );
  35. //-----------------------------------------------------------------------------
  36. // Purpose: Takes a full path and determines if the file exists on the disk
  37. // Input : *filename -
  38. // Output : Returns true on success, false on failure.
  39. //-----------------------------------------------------------------------------
  40. bool FPFullpathFileExists( const char *filename )
  41. {
  42. // Should be a full path
  43. Assert( strchr( filename, ':' ) );
  44. struct _stat buf;
  45. int result = _stat( filename, &buf );
  46. if ( result != -1 )
  47. return true;
  48. return false;
  49. }
  50. // Utility functions mostly
  51. char *FacePoser_MakeWindowsSlashes( char *pname )
  52. {
  53. static char returnString[ 4096 ];
  54. strcpy( returnString, pname );
  55. pname = returnString;
  56. while ( *pname )
  57. {
  58. if ( *pname == '/' )
  59. {
  60. *pname = '\\';
  61. }
  62. pname++;
  63. }
  64. return returnString;
  65. }
  66. //-----------------------------------------------------------------------------
  67. // Purpose:
  68. // Output : int
  69. //-----------------------------------------------------------------------------
  70. int GetCloseCaptionLanguageId()
  71. {
  72. return g_viewerSettings.cclanguageid;
  73. }
  74. //-----------------------------------------------------------------------------
  75. // Purpose:
  76. // Input : id -
  77. //-----------------------------------------------------------------------------
  78. void SetCloseCaptionLanguageId( int id, bool force /* = false */ )
  79. {
  80. Assert( id >= 0 && id < CC_NUM_LANGUAGES );
  81. bool changed = g_viewerSettings.cclanguageid != id;
  82. g_viewerSettings.cclanguageid = id;
  83. if ( changed || force )
  84. {
  85. // Switch languages
  86. char const *suffix = CSentence::NameForLanguage( id );
  87. if ( Q_stricmp( suffix, "unknown_language" ) )
  88. {
  89. char fn[ MAX_PATH ];
  90. Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", suffix );
  91. g_pLocalize->RemoveAll();
  92. if ( Q_stricmp( suffix, "english" )&&
  93. filesystem->FileExists( "resource/closecaption_english.txt" ) )
  94. {
  95. g_pLocalize->AddFile( "resource/closecaption_english.txt", "GAME", true );
  96. }
  97. if ( filesystem->FileExists( fn ) )
  98. {
  99. g_pLocalize->AddFile( fn, "GAME", true );
  100. }
  101. else
  102. {
  103. Con_ErrorPrintf( "PhonemeEditor::SetCloseCaptionLanguageId Warning, can't find localization file %s\n", fn );
  104. }
  105. // Need to redraw the choreoview at least
  106. if ( g_pChoreoView )
  107. {
  108. g_pChoreoView->InvalidateLayout();
  109. }
  110. }
  111. }
  112. if ( g_MDLViewer )
  113. {
  114. g_MDLViewer->UpdateLanguageMenu( id );
  115. }
  116. }
  117. char *va( const char *fmt, ... )
  118. {
  119. va_list args;
  120. static char output[32][1024];
  121. static int outbuffer = 0;
  122. outbuffer++;
  123. va_start( args, fmt );
  124. vprintf( fmt, args );
  125. vsprintf( output[ outbuffer & 31 ], fmt, args );
  126. return output[ outbuffer & 31 ];
  127. }
  128. void Con_Printf( const char *fmt, ... )
  129. {
  130. va_list args;
  131. static char output[1024];
  132. va_start( args, fmt );
  133. vprintf( fmt, args );
  134. vsprintf( output, fmt, args );
  135. if ( !g_pStatusWindow )
  136. {
  137. return;
  138. }
  139. g_pStatusWindow->StatusPrint( CONSOLE_COLOR, false, output );
  140. }
  141. void Con_ColorPrintf( const Color& rgb, const char *fmt, ... )
  142. {
  143. va_list args;
  144. static char output[1024];
  145. va_start( args, fmt );
  146. vprintf( fmt, args );
  147. vsprintf( output, fmt, args );
  148. if ( !g_pStatusWindow )
  149. {
  150. return;
  151. }
  152. g_pStatusWindow->StatusPrint( rgb, false, output );
  153. }
  154. void Con_ErrorPrintf( const char *fmt, ... )
  155. {
  156. va_list args;
  157. static char output[1024];
  158. va_start( args, fmt );
  159. vprintf( fmt, args );
  160. vsprintf( output, fmt, args );
  161. if ( !g_pStatusWindow )
  162. {
  163. return;
  164. }
  165. g_pStatusWindow->StatusPrint( ERROR_COLOR, false, output );
  166. }
  167. //-----------------------------------------------------------------------------
  168. // Purpose:
  169. // Input : *filename -
  170. //-----------------------------------------------------------------------------
  171. void MakeFileWriteable( const char *filename )
  172. {
  173. Assert( filesystem );
  174. char pFullPathBuf[ 512 ];
  175. char *pFullPath;
  176. if ( !Q_IsAbsolutePath( filename ) )
  177. {
  178. pFullPath = (char*)filesystem->RelativePathToFullPath( filename, NULL, pFullPathBuf, sizeof(pFullPathBuf) );
  179. }
  180. else
  181. {
  182. Q_strncpy( pFullPathBuf, filename, sizeof(pFullPathBuf) );
  183. pFullPath = pFullPathBuf;
  184. }
  185. if ( pFullPath )
  186. {
  187. Q_FixSlashes( pFullPath );
  188. SetFileAttributes( pFullPath, FILE_ATTRIBUTE_NORMAL );
  189. }
  190. }
  191. //-----------------------------------------------------------------------------
  192. // Purpose:
  193. // Input : *filename -
  194. // Output : Returns true on success, false on failure.
  195. //-----------------------------------------------------------------------------
  196. bool IsFileWriteable( const char *filename )
  197. {
  198. Assert( filesystem );
  199. char pFullPathBuf[ 512 ];
  200. char *pFullPath;
  201. if ( !Q_IsAbsolutePath( filename ) )
  202. {
  203. pFullPath = (char*)filesystem->RelativePathToFullPath( filename, NULL, pFullPathBuf, sizeof(pFullPathBuf) );
  204. }
  205. else
  206. {
  207. Q_strncpy( pFullPathBuf, filename, sizeof(pFullPathBuf) );
  208. pFullPath = pFullPathBuf;
  209. }
  210. if ( pFullPath )
  211. {
  212. Q_FixSlashes( pFullPath );
  213. DWORD attrib = GetFileAttributes( pFullPath );
  214. return ( ( attrib & FILE_ATTRIBUTE_READONLY ) == 0 );
  215. }
  216. // Doesn't seem to exist, so yeah, it's writable
  217. return true;
  218. }
  219. bool MakeFileWriteablePrompt( const char *filename, char const *promptTitle )
  220. {
  221. if ( !IsFileWriteable( filename ) )
  222. {
  223. int retval = mxMessageBox( NULL, va( "File '%s' is Read-Only, make writable?", filename ),
  224. promptTitle, MX_MB_WARNING | MX_MB_YESNO );
  225. // Didn't pick yes, bail
  226. if ( retval != 0 )
  227. return false;
  228. MakeFileWriteable( filename );
  229. }
  230. return true;
  231. }
  232. void FPCopyFile( const char *source, const char *dest, bool bCheckOut )
  233. {
  234. Assert( filesystem );
  235. char fullpaths[ MAX_PATH ];
  236. char fullpathd[ MAX_PATH ];
  237. if ( !Q_IsAbsolutePath( source ) )
  238. {
  239. filesystem->RelativePathToFullPath( source, NULL, fullpaths, sizeof(fullpaths) );
  240. }
  241. else
  242. {
  243. Q_strncpy( fullpaths, source, sizeof(fullpaths) );
  244. }
  245. Q_strncpy( fullpathd, fullpaths, MAX_PATH );
  246. char *pSubdir = Q_stristr( fullpathd, source );
  247. if ( pSubdir )
  248. {
  249. *pSubdir = 0;
  250. }
  251. Q_AppendSlash( fullpathd, MAX_PATH );
  252. Q_strncat( fullpathd, dest, MAX_PATH, MAX_PATH );
  253. Q_FixSlashes( fullpaths );
  254. Q_FixSlashes( fullpathd );
  255. if ( bCheckOut )
  256. {
  257. CP4AutoEditAddFile checkout( fullpathd );
  258. CopyFile( fullpaths, fullpathd, FALSE );
  259. }
  260. else
  261. {
  262. CopyFile( fullpaths, fullpathd, FALSE );
  263. }
  264. }
  265. bool FacePoser_HasWindowStyle( mxWindow *w, int bits )
  266. {
  267. HWND wnd = (HWND)w->getHandle();
  268. DWORD style = GetWindowLong( wnd, GWL_STYLE );
  269. return ( style & bits ) ? true : false;
  270. }
  271. void FacePoser_AddWindowStyle( mxWindow *w, int addbits )
  272. {
  273. HWND wnd = (HWND)w->getHandle();
  274. DWORD style = GetWindowLong( wnd, GWL_STYLE );
  275. style |= addbits;
  276. SetWindowLong( wnd, GWL_STYLE, style );
  277. }
  278. void FacePoser_AddWindowExStyle( mxWindow *w, int addbits )
  279. {
  280. HWND wnd = (HWND)w->getHandle();
  281. DWORD style = GetWindowLong( wnd, GWL_EXSTYLE );
  282. style |= addbits;
  283. SetWindowLong( wnd, GWL_EXSTYLE, style );
  284. }
  285. void FacePoser_RemoveWindowStyle( mxWindow *w, int removebits )
  286. {
  287. HWND wnd = (HWND)w->getHandle();
  288. DWORD style = GetWindowLong( wnd, GWL_STYLE );
  289. style &= ~removebits;
  290. SetWindowLong( wnd, GWL_STYLE, style );
  291. }
  292. //-----------------------------------------------------------------------------
  293. // Purpose:
  294. // Input : *w -
  295. //-----------------------------------------------------------------------------
  296. void FacePoser_MakeToolWindow( mxWindow *w, bool smallcaption )
  297. {
  298. FacePoser_AddWindowStyle( w, WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS );
  299. if ( smallcaption )
  300. {
  301. FacePoser_AddWindowExStyle( w, WS_EX_OVERLAPPEDWINDOW );
  302. FacePoser_AddWindowExStyle( w, WS_EX_TOOLWINDOW );
  303. }
  304. }
  305. bool LoadViewerSettingsInt( char const *keyname, int *value );
  306. bool SaveViewerSettingsInt ( const char *keyname, int value );
  307. void FacePoser_LoadWindowPositions( char const *name, bool& visible, int& x, int& y, int& w, int& h, bool& locked, bool& zoomed )
  308. {
  309. char subkey[ 512 ];
  310. int v;
  311. Q_snprintf( subkey, sizeof( subkey ), "%s - visible", name );
  312. LoadViewerSettingsInt( subkey, &v );
  313. visible = v ? true : false;
  314. Q_snprintf( subkey, sizeof( subkey ), "%s - locked", name );
  315. LoadViewerSettingsInt( subkey, &v );
  316. locked = v ? true : false;
  317. Q_snprintf( subkey, sizeof( subkey ), "%s - zoomed", name );
  318. LoadViewerSettingsInt( subkey, &v );
  319. zoomed = v ? true : false;
  320. Q_snprintf( subkey, sizeof( subkey ), "%s - x", name );
  321. LoadViewerSettingsInt( subkey, &x );
  322. Q_snprintf( subkey, sizeof( subkey ), "%s - y", name );
  323. LoadViewerSettingsInt( subkey, &y );
  324. Q_snprintf( subkey, sizeof( subkey ), "%s - width", name );
  325. LoadViewerSettingsInt( subkey, &w );
  326. Q_snprintf( subkey, sizeof( subkey ), "%s - height", name );
  327. LoadViewerSettingsInt( subkey, &h );
  328. }
  329. void FacePoser_SaveWindowPositions( char const *name, bool visible, int x, int y, int w, int h, bool locked, bool zoomed )
  330. {
  331. char subkey[ 512 ];
  332. Q_snprintf( subkey, sizeof( subkey ), "%s - visible", name );
  333. SaveViewerSettingsInt( subkey, visible );
  334. Q_snprintf( subkey, sizeof( subkey ), "%s - locked", name );
  335. SaveViewerSettingsInt( subkey, locked );
  336. Q_snprintf( subkey, sizeof( subkey ), "%s - x", name );
  337. SaveViewerSettingsInt( subkey, x );
  338. Q_snprintf( subkey, sizeof( subkey ), "%s - y", name );
  339. SaveViewerSettingsInt( subkey, y );
  340. Q_snprintf( subkey, sizeof( subkey ), "%s - width", name );
  341. SaveViewerSettingsInt( subkey, w );
  342. Q_snprintf( subkey, sizeof( subkey ), "%s - height", name );
  343. SaveViewerSettingsInt( subkey, h );
  344. Q_snprintf( subkey, sizeof( subkey ), "%s - zoomed", name );
  345. SaveViewerSettingsInt( subkey, zoomed );
  346. }
  347. static char g_PhonemeRoot[ MAX_PATH ] = { 0 };
  348. void FacePoser_SetPhonemeRootDir( char const *pchRootDir )
  349. {
  350. Q_strncpy( g_PhonemeRoot, pchRootDir, sizeof( g_PhonemeRoot ) );
  351. }
  352. //-----------------------------------------------------------------------------
  353. // Purpose:
  354. //-----------------------------------------------------------------------------
  355. void FacePoser_EnsurePhonemesLoaded( void )
  356. {
  357. // Don't bother unless a model is loaded, at least...
  358. CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr();
  359. if ( !hdr )
  360. {
  361. return;
  362. }
  363. char const *ext[] =
  364. {
  365. "",
  366. "_strong",
  367. "_weak",
  368. };
  369. for ( int i = 0 ; i < ARRAYSIZE( ext ); ++i )
  370. {
  371. char clname[ 256 ];
  372. Q_snprintf( clname, sizeof( clname ), "%sphonemes%s", g_PhonemeRoot, ext[ i ] );
  373. Q_FixSlashes( clname );
  374. Q_strlower( clname );
  375. if ( !expressions->FindClass( clname, false ) )
  376. {
  377. char clfile[ MAX_PATH ];
  378. Q_snprintf( clfile, sizeof( clfile ), "expressions/%sphonemes%s.txt", g_PhonemeRoot, ext[ i ] );
  379. Q_FixSlashes( clfile );
  380. Q_strlower( clfile );
  381. if ( g_pFileSystem->FileExists( clfile ) )
  382. {
  383. expressions->LoadClass( clfile );
  384. CExpClass *cl = expressions->FindClass( clname, false );
  385. if ( !cl )
  386. {
  387. Con_Printf( "FacePoser_EnsurePhonemesLoaded: %s missing!!!\n", clfile );
  388. }
  389. }
  390. }
  391. }
  392. }
  393. bool FacePoser_ShowFileNameDialog( bool openFile, char *relative, size_t bufsize, char const *subdir, char const *wildcard )
  394. {
  395. Assert( relative );
  396. relative[ 0 ] = 0 ;
  397. Assert( subdir );
  398. Assert( wildcard );
  399. char workingdir[ 256 ];
  400. Q_getwd( workingdir, sizeof( workingdir ) );
  401. strlwr( workingdir );
  402. Q_FixSlashes( workingdir, '/' );
  403. // Show file io
  404. bool inWorkingDirectoryAlready = false;
  405. if ( Q_stristr_slash( workingdir, va( "%s%s", GetGameDirectory(), subdir ) ) )
  406. {
  407. inWorkingDirectoryAlready = true;
  408. }
  409. // Show file io
  410. const char *fullpath = NULL;
  411. if ( openFile )
  412. {
  413. fullpath = mxGetOpenFileName(
  414. 0,
  415. inWorkingDirectoryAlready ? "." : FacePoser_MakeWindowsSlashes( va( "%s%s/", GetGameDirectory(), subdir ) ),
  416. wildcard );
  417. }
  418. else
  419. {
  420. fullpath = mxGetSaveFileName(
  421. 0,
  422. inWorkingDirectoryAlready ? "." : FacePoser_MakeWindowsSlashes( va( "%s%s/", GetGameDirectory(), subdir ) ),
  423. wildcard );
  424. }
  425. if ( !fullpath || !fullpath[ 0 ] )
  426. return false;
  427. Q_strncpy( relative, fullpath, bufsize );
  428. return true;
  429. }
  430. bool FacePoser_ShowOpenFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard )
  431. {
  432. return FacePoser_ShowFileNameDialog( true, relative, bufsize, subdir, wildcard );
  433. }
  434. bool FacePoser_ShowSaveFileNameDialog( char *relative, size_t bufsize, char const *subdir, char const *wildcard )
  435. {
  436. return FacePoser_ShowFileNameDialog( false, relative, bufsize, subdir, wildcard );
  437. }
  438. //-----------------------------------------------------------------------------
  439. // Purpose: converts an english string to unicode
  440. //-----------------------------------------------------------------------------
  441. int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize)
  442. {
  443. return ::MultiByteToWideChar(CP_ACP, 0, ansi, -1, unicode, unicodeBufferSize);
  444. }
  445. //-----------------------------------------------------------------------------
  446. // Purpose: converts an unicode string to an english string
  447. //-----------------------------------------------------------------------------
  448. int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize)
  449. {
  450. return ::WideCharToMultiByte(CP_ACP, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
  451. }
  452. //-----------------------------------------------------------------------------
  453. // Purpose: If FPS is set and "using grid", snap to proper fractional time value
  454. // Input : t -
  455. // Output : float
  456. //-----------------------------------------------------------------------------
  457. float FacePoser_SnapTime( float t )
  458. {
  459. if ( !g_pChoreoView )
  460. return t;
  461. CChoreoScene *scene = g_pChoreoView->GetScene();
  462. if ( !scene )
  463. return t;
  464. return scene->SnapTime( t );
  465. }
  466. //-----------------------------------------------------------------------------
  467. // Purpose:
  468. // Input : t -
  469. // Output : char const
  470. //-----------------------------------------------------------------------------
  471. char const *FacePoser_DescribeSnappedTime( float t )
  472. {
  473. static char desc[ 128 ];
  474. Q_snprintf( desc, sizeof( desc ), "%.3f", t );
  475. if ( !g_pChoreoView )
  476. return desc;
  477. CChoreoScene *scene = g_pChoreoView->GetScene();
  478. if ( !scene )
  479. return desc;
  480. t = scene->SnapTime( t );
  481. int fps = scene->GetSceneFPS();
  482. int ipart = (int)t;
  483. int fracpart = (int)( ( t - (float)ipart ) * (float)fps + 0.5f );
  484. int frame = ipart * fps + fracpart;
  485. if ( fracpart == 0 )
  486. {
  487. Q_snprintf( desc, sizeof( desc ), "frame %i (time %i s.)", frame, ipart );
  488. }
  489. else
  490. {
  491. Q_snprintf( desc, sizeof( desc ), "frame %i (time %i + %i/%i s.)",
  492. frame, ipart,fracpart, fps );
  493. }
  494. return desc;
  495. }
  496. //-----------------------------------------------------------------------------
  497. // Purpose:
  498. // Output : int
  499. //-----------------------------------------------------------------------------
  500. int FacePoser_GetSceneFPS( void )
  501. {
  502. if ( !g_pChoreoView )
  503. return 1000;
  504. CChoreoScene *scene = g_pChoreoView->GetScene();
  505. if ( !scene )
  506. return 1000;
  507. return scene->GetSceneFPS();
  508. }
  509. //-----------------------------------------------------------------------------
  510. // Purpose:
  511. // Output : Returns true on success, false on failure.
  512. //-----------------------------------------------------------------------------
  513. bool FacePoser_IsSnapping( void )
  514. {
  515. if ( !g_pChoreoView )
  516. return false;
  517. CChoreoScene *scene = g_pChoreoView->GetScene();
  518. if ( !scene )
  519. return false;
  520. return scene->IsUsingFrameSnap();
  521. }
  522. char const *FacePoser_TranslateSoundNameGender( char const *soundname, gender_t gender )
  523. {
  524. if ( Q_stristr( soundname, ".wav" ) )
  525. return PSkipSoundChars( soundname );
  526. return PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, gender ) );
  527. }
  528. char const *FacePoser_TranslateSoundName( char const *soundname, StudioModel *model /*= NULL*/ )
  529. {
  530. if ( Q_stristr( soundname, ".wav" ) )
  531. return PSkipSoundChars( soundname );
  532. static char temp[ 256 ];
  533. if ( model )
  534. {
  535. Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, model->GetFileName() ) ), sizeof( temp ) );
  536. }
  537. else
  538. {
  539. Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, NULL ) ), sizeof( temp ) );
  540. }
  541. return temp;
  542. }
  543. char const *FacePoser_TranslateSoundName( CChoreoEvent *event )
  544. {
  545. char const *soundname = event->GetParameters();
  546. if ( Q_stristr( soundname, ".wav" ) )
  547. return PSkipSoundChars( soundname );
  548. // See if we can figure out the .mdl associated to this event's actor
  549. static char temp[ 256 ];
  550. temp[ 0 ] = 0;
  551. StudioModel *model = NULL;
  552. CChoreoActor *a = event->GetActor();
  553. CChoreoScene *s = event->GetScene();
  554. if ( a != NULL &&
  555. s != NULL )
  556. {
  557. model = FindAssociatedModel( s, a );
  558. }
  559. Q_strncpy( temp, PSkipSoundChars( soundemitter->GetWavFileForSound( soundname, model ? model->GetFileName() : NULL ) ), sizeof( temp ) );
  560. return temp;
  561. }
  562. #if defined( _WIN32 ) || defined( WIN32 )
  563. #define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/')
  564. #else //_WIN32
  565. #define PATHSEPARATOR(c) ((c) == '/')
  566. #endif //_WIN32
  567. static bool charsmatch( char c1, char c2 )
  568. {
  569. if ( tolower( c1 ) == tolower( c2 ) )
  570. return true;
  571. if ( PATHSEPARATOR( c1 ) && PATHSEPARATOR( c2 ) )
  572. return true;
  573. return false;
  574. }
  575. char *Q_stristr_slash( char const *pStr, char const *pSearch )
  576. {
  577. AssertValidStringPtr(pStr);
  578. AssertValidStringPtr(pSearch);
  579. if (!pStr || !pSearch)
  580. return 0;
  581. char const* pLetter = pStr;
  582. // Check the entire string
  583. while (*pLetter != 0)
  584. {
  585. // Skip over non-matches
  586. if ( charsmatch( *pLetter, *pSearch ) )
  587. {
  588. // Check for match
  589. char const* pMatch = pLetter + 1;
  590. char const* pTest = pSearch + 1;
  591. while (*pTest != 0)
  592. {
  593. // We've run off the end; don't bother.
  594. if (*pMatch == 0)
  595. return 0;
  596. if ( !charsmatch( *pMatch, *pTest ) )
  597. break;
  598. ++pMatch;
  599. ++pTest;
  600. }
  601. // Found a match!
  602. if (*pTest == 0)
  603. return (char *)pLetter;
  604. }
  605. ++pLetter;
  606. }
  607. return 0;
  608. }
  609. static CUniformRandomStream g_Random;
  610. IUniformRandomStream *random = &g_Random;