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.

1605 lines
48 KiB

  1. //======= Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #pragma warning( disable: 4018 ) // '==' : signed/unsigned mismatch in rbtree
  8. #if defined( WIN32 ) && !defined( _X360 )
  9. #include <windows.h>
  10. #include <vadefs.h>
  11. #elif defined( _PS3 )
  12. #elif defined( POSIX )
  13. #include <iconv.h>
  14. #endif
  15. #include <wchar.h>
  16. #include "filesystem.h"
  17. #include "localize/ilocalize.h"
  18. #include "tier1/utlvector.h"
  19. #include "tier1/utlrbtree.h"
  20. #include "tier1/utlsymbol.h"
  21. #include "tier1/utlstring.h"
  22. #include "UnicodeFileHelpers.h"
  23. #include "tier0/icommandline.h"
  24. #include "byteswap.h"
  25. #include "exprevaluator.h"
  26. #include "iregistry.h"
  27. #include <vstdlib/vstrtools.h>
  28. #include "vgui/ISystem.h"
  29. #include "vgui_controls/Controls.h"
  30. #if defined( _X360 )
  31. #include "xbox/xbox_win32stubs.h"
  32. #endif
  33. // memdbgon must be the last include file in a .cpp file!!!
  34. #include "tier0/memdbgon.h"
  35. #define MAX_LOCALIZED_CHARS 4096
  36. //-----------------------------------------------------------------------------
  37. //
  38. // Internal implementation
  39. //
  40. //-----------------------------------------------------------------------------
  41. //-----------------------------------------------------------------------------
  42. // Purpose: Maps token names to localized unicode strings
  43. //-----------------------------------------------------------------------------
  44. class CLocalize : public CTier2AppSystem< ILocalize >
  45. {
  46. typedef CTier2AppSystem< ILocalize > BaseClass;
  47. // Methods of IAppSystem
  48. public:
  49. virtual InitReturnVal_t Init();
  50. // ILocalize overrides
  51. public:
  52. virtual bool AddFile( const char *fileName, const char *pPathID, bool bIncludeFallbackSearchPaths );
  53. virtual void RemoveAll();
  54. virtual wchar_t *Find(const char *pName);
  55. virtual const wchar_t *FindSafe(const char *tokenName);
  56. virtual int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSizeInBytes);
  57. virtual int ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize);
  58. virtual LocalizeStringIndex_t FindIndex(const char *pName);
  59. virtual const char *GetNameByIndex(LocalizeStringIndex_t index);
  60. virtual wchar_t *GetValueByIndex(LocalizeStringIndex_t index);
  61. virtual LocalizeStringIndex_t GetFirstStringIndex();
  62. virtual LocalizeStringIndex_t GetNextStringIndex(LocalizeStringIndex_t index);
  63. virtual void AddString(const char *tokenName, wchar_t *unicodeString, const char *fileName);
  64. virtual void SetValueByIndex(LocalizeStringIndex_t index, wchar_t *newValue);
  65. virtual bool SaveToFile( const char *fileName );
  66. virtual int GetLocalizationFileCount();
  67. virtual const char *GetLocalizationFileName(int index);
  68. virtual const char *GetFileNameByIndex(LocalizeStringIndex_t index);
  69. virtual void ReloadLocalizationFiles( );
  70. virtual void ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const char *tokenName, KeyValues *dialogVariables);
  71. virtual void ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, LocalizeStringIndex_t unlocalizedTextSymbol, KeyValues *dialogVariables);
  72. virtual void SetTextQuery( ILocalizeTextQuery *pQuery );
  73. virtual void InstallChangeCallback( ILocalizationChangeCallback *pCallback );
  74. virtual void RemoveChangeCallback( ILocalizationChangeCallback *pCallback );
  75. virtual const char *FindAsUTF8( const char *pchTokenName );
  76. virtual wchar_t* GetAsianFrequencySequence( const char * pLanguage );
  77. protected:
  78. // internal "interface"
  79. virtual void ConstructStringVArgsInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, int numFormatParameters, va_list argList);
  80. virtual void ConstructStringVArgsInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, int numFormatParameters, va_list argList);
  81. virtual void ConstructStringKeyValuesInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, KeyValues *localizationVariables);
  82. virtual void ConstructStringKeyValuesInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, KeyValues *localizationVariables);
  83. // Other public methods
  84. public:
  85. CLocalize();
  86. virtual ~CLocalize();
  87. // returns whether a file has already been loaded
  88. bool LocalizationFileIsLoaded( const char *name );
  89. private:
  90. struct localizedstring_t
  91. {
  92. LocalizeStringIndex_t nameIndex;
  93. // nameIndex == LOCALIZE_INVALID_STRING_INDEX is used only for searches and implies
  94. // that pszValueString will be used from union fields.
  95. union
  96. {
  97. LocalizeStringIndex_t valueIndex; // Used when nameIndex != LOCALIZE_INVALID_STRING_INDEX
  98. const char * pszValueString; // Used only if nameIndex == LOCALIZE_INVALID_STRING_INDEX
  99. };
  100. CUtlSymbol filename;
  101. };
  102. struct LocalizationFileInfo_t
  103. {
  104. CUtlSymbol symName;
  105. CUtlSymbol symPathID;
  106. bool bIncludeFallbacks;
  107. static bool LessFunc( const LocalizationFileInfo_t& lhs, const LocalizationFileInfo_t& rhs )
  108. {
  109. int iresult = Q_stricmp( lhs.symPathID.String(), rhs.symPathID.String() );
  110. if ( iresult != 0 )
  111. {
  112. return iresult == -1;
  113. }
  114. return Q_stricmp( lhs.symName.String(), rhs.symName.String() ) < 0;
  115. }
  116. };
  117. struct fastvalue_t
  118. {
  119. int valueindex;
  120. const wchar_t *search;
  121. static CLocalize *s_pTable;
  122. };
  123. private:
  124. bool AddAllLanguageFiles( const char *baseFileName );
  125. void BuildFastValueLookup();
  126. void DiscardFastValueLookup();
  127. int FindExistingValueIndex( const wchar_t *value );
  128. bool ReadLocalizationFile( const char *pRelativePath, const char *pPathID );
  129. void InvokeChangeCallbacks( );
  130. virtual int ConvertANSIToUCS2(const char *ansi, OUT_Z_BYTECAP(unicodeBufferSizeInBytes) ucs2 *unicode, int unicodeBufferSizeInBytes);
  131. virtual int ConvertUCS2ToANSI(const ucs2 *unicode, OUT_Z_BYTECAP(ansiBufferSize) char *ansi, int ansiBufferSize);
  132. #if defined ( POSIX ) && !defined( _PS3 )
  133. virtual void AddString(const char *tokenName, ucs2 *unicodeString, const char *fileName);
  134. #endif
  135. char m_szLanguage[64];
  136. bool m_bUseOnlyLongestLanguageString;
  137. bool m_bSuppressChangeCallbacks;
  138. bool m_bQueuedChangeCallback;
  139. // Stores the symbol lookup
  140. CUtlRBTree<localizedstring_t, LocalizeStringIndex_t> m_Lookup;
  141. // stores the string data
  142. CUtlVector<char> m_Names;
  143. CUtlVector<wchar_t> m_Values;
  144. CUtlSymbol m_CurrentFile;
  145. CUtlVector< LocalizationFileInfo_t > m_LocalizationFiles;
  146. CUtlRBTree< fastvalue_t, int > m_FastValueLookup;
  147. ILocalizeTextQuery *m_pQuery;
  148. static CLocalize *s_pTable;
  149. CUtlVector< ILocalizationChangeCallback* > m_ChangeCallbacks;
  150. CUtlBuffer m_bufAsianFrequencySequence;
  151. bool m_bAsianFrequencySequenceLoaded;
  152. // Less function, for sorting strings
  153. static bool SymLess( localizedstring_t const& i1, localizedstring_t const& i2 );
  154. static bool FastValueLessFunc( const fastvalue_t& lhs, const fastvalue_t& rhs );
  155. };
  156. // global instance of table
  157. static CLocalize s_Localize;
  158. // expose the interface
  159. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CLocalize, ILocalize, LOCALIZE_INTERFACE_VERSION, s_Localize);
  160. //-----------------------------------------------------------------------------
  161. // Purpose: Constructor
  162. //-----------------------------------------------------------------------------
  163. CLocalize::CLocalize() :
  164. m_Lookup( 0, 0, SymLess ), m_Names( 1024 ), m_Values( 2048 ), m_FastValueLookup( 0, 0, FastValueLessFunc )
  165. {
  166. m_bUseOnlyLongestLanguageString = false;
  167. m_bSuppressChangeCallbacks = false;
  168. m_bQueuedChangeCallback = false;
  169. m_pQuery = NULL;
  170. m_bAsianFrequencySequenceLoaded = false;
  171. }
  172. //-----------------------------------------------------------------------------
  173. // Purpose: Destructor
  174. //-----------------------------------------------------------------------------
  175. CLocalize::~CLocalize()
  176. {
  177. m_Names.Purge();
  178. m_Values.Purge();
  179. m_LocalizationFiles.Purge();
  180. }
  181. //-----------------------------------------------------------------------------
  182. // Init
  183. //-----------------------------------------------------------------------------
  184. InitReturnVal_t CLocalize::Init()
  185. {
  186. InitReturnVal_t nRetVal = BaseClass::Init();
  187. if ( nRetVal != INIT_OK )
  188. return nRetVal;
  189. m_bUseOnlyLongestLanguageString = ( CommandLine()->FindParm("-all_languages") > 0 );
  190. return INIT_OK;
  191. }
  192. //-----------------------------------------------------------------------------
  193. // Sets the callback used to check length of a localization string
  194. //-----------------------------------------------------------------------------
  195. void CLocalize::SetTextQuery( ILocalizeTextQuery *pQuery )
  196. {
  197. m_pQuery = pQuery;
  198. }
  199. //-----------------------------------------------------------------------------
  200. // Add, remove, invoke localization string change callbacks
  201. //-----------------------------------------------------------------------------
  202. void CLocalize::InstallChangeCallback( ILocalizationChangeCallback *pCallback )
  203. {
  204. if ( m_ChangeCallbacks.Find( pCallback ) != m_ChangeCallbacks.InvalidIndex() )
  205. {
  206. Warning( "CLocalize::InstallChangeCallback: Attempted to add the same callback twice!\n" );
  207. return;
  208. }
  209. m_ChangeCallbacks.AddToTail( pCallback );
  210. }
  211. void CLocalize::RemoveChangeCallback( ILocalizationChangeCallback *pCallback )
  212. {
  213. m_ChangeCallbacks.FindAndRemove( pCallback );
  214. }
  215. //-----------------------------------------------------------------------------
  216. // Purpose: Finds a string in the table
  217. //-----------------------------------------------------------------------------
  218. const char *CLocalize::FindAsUTF8( const char *pchTokenName )
  219. {
  220. wchar_t *pwch = Find( pchTokenName );
  221. if ( !pwch )
  222. return pchTokenName;
  223. static char rgchT[2048];
  224. Q_UnicodeToUTF8( pwch, rgchT, sizeof( rgchT ) );
  225. return rgchT;
  226. }
  227. void CLocalize::InvokeChangeCallbacks( )
  228. {
  229. // This is to prevent a ton of change callbacks while loading using -all_languages
  230. if ( m_bSuppressChangeCallbacks )
  231. {
  232. m_bQueuedChangeCallback = true;
  233. return;
  234. }
  235. int nCount = m_ChangeCallbacks.Count();
  236. for ( int i = 0; i < nCount; ++i )
  237. {
  238. m_ChangeCallbacks[i]->OnLocalizationChanged();
  239. }
  240. }
  241. int DistanceToEndOfLine( ucs2 *start )
  242. {
  243. int nResult = 0;
  244. if ( !*start )
  245. {
  246. return nResult;
  247. }
  248. while ( *start )
  249. {
  250. if ( *start == 0x0D || *start== 0x0A )
  251. {
  252. break;
  253. }
  254. start++;
  255. nResult++;
  256. }
  257. while ( *start == 0x0D || *start== 0x0A )
  258. {
  259. start++;
  260. nResult++;
  261. }
  262. return nResult;
  263. }
  264. //-----------------------------------------------------------------------------
  265. // Purpose:Reads the contents of a file
  266. //-----------------------------------------------------------------------------
  267. bool CLocalize::ReadLocalizationFile( const char *pRelativePath, const char *pPathID )
  268. {
  269. FileHandle_t file = g_pFullFileSystem->Open( pRelativePath, "rb", pPathID );
  270. if ( FILESYSTEM_INVALID_HANDLE == file )
  271. return false;
  272. // this is an optimization so that the filename string doesn't have to get converted to a symbol for each key/value
  273. m_CurrentFile = pRelativePath;
  274. // read into a memory block
  275. int fileSize = g_pFullFileSystem->Size(file);
  276. int bufferSize = g_pFullFileSystem->GetOptimalReadSize( file, fileSize + sizeof(wchar_t) );
  277. ucs2 *memBlock = (ucs2 *)g_pFullFileSystem->AllocOptimalReadBuffer(file, bufferSize);
  278. bool bReadOK = ( g_pFullFileSystem->ReadEx(memBlock, bufferSize, fileSize, file) != 0 );
  279. // finished with file
  280. g_pFullFileSystem->Close(file);
  281. // null-terminate the stream
  282. memBlock[fileSize / sizeof(ucs2)] = 0x0000;
  283. // check the first character, make sure this a little-endian unicode file
  284. ucs2 *data = memBlock;
  285. ucs2 signature = LittleShort( data[0] );
  286. if ( !bReadOK || signature != 0xFEFF )
  287. {
  288. Msg( "Ignoring non-unicode close caption file %s\n", pRelativePath );
  289. g_pFullFileSystem->FreeOptimalReadBuffer( memBlock );
  290. m_CurrentFile = UTL_INVAL_SYMBOL;
  291. return false;
  292. }
  293. // ensure little-endian unicode reads correctly on all platforms
  294. CByteswap byteSwap;
  295. byteSwap.SetTargetBigEndian( false );
  296. byteSwap.SwapBufferToTargetEndian( data, data, fileSize / sizeof(ucs2) );
  297. // skip past signature
  298. data++;
  299. // parse out a token at a time
  300. enum states_e
  301. {
  302. STATE_BASE, // looking for base settings
  303. STATE_TOKENS, // reading in unicode tokens
  304. };
  305. bool bQuoted;
  306. bool bEnglishFile = false;
  307. if ( Q_stristr(pRelativePath, "_english.txt") )
  308. {
  309. bEnglishFile = true;
  310. }
  311. bool spew = false;
  312. if ( CommandLine()->FindParm( "-ccsyntax" ) )
  313. {
  314. spew = true;
  315. }
  316. BuildFastValueLookup();
  317. CExpressionEvaluator ExpressionHandler;
  318. states_e state = STATE_BASE;
  319. while (1)
  320. {
  321. // read the key and the value
  322. ucs2 keytoken[128];
  323. data = ReadUnicodeToken(data, keytoken, 128, bQuoted);
  324. if (!keytoken[0])
  325. break; // we've hit the null terminator
  326. // convert the token to a string
  327. char key[128];
  328. ConvertUCS2ToANSI(keytoken, key, sizeof(key));
  329. // if we have a C++ style comment, read to end of line and continue
  330. if (!strnicmp(key, "//", 2))
  331. {
  332. data = ReadToEndOfLine(data);
  333. continue;
  334. }
  335. if ( spew )
  336. {
  337. Msg( "%s\n", key );
  338. }
  339. ucs2 valuetoken[ MAX_LOCALIZED_CHARS ];
  340. bool bEnoughCapacity = true;
  341. if ( DistanceToEndOfLine( data ) > ( MAX_LOCALIZED_CHARS - 1 ) )
  342. {
  343. Warning( "Error: Localization key value exceeds MAX_LOCALIZED_CHARS. Problem key: %s\n", key );
  344. bEnoughCapacity = false;
  345. }
  346. data = ReadUnicodeToken(data, valuetoken, MAX_LOCALIZED_CHARS, bQuoted);
  347. if (!valuetoken[0] && !bQuoted)
  348. break; // we've hit the null terminator
  349. if (state == STATE_BASE)
  350. {
  351. if (!stricmp(key, "Language"))
  352. {
  353. // copy out our language setting
  354. char value[MAX_LOCALIZED_CHARS];
  355. ConvertUCS2ToANSI(valuetoken, value, sizeof(value));
  356. strncpy(m_szLanguage, value, sizeof(m_szLanguage) - 1);
  357. }
  358. else if (!stricmp(key, "Tokens"))
  359. {
  360. state = STATE_TOKENS;
  361. }
  362. else if (!stricmp(key, "}"))
  363. {
  364. // we've hit the end
  365. break;
  366. }
  367. }
  368. else if (state == STATE_TOKENS)
  369. {
  370. if (!stricmp(key, "}"))
  371. {
  372. // end of tokens
  373. state = STATE_BASE;
  374. }
  375. else
  376. {
  377. // skip our [english] beginnings (in non-english files)
  378. if ( (bEnglishFile) || (!bEnglishFile && strnicmp(key, "[english]", 9)))
  379. {
  380. // Check for a conditional tag
  381. bool bAccepted = true;
  382. ucs2 conditional[ MAX_LOCALIZED_CHARS ];
  383. ucs2 *tempData = ReadUnicodeToken(data, conditional, MAX_LOCALIZED_CHARS, bQuoted);
  384. char cond[MAX_LOCALIZED_CHARS];
  385. V_UCS2ToUTF8( conditional, cond, sizeof(cond) );
  386. if ( !bQuoted && (strstr( cond, "[$" )||strstr( cond, "[!$" )) )
  387. {
  388. // Evaluate the conditional tag
  389. char cond[MAX_LOCALIZED_CHARS];
  390. ConvertUCS2ToANSI( conditional, cond, sizeof( cond ) );
  391. ExpressionHandler.Evaluate( bAccepted, cond );
  392. data = tempData;
  393. }
  394. if ( bAccepted && bEnoughCapacity )
  395. {
  396. // add the string to the table
  397. AddString(key, valuetoken, NULL);
  398. }
  399. }
  400. }
  401. }
  402. }
  403. g_pFullFileSystem->FreeOptimalReadBuffer( memBlock );
  404. m_CurrentFile = UTL_INVAL_SYMBOL;
  405. DiscardFastValueLookup();
  406. return true;
  407. }
  408. //-----------------------------------------------------------------------------
  409. // Purpose: Adds the contents of a file
  410. //-----------------------------------------------------------------------------
  411. bool CLocalize::AddFile( const char *szFileName, const char *pPathID, bool bIncludeFallbackSearchPaths )
  412. {
  413. // use the correct file based on the chosen language
  414. static const char *const LANGUAGE_STRING = "%language%";
  415. static const char *const ENGLISH_STRING = "english";
  416. static const int MAX_LANGUAGE_NAME_LENGTH = 64;
  417. int offs = 0;
  418. bool success = false;
  419. char language[MAX_LANGUAGE_NAME_LENGTH];
  420. memset( language, 0, sizeof(language) );
  421. if ( Q_IsAbsolutePath( szFileName ) )
  422. {
  423. Warning( "Full paths not allowed in localization file specificaton %s\n", szFileName );
  424. return false;
  425. }
  426. const char *langptr = strstr(szFileName, LANGUAGE_STRING);
  427. if (langptr)
  428. {
  429. // LOAD THE ENGLISH FILE FIRST
  430. // always load the file to make sure we're not missing any strings
  431. // copy out the initial part of the string
  432. offs = langptr - szFileName;
  433. char fileName[MAX_PATH];
  434. strncpy(fileName, szFileName, offs);
  435. fileName[offs] = 0;
  436. if ( m_bUseOnlyLongestLanguageString )
  437. {
  438. return AddAllLanguageFiles( fileName );
  439. }
  440. // append "english" as our default language
  441. Q_strncat(fileName, ENGLISH_STRING, sizeof( fileName ), COPY_ALL_CHARACTERS );
  442. // append the end of the initial string
  443. offs += strlen(LANGUAGE_STRING);
  444. Q_strncat(fileName, szFileName + offs, sizeof( fileName ), COPY_ALL_CHARACTERS);
  445. success = AddFile( fileName, pPathID, bIncludeFallbackSearchPaths );
  446. bool bValid = true;
  447. if ( IsPC() )
  448. {
  449. if ( CommandLine()->CheckParm( "-language" ) )
  450. {
  451. Q_strncpy( language, CommandLine()->ParmValue( "-language", "english" ), sizeof( language ) );
  452. bValid = true;
  453. }
  454. else
  455. {
  456. bValid = vgui::system()->GetRegistryString( "HKEY_CURRENT_USER\\Software\\Valve\\Steam\\Language", language, sizeof(language)-1 );
  457. }
  458. if ( bValid && !Q_stricmp( language, "unknown" ) )
  459. {
  460. // Fall back to english
  461. bValid = false;
  462. }
  463. }
  464. else
  465. {
  466. #ifdef _GAMECONSOLE
  467. Q_strncpy( language, XBX_GetLanguageString(), sizeof( language ) );
  468. #endif
  469. }
  470. // LOAD THE LOCALIZED FILE IF IT'S NOT ENGLISH
  471. // append the language
  472. if ( bValid )
  473. {
  474. if ( strlen(language) != 0 && stricmp(language, ENGLISH_STRING) != 0 )
  475. {
  476. // copy out the initial part of the string
  477. offs = langptr - szFileName;
  478. strncpy(fileName, szFileName, offs);
  479. fileName[offs] = 0;
  480. Q_strncat(fileName, language, sizeof( fileName ), COPY_ALL_CHARACTERS);
  481. // append the end of the initial string
  482. offs += strlen(LANGUAGE_STRING);
  483. Q_strncat(fileName, szFileName + offs, sizeof( fileName ), COPY_ALL_CHARACTERS );
  484. success &= AddFile( fileName, pPathID, bIncludeFallbackSearchPaths );
  485. }
  486. }
  487. return success;
  488. }
  489. // store the localization file name if it doesn't already exist
  490. LocalizationFileInfo_t search;
  491. search.symName = szFileName;
  492. search.symPathID = pPathID ? pPathID : "";
  493. search.bIncludeFallbacks = false;
  494. int lfc = m_LocalizationFiles.Count();
  495. for ( int lf = 0; lf < lfc; ++lf )
  496. {
  497. LocalizationFileInfo_t& entry = m_LocalizationFiles[ lf ];
  498. if ( !Q_stricmp( entry.symName.String(), szFileName ) )
  499. {
  500. m_LocalizationFiles.Remove( lf );
  501. break;
  502. }
  503. }
  504. m_LocalizationFiles.AddToTail( search );
  505. bool bOk = ReadLocalizationFile( szFileName, pPathID );
  506. if ( !bOk )
  507. {
  508. DevWarning( "ILocalize::AddFile() failed to load file \"%s\".\n", szFileName );
  509. }
  510. return bOk;
  511. }
  512. //-----------------------------------------------------------------------------
  513. // Purpose: Load all the localized language strings, and uses the longest string from each language
  514. //-----------------------------------------------------------------------------
  515. bool CLocalize::AddAllLanguageFiles( const char *baseFileName )
  516. {
  517. bool bSuccess = true;
  518. // Each new language load could potentially change the string value
  519. // This will suppress callbacks until we're done.
  520. m_bSuppressChangeCallbacks = true;
  521. if ( IsX360() )
  522. {
  523. #ifdef _X360
  524. // xbox cannot support FindFirst/FindNext due to zips
  525. const char *pLanguageString = NULL;
  526. while ( 1 )
  527. {
  528. pLanguageString = XBX_GetNextSupportedLanguage( pLanguageString, NULL );
  529. if ( !pLanguageString )
  530. {
  531. // end of list
  532. break;
  533. }
  534. // re-add in the search path
  535. char szFile[MAX_PATH];
  536. V_snprintf( szFile, sizeof( szFile ), "%s%s.txt", baseFileName, pLanguageString );
  537. // add the file
  538. bSuccess &= AddFile( szFile, NULL, true );
  539. }
  540. #endif
  541. }
  542. else
  543. {
  544. // work out the path the files are in
  545. char szFilePath[MAX_PATH];
  546. Q_strncpy( szFilePath, baseFileName, sizeof(szFilePath) );
  547. char *pLastSlash = strrchr( szFilePath, '\\' );
  548. if ( !pLastSlash )
  549. {
  550. pLastSlash = strrchr( szFilePath, '/' );
  551. }
  552. if ( pLastSlash )
  553. {
  554. pLastSlash[1] = 0;
  555. }
  556. else
  557. {
  558. szFilePath[0] = 0;
  559. }
  560. // iterate through and add all the languages (for development)
  561. // the longest string out of all the languages will be used
  562. char szSearchPath[MAX_PATH];
  563. Q_snprintf( szSearchPath, sizeof(szSearchPath), "%s*.txt", baseFileName );
  564. FileFindHandle_t hFind = NULL;
  565. const char *file = g_pFullFileSystem->FindFirst( szSearchPath, &hFind );
  566. while ( file )
  567. {
  568. // re-add in the search path
  569. char szFile[MAX_PATH];
  570. V_snprintf( szFile, sizeof(szFile), "%s%s", szFilePath, file );
  571. // add the file
  572. bSuccess &= AddFile( szFile, NULL, true );
  573. // next file
  574. file = g_pFullFileSystem->FindNext( hFind );
  575. }
  576. g_pFullFileSystem->FindClose( hFind );
  577. }
  578. m_bSuppressChangeCallbacks = false;
  579. if ( m_bQueuedChangeCallback )
  580. {
  581. m_bQueuedChangeCallback = false;
  582. InvokeChangeCallbacks();
  583. }
  584. return bSuccess;
  585. }
  586. //-----------------------------------------------------------------------------
  587. // Purpose: saves the entire contents of the token tree to the file
  588. //-----------------------------------------------------------------------------
  589. bool CLocalize::SaveToFile( const char *szFileName )
  590. {
  591. // parse out the file
  592. FileHandle_t file = g_pFullFileSystem->Open(szFileName, "wb");
  593. if (!file)
  594. return false;
  595. // only save the symbols relevant to this file
  596. CUtlSymbol fileName = szFileName;
  597. // write litte-endian unicode marker
  598. unsigned short marker = 0xFEFF;
  599. marker = LittleShort( marker );
  600. g_pFullFileSystem->Write(&marker, sizeof( marker ), file);
  601. const char *startStr = "\"lang\"\r\n{\r\n\"Language\" \"English\"\r\n\"Tokens\"\r\n{\r\n";
  602. const char *endStr = "}\r\n}\r\n";
  603. // write out the first string
  604. static ucs2 unicodeString[1024];
  605. int strLength = ConvertANSIToUCS2(startStr, unicodeString, sizeof(unicodeString));
  606. if (!strLength)
  607. return false;
  608. g_pFullFileSystem->Write(unicodeString, strlen(startStr) * sizeof(ucs2), file);
  609. // convert our spacing characters to unicode
  610. // wchar_t unicodeSpace = L' ';
  611. ucs2 unicodeQuote = L'\"';
  612. ucs2 unicodeCR = L'\r';
  613. ucs2 unicodeNewline = L'\n';
  614. ucs2 unicodeTab = L'\t';
  615. // write out all the key/value pairs
  616. for (LocalizeStringIndex_t idx = GetFirstStringIndex(); idx != LOCALIZE_INVALID_STRING_INDEX; idx = GetNextStringIndex(idx))
  617. {
  618. // only write strings that belong in this file
  619. if (fileName != m_Lookup[idx].filename)
  620. continue;
  621. const char *name = GetNameByIndex(idx);
  622. wchar_t *value = GetValueByIndex(idx);
  623. // convert the name to a unicode string
  624. ConvertANSIToUCS2(name, unicodeString, sizeof(unicodeString));
  625. g_pFullFileSystem->Write(&unicodeTab, sizeof(ucs2), file);
  626. // write out
  627. g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
  628. g_pFullFileSystem->Write(unicodeString, strlen(name) * sizeof(ucs2), file);
  629. g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
  630. g_pFullFileSystem->Write(&unicodeTab, sizeof(ucs2), file);
  631. g_pFullFileSystem->Write(&unicodeTab, sizeof(ucs2), file);
  632. g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
  633. #ifdef POSIX
  634. ucs2 ucs2Value[MAX_LOCALIZED_CHARS];
  635. V_UnicodeToUCS2( value, wcslen(value)*sizeof(wchar_t), (char *)ucs2Value, sizeof(ucs2Value) );
  636. g_pFullFileSystem->Write(ucs2Value, wcslen(value) * sizeof(ucs2), file);
  637. #else
  638. g_pFullFileSystem->Write(value, wcslen(value) * sizeof(ucs2), file);
  639. #endif
  640. g_pFullFileSystem->Write(&unicodeQuote, sizeof(ucs2), file);
  641. g_pFullFileSystem->Write(&unicodeCR, sizeof(ucs2), file);
  642. g_pFullFileSystem->Write(&unicodeNewline, sizeof(ucs2), file);
  643. }
  644. // write end string
  645. strLength = ConvertANSIToUCS2(endStr, unicodeString, sizeof(unicodeString));
  646. g_pFullFileSystem->Write(unicodeString, strLength * sizeof(ucs2), file);
  647. g_pFullFileSystem->Close(file);
  648. return true;
  649. }
  650. //-----------------------------------------------------------------------------
  651. // Purpose: for development, reloads localization files
  652. //-----------------------------------------------------------------------------
  653. void CLocalize::ReloadLocalizationFiles( )
  654. {
  655. // re-add all the localization files
  656. for (int i = 0; i < m_LocalizationFiles.Count(); i++)
  657. {
  658. LocalizationFileInfo_t& entry = m_LocalizationFiles[ i ];
  659. AddFile
  660. (
  661. entry.symName.String(),
  662. entry.symPathID.String()[0] ? entry.symPathID.String() : NULL,
  663. entry.bIncludeFallbacks
  664. );
  665. }
  666. }
  667. //-----------------------------------------------------------------------------
  668. // Purpose: Used to sort strings
  669. //-----------------------------------------------------------------------------
  670. bool CLocalize::SymLess(localizedstring_t const &i1, localizedstring_t const &i2)
  671. {
  672. const char *str1 = (i1.nameIndex == LOCALIZE_INVALID_STRING_INDEX) ? i1.pszValueString :
  673. &s_Localize.m_Names[i1.nameIndex];
  674. const char *str2 = (i2.nameIndex == LOCALIZE_INVALID_STRING_INDEX) ? i2.pszValueString :
  675. &s_Localize.m_Names[i2.nameIndex];
  676. return stricmp(str1, str2) < 0;
  677. }
  678. //-----------------------------------------------------------------------------
  679. // Purpose: Finds a string in the table
  680. //-----------------------------------------------------------------------------
  681. wchar_t *CLocalize::Find(const char *pName)
  682. {
  683. LocalizeStringIndex_t idx = FindIndex(pName);
  684. if (idx == LOCALIZE_INVALID_STRING_INDEX)
  685. return NULL;
  686. return &m_Values[m_Lookup[idx].valueIndex];
  687. }
  688. // Like Find(), but as a failsafe, returns an error message instead of NULL if the string isn't found.
  689. const wchar_t *CLocalize::FindSafe(const char *pName)
  690. {
  691. #ifdef _CERT
  692. const wchar_t *failsafe = L"";
  693. #else
  694. const wchar_t *failsafe = L"#FIXME_LOCALIZATION_FAIL_MISSING_STRING";
  695. #endif
  696. const wchar_t *locstr = Find( pName );
  697. if ( !locstr )
  698. {
  699. DevMsg( "CLocalize::FindSafe failed to localize: %s\n", pName );
  700. return failsafe;
  701. }
  702. else
  703. {
  704. return locstr;
  705. }
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose: finds the index of a token by token name
  709. //-----------------------------------------------------------------------------
  710. LocalizeStringIndex_t CLocalize::FindIndex(const char *pName)
  711. {
  712. if (!pName)
  713. return LOCALIZE_INVALID_STRING_INDEX;
  714. // strip the pound character (which is used elsewhere to indicate that it's a string that should be translated)
  715. if (pName[0] == '#')
  716. {
  717. pName++;
  718. }
  719. // Passing this special invalid symbol makes the comparison function
  720. // use the string passed in the context
  721. localizedstring_t invalidItem;
  722. invalidItem.nameIndex = LOCALIZE_INVALID_STRING_INDEX;
  723. invalidItem.pszValueString = pName;
  724. return m_Lookup.Find( invalidItem );
  725. }
  726. #if defined( POSIX ) && !defined( _PS3 )
  727. void CLocalize::AddString(const char *pString, ucs2 *pUCS2Value, const char *fileName)
  728. {
  729. if (!pString || !pUCS2Value )
  730. return;
  731. wchar_t pValue[2048];
  732. V_UCS2ToUnicode( pUCS2Value, pValue, sizeof(pValue) );
  733. AddString( pString, pValue, fileName );
  734. }
  735. #endif
  736. //-----------------------------------------------------------------------------
  737. // Finds and/or creates a symbol based on the string
  738. //-----------------------------------------------------------------------------
  739. void CLocalize::AddString(const char *pString, wchar_t *pValue, const char *fileName)
  740. {
  741. if (!pString)
  742. return;
  743. MEM_ALLOC_CREDIT();
  744. // see if the value is already in our string table
  745. int valueIndex = FindExistingValueIndex( pValue );
  746. if ( valueIndex == LOCALIZE_INVALID_STRING_INDEX )
  747. {
  748. int len = wcslen( pValue ) + 1;
  749. valueIndex = m_Values.AddMultipleToTail( len );
  750. memcpy( &m_Values[valueIndex], pValue, len * sizeof(wchar_t) );
  751. }
  752. // see if the key is already in the table
  753. LocalizeStringIndex_t stridx = FindIndex( pString );
  754. localizedstring_t item;
  755. item.nameIndex = stridx;
  756. if ( stridx == LOCALIZE_INVALID_STRING_INDEX )
  757. {
  758. // didn't find, insert the string into the vector.
  759. int len = strlen(pString) + 1;
  760. stridx = m_Names.AddMultipleToTail( len );
  761. memcpy( &m_Names[stridx], pString, len * sizeof(char) );
  762. item.nameIndex = stridx;
  763. item.valueIndex = valueIndex;
  764. item.filename = fileName ? fileName : m_CurrentFile;
  765. m_Lookup.Insert( item );
  766. }
  767. else
  768. {
  769. // it's already in the table
  770. if ( m_bUseOnlyLongestLanguageString )
  771. {
  772. // check which string is longer
  773. wchar_t *newValue = pValue;
  774. wchar_t *oldValue = GetValueByIndex( stridx );
  775. // get the width of the string, using just the first font
  776. if ( m_pQuery )
  777. {
  778. int newWide = m_pQuery->ComputeTextWidth( newValue );
  779. int oldWide = m_pQuery->ComputeTextWidth( oldValue );
  780. // if the new one is shorter, don't let it be added
  781. if (newWide < oldWide)
  782. return;
  783. }
  784. }
  785. // replace the current item
  786. item.nameIndex = GetNameByIndex( stridx ) - &m_Names[ 0 ];
  787. item.valueIndex = valueIndex;
  788. item.filename = fileName ? fileName : m_CurrentFile;
  789. m_Lookup[ stridx ] = item;
  790. InvokeChangeCallbacks();
  791. }
  792. }
  793. //-----------------------------------------------------------------------------
  794. // Remove all symbols in the table.
  795. //-----------------------------------------------------------------------------
  796. void CLocalize::RemoveAll()
  797. {
  798. m_Lookup.RemoveAll();
  799. m_Names.RemoveAll();
  800. m_Values.RemoveAll();
  801. m_LocalizationFiles.RemoveAll();
  802. }
  803. //-----------------------------------------------------------------------------
  804. // Purpose: iteration functions
  805. //-----------------------------------------------------------------------------
  806. LocalizeStringIndex_t CLocalize::GetFirstStringIndex()
  807. {
  808. return m_Lookup.FirstInorder();
  809. }
  810. //-----------------------------------------------------------------------------
  811. // Purpose: returns the next index, or INVALID_STRING_INDEX if no more strings available
  812. //-----------------------------------------------------------------------------
  813. LocalizeStringIndex_t CLocalize::GetNextStringIndex(LocalizeStringIndex_t index)
  814. {
  815. LocalizeStringIndex_t idx = m_Lookup.NextInorder(index);
  816. if (idx == m_Lookup.InvalidIndex())
  817. return LOCALIZE_INVALID_STRING_INDEX;
  818. return idx;
  819. }
  820. //-----------------------------------------------------------------------------
  821. // Purpose: gets the name of the localization string by index
  822. //-----------------------------------------------------------------------------
  823. const char *CLocalize::GetNameByIndex(LocalizeStringIndex_t index)
  824. {
  825. localizedstring_t &lstr = m_Lookup[index];
  826. return &m_Names[lstr.nameIndex];
  827. }
  828. //-----------------------------------------------------------------------------
  829. // Purpose: gets the localized string value by index
  830. //-----------------------------------------------------------------------------
  831. wchar_t *CLocalize::GetValueByIndex(LocalizeStringIndex_t index)
  832. {
  833. if (index == LOCALIZE_INVALID_STRING_INDEX)
  834. return NULL;
  835. localizedstring_t &lstr = m_Lookup[index];
  836. return &m_Values[lstr.valueIndex];
  837. }
  838. CLocalize *CLocalize::s_pTable = NULL;
  839. bool CLocalize::FastValueLessFunc( const fastvalue_t& lhs, const fastvalue_t& rhs )
  840. {
  841. Assert( s_pTable );
  842. const wchar_t *w1 = lhs.search ? lhs.search : &s_pTable->m_Values[ lhs.valueindex ];
  843. const wchar_t *w2 = rhs.search ? rhs.search : &s_pTable->m_Values[ rhs.valueindex ];
  844. return ( wcscmp( w1, w2 ) < 0 ) ? true : false;
  845. }
  846. void CLocalize::BuildFastValueLookup()
  847. {
  848. m_FastValueLookup.RemoveAll();
  849. s_pTable = this;
  850. // Build it
  851. int c = m_Lookup.Count();
  852. for ( int i = 0; i < c; ++i )
  853. {
  854. fastvalue_t val;
  855. val.valueindex = m_Lookup[ i ].valueIndex;
  856. val.search = NULL;
  857. m_FastValueLookup.Insert( val );
  858. }
  859. }
  860. void CLocalize::DiscardFastValueLookup()
  861. {
  862. m_FastValueLookup.RemoveAll();
  863. s_pTable = NULL;
  864. }
  865. //-----------------------------------------------------------------------------
  866. // Purpose:
  867. //-----------------------------------------------------------------------------
  868. int CLocalize::FindExistingValueIndex( const wchar_t *value )
  869. {
  870. if ( !s_pTable )
  871. return (int)LOCALIZE_INVALID_STRING_INDEX;
  872. fastvalue_t val;
  873. val.valueindex = -1;
  874. val.search = value;
  875. int idx = m_FastValueLookup.Find( val );
  876. if ( idx != m_FastValueLookup.InvalidIndex() )
  877. {
  878. return m_FastValueLookup[ idx ].valueindex;
  879. }
  880. return (int)LOCALIZE_INVALID_STRING_INDEX;
  881. }
  882. //-----------------------------------------------------------------------------
  883. // Purpose: returns which file a string was loaded from
  884. //-----------------------------------------------------------------------------
  885. const char *CLocalize::GetFileNameByIndex(LocalizeStringIndex_t index)
  886. {
  887. localizedstring_t &lstr = m_Lookup[index];
  888. return lstr.filename.String();
  889. }
  890. //-----------------------------------------------------------------------------
  891. // Purpose: sets the value in the index
  892. //-----------------------------------------------------------------------------
  893. void CLocalize::SetValueByIndex(LocalizeStringIndex_t index, wchar_t *newValue)
  894. {
  895. // get the existing string
  896. localizedstring_t &lstr = m_Lookup[index];
  897. wchar_t *wstr = &m_Values[lstr.valueIndex];
  898. // see if the new string will fit within the old memory
  899. int newLen = wcslen(newValue);
  900. int oldLen = wcslen(wstr);
  901. if (newLen > oldLen)
  902. {
  903. // it won't fit, so allocate new memory - this is wasteful, but only happens in edit mode
  904. lstr.valueIndex = m_Values.AddMultipleToTail(newLen + 1);
  905. memcpy(&m_Values[lstr.valueIndex], newValue, (newLen + 1) * sizeof(wchar_t));
  906. }
  907. else
  908. {
  909. // copy the string into the old position
  910. wcscpy(wstr, newValue);
  911. }
  912. InvokeChangeCallbacks();
  913. }
  914. //-----------------------------------------------------------------------------
  915. // Purpose: returns number of localization files currently loaded
  916. //-----------------------------------------------------------------------------
  917. int CLocalize::GetLocalizationFileCount()
  918. {
  919. return m_LocalizationFiles.Count();
  920. }
  921. //-----------------------------------------------------------------------------
  922. // Purpose: returns localization filename by index
  923. //-----------------------------------------------------------------------------
  924. const char *CLocalize::GetLocalizationFileName(int index)
  925. {
  926. return m_LocalizationFiles[index].symName.String();
  927. }
  928. //-----------------------------------------------------------------------------
  929. // Purpose: returns whether a localization file has been loaded already
  930. //-----------------------------------------------------------------------------
  931. bool CLocalize::LocalizationFileIsLoaded(const char *name)
  932. {
  933. int c = m_LocalizationFiles.Count();
  934. for ( int i = 0; i < c; ++i )
  935. {
  936. if ( !Q_stricmp( m_LocalizationFiles[ i ].symName.String(), name ) )
  937. return true;
  938. }
  939. return false;
  940. }
  941. //-----------------------------------------------------------------------------
  942. // Purpose: converts an english string to unicode
  943. //-----------------------------------------------------------------------------
  944. int CLocalize::ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSizeInBytes)
  945. {
  946. #ifdef POSIX
  947. return V_UTF8ToUnicode(ansi, unicode, unicodeBufferSizeInBytes);
  948. #else
  949. int chars = ::MultiByteToWideChar(CP_UTF8, 0, ansi, -1, unicode, unicodeBufferSizeInBytes / sizeof(wchar_t));
  950. unicode[(unicodeBufferSizeInBytes / sizeof(wchar_t)) - 1] = 0;
  951. return chars;
  952. #endif
  953. }
  954. //-----------------------------------------------------------------------------
  955. // Purpose: converts an unicode string to an english string
  956. //-----------------------------------------------------------------------------
  957. int CLocalize::ConvertUnicodeToANSI(const wchar_t *unicode, char *ansi, int ansiBufferSize)
  958. {
  959. #ifdef POSIX
  960. return V_UnicodeToUTF8(unicode, ansi, ansiBufferSize);
  961. #else
  962. int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
  963. ansi[ansiBufferSize - 1] = 0;
  964. return result;
  965. #endif
  966. }
  967. //-----------------------------------------------------------------------------
  968. // Purpose: converts an english string to unicode
  969. //-----------------------------------------------------------------------------
  970. int CLocalize::ConvertANSIToUCS2(const char *ansi, OUT_Z_BYTECAP(unicodeBufferSizeInBytes) ucs2 *unicode, int unicodeBufferSizeInBytes)
  971. {
  972. #ifdef POSIX
  973. return V_UTF8ToUCS2(ansi, strlen(ansi)*sizeof(char), unicode, unicodeBufferSizeInBytes);
  974. #else
  975. int chars = ::MultiByteToWideChar(CP_UTF8, 0, ansi, -1, unicode, unicodeBufferSizeInBytes / sizeof(wchar_t));
  976. unicode[(unicodeBufferSizeInBytes / sizeof(wchar_t)) - 1] = 0;
  977. return chars;
  978. #endif
  979. }
  980. //-----------------------------------------------------------------------------
  981. // Purpose: converts an unicode string to an english string
  982. //-----------------------------------------------------------------------------
  983. int CLocalize::ConvertUCS2ToANSI(const ucs2 *unicode, OUT_Z_BYTECAP(ansiBufferSize) char *ansi, int ansiBufferSize)
  984. {
  985. #ifdef POSIX
  986. return V_UCS2ToUTF8(unicode, ansi, ansiBufferSize);
  987. #else
  988. int result = ::WideCharToMultiByte(CP_UTF8, 0, unicode, -1, ansi, ansiBufferSize, NULL, NULL);
  989. ansi[ansiBufferSize - 1] = 0;
  990. return result;
  991. #endif
  992. }
  993. //-----------------------------------------------------------------------------
  994. // Purpose: Constructs a string, inserting variables where necessary
  995. //-----------------------------------------------------------------------------
  996. void CLocalize::ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const char *tokenName, KeyValues *localizationVariables)
  997. {
  998. LocalizeStringIndex_t index = FindIndex(tokenName);
  999. if (index != LOCALIZE_INVALID_STRING_INDEX)
  1000. {
  1001. ConstructString(unicodeOutput, unicodeBufferSizeInBytes, index, localizationVariables);
  1002. }
  1003. else
  1004. {
  1005. // string not found, just return the token name
  1006. ConvertANSIToUnicode(tokenName, unicodeOutput, unicodeBufferSizeInBytes);
  1007. }
  1008. }
  1009. //-----------------------------------------------------------------------------
  1010. // Purpose: Constructs a string, inserting variables where necessary
  1011. //-----------------------------------------------------------------------------
  1012. void CLocalize::ConstructString(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, LocalizeStringIndex_t unlocalizedTextSymbol, KeyValues *localizationVariables)
  1013. {
  1014. if (unicodeBufferSizeInBytes < 1)
  1015. return;
  1016. unicodeOutput[0] = 0;
  1017. const wchar_t *searchPos = GetValueByIndex(unlocalizedTextSymbol);
  1018. if (!searchPos)
  1019. {
  1020. wcsncpy(unicodeOutput, L"[unknown string]", unicodeBufferSizeInBytes / sizeof(wchar_t));
  1021. return;
  1022. }
  1023. wchar_t *outputPos = unicodeOutput;
  1024. //assumes we can't have %s10
  1025. //assume both are 0 terminated?
  1026. int unicodeBufferSize = unicodeBufferSizeInBytes / sizeof(wchar_t);
  1027. while ( *searchPos != '\0' && unicodeBufferSize > 0 )
  1028. {
  1029. bool shouldAdvance = true;
  1030. if ( *searchPos == '%' )
  1031. {
  1032. // this is an escape sequence that specifies a variable name
  1033. if ( searchPos[1] == 's' && searchPos[2] >= '0' && searchPos[2] <= '9' )
  1034. {
  1035. shouldAdvance = false;
  1036. char variableName[3];
  1037. variableName[0] = searchPos[1];
  1038. variableName[1] = searchPos[2];
  1039. variableName[2] = 0;
  1040. // Handle this as a valid, fixed substitution string
  1041. // look up the variable name
  1042. const wchar_t *value = localizationVariables->GetWString( variableName, L"[unknown]" );
  1043. int paramSize = wcslen(value);
  1044. if (paramSize >= unicodeBufferSize)
  1045. {
  1046. paramSize = MAX( 0, unicodeBufferSize - 1 );
  1047. }
  1048. wcsncpy(outputPos, value, paramSize);
  1049. unicodeBufferSize -= paramSize;
  1050. outputPos += paramSize;
  1051. searchPos += 3;
  1052. }
  1053. else if ( searchPos[1] == '%' )
  1054. {
  1055. // just a '%' char, just write the second one
  1056. searchPos++;
  1057. }
  1058. else if ( localizationVariables )
  1059. {
  1060. // get out the variable name
  1061. const wchar_t *varStart = searchPos + 1;
  1062. // first letter of a valid variable MUST be alphanumeric, otherwise this isn't a variable
  1063. if ( iswalnum(*varStart) )
  1064. {
  1065. const wchar_t *varEnd = wcschr( varStart, '%' );
  1066. if ( varEnd && *varEnd == '%' )
  1067. {
  1068. shouldAdvance = false;
  1069. // assume variable names must be ascii, do a quick convert
  1070. char variableName[32];
  1071. char *vset = variableName;
  1072. for ( const wchar_t *pws = varStart; pws < varEnd && (vset < variableName + sizeof(variableName) - 1); ++pws, ++vset )
  1073. {
  1074. *vset = (char)*pws;
  1075. }
  1076. *vset = 0;
  1077. // look up the variable name
  1078. const wchar_t *value = localizationVariables->GetWString( variableName, L"[unknown]" );
  1079. int paramSize = wcslen(value);
  1080. if (paramSize >= unicodeBufferSize)
  1081. {
  1082. paramSize = MAX( 0, unicodeBufferSize - 1 );
  1083. }
  1084. wcsncpy(outputPos, value, paramSize);
  1085. unicodeBufferSize -= paramSize;
  1086. outputPos += paramSize;
  1087. searchPos = varEnd + 1;
  1088. }
  1089. }
  1090. }
  1091. }
  1092. if (shouldAdvance)
  1093. {
  1094. //copy it over, char by char
  1095. *outputPos = *searchPos;
  1096. outputPos++;
  1097. unicodeBufferSize--;
  1098. searchPos++;
  1099. }
  1100. }
  1101. // ensure null termination
  1102. *outputPos = '\0';
  1103. }
  1104. wchar_t* CLocalize::GetAsianFrequencySequence( const char * pLanguage )
  1105. {
  1106. if( !m_bAsianFrequencySequenceLoaded )
  1107. {
  1108. m_bAsianFrequencySequenceLoaded = true;
  1109. char szFileName[128];
  1110. V_snprintf( szFileName, sizeof( szFileName ), "resource/%s_frequency.txt", pLanguage );
  1111. g_pFullFileSystem->ReadFile( szFileName, "GAME", m_bufAsianFrequencySequence );
  1112. uint nSize = m_bufAsianFrequencySequence.TellPut() / sizeof( wchar_t );
  1113. m_bufAsianFrequencySequence.PutUnsignedShort( 0 ); // 0-terminate
  1114. wchar_t * pAsianFrequencySequence = (wchar_t*) m_bufAsianFrequencySequence.Base();
  1115. // transcode from LT Unicode to GT Unicode
  1116. if( pAsianFrequencySequence[0] == 0xFFFE )
  1117. {
  1118. // switch from little-endian
  1119. for( uint i = 0; i < nSize; ++i )
  1120. {
  1121. wchar_t &refChar = pAsianFrequencySequence[i];
  1122. refChar = ( refChar >> 8 ) | ( refChar << 8 );
  1123. }
  1124. }
  1125. }
  1126. if( m_bufAsianFrequencySequence.TellPut() > 2 )
  1127. {
  1128. wchar_t * pAsianFrequencySequence = (wchar_t*) m_bufAsianFrequencySequence.Base();
  1129. if( pAsianFrequencySequence[0] == 0xFEFF )
  1130. {
  1131. return pAsianFrequencySequence + 1;
  1132. }
  1133. return pAsianFrequencySequence;
  1134. }
  1135. return NULL;
  1136. }
  1137. #if defined( GNUC ) || defined( _WIN64 )
  1138. #define _INTSIZEOF(n) ((sizeof(n) + sizeof(intp) - 1) & ~(sizeof(intp) - 1))
  1139. #endif
  1140. #define va_argByIndex(ap,t,i) ( *(t *)(ap + i * _INTSIZEOF(t)) )
  1141. //-----------------------------------------------------------------------------
  1142. // Purpose: construct string helper
  1143. //-----------------------------------------------------------------------------
  1144. template < typename T >
  1145. void ConstructStringVArgsInternal_Impl(T *unicodeOutput, int unicodeBufferSizeInBytes, const T *formatString, int numFormatParameters, va_list argList)
  1146. {
  1147. // Safety check
  1148. if ( unicodeOutput == NULL || unicodeBufferSizeInBytes < 1 )
  1149. {
  1150. return;
  1151. }
  1152. if (!formatString)
  1153. {
  1154. unicodeOutput[0] = 0;
  1155. return;
  1156. }
  1157. int unicodeBufferSize = unicodeBufferSizeInBytes / sizeof(T);
  1158. const T *searchPos = formatString;
  1159. T *outputPos = unicodeOutput;
  1160. //assumes we can't have %s10
  1161. //assume both are 0 terminated?
  1162. int formatLength = StringFuncs<T>::Length( formatString );
  1163. #ifdef PLATFORM_64BITS
  1164. // On 64 bits, va_list does not just point to a contiguous blob of parameters
  1165. // so extract into an array here.
  1166. // TODO: this code is probably fast enough and efficient enough to use
  1167. // on all platforms, so consider enabling it everywhere.
  1168. T** arguments = (T**)stackalloc( sizeof(T*)*numFormatParameters );
  1169. if ( IsPC() )
  1170. {
  1171. for ( int i = 0; i < numFormatParameters; ++i )
  1172. {
  1173. arguments[i] = va_arg( argList, T* );
  1174. }
  1175. }
  1176. #endif
  1177. #ifdef _DEBUG
  1178. int curArgIdx = 0;
  1179. #endif
  1180. while ( searchPos[0] != '\0' && unicodeBufferSize > 1 )
  1181. {
  1182. if ( formatLength >= 3 && searchPos[0] == '%' && searchPos[1] == 's' )
  1183. {
  1184. //this is an escape sequence - %s1, %s2 etc, up to %s9
  1185. int argindex = ( searchPos[2] ) - '0' - 1;
  1186. if ( argindex < 0 || argindex > 9 )
  1187. {
  1188. Warning( "Bad format string in CLocalizeStringTable::ConstructString\n" );
  1189. *outputPos = '\0';
  1190. return;
  1191. }
  1192. if ( argindex < numFormatParameters )
  1193. {
  1194. T *param = NULL;
  1195. if ( IsPC() )
  1196. {
  1197. #if !defined( _PS3 )
  1198. #ifdef PLATFORM_64BITS
  1199. param = arguments[ argindex ];
  1200. #else
  1201. param = va_argByIndex( argList, T *, argindex );
  1202. #endif
  1203. #endif // !_PS3
  1204. }
  1205. else
  1206. {
  1207. // X360TBD: convert string to new %var% format if this assert hits
  1208. Assert( argindex == curArgIdx++ );
  1209. param = va_arg( argList, T* );
  1210. }
  1211. if (!param)
  1212. {
  1213. Assert( !("ConstructStringVArgsInternal_Impl() - Found a %s# escape sequence whose index was more than the number of args.") );
  1214. *outputPos = '\0';
  1215. return;
  1216. }
  1217. int paramSize = StringFuncs<T>::Length(param);
  1218. if (paramSize >= unicodeBufferSize)
  1219. {
  1220. paramSize = unicodeBufferSize - 1;
  1221. }
  1222. memcpy(outputPos, param, paramSize * sizeof(T));
  1223. unicodeBufferSize -= paramSize;
  1224. outputPos += paramSize;
  1225. searchPos += 3;
  1226. formatLength -= 3;
  1227. }
  1228. else
  1229. {
  1230. //copy it over, char by char
  1231. *outputPos = *searchPos;
  1232. outputPos++;
  1233. unicodeBufferSize--;
  1234. searchPos++;
  1235. formatLength--;
  1236. }
  1237. }
  1238. else
  1239. {
  1240. //copy it over, char by char
  1241. *outputPos = *searchPos;
  1242. outputPos++;
  1243. unicodeBufferSize--;
  1244. searchPos++;
  1245. formatLength--;
  1246. }
  1247. }
  1248. // ensure null termination
  1249. Assert( outputPos - unicodeOutput < unicodeBufferSizeInBytes/sizeof(T) );
  1250. *outputPos = L'\0';
  1251. }
  1252. void CLocalize::ConstructStringVArgsInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, int numFormatParameters, va_list argList)
  1253. {
  1254. ConstructStringVArgsInternal_Impl<char>( unicodeOutput, unicodeBufferSizeInBytes, formatString, numFormatParameters, argList );
  1255. }
  1256. void CLocalize::ConstructStringVArgsInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, int numFormatParameters, va_list argList)
  1257. {
  1258. ConstructStringVArgsInternal_Impl<wchar_t>( unicodeOutput, unicodeBufferSizeInBytes, formatString, numFormatParameters, argList );
  1259. }
  1260. //-----------------------------------------------------------------------------
  1261. // Purpose: construct string helper
  1262. //-----------------------------------------------------------------------------
  1263. template < typename T >
  1264. const T *GetTypedKeyValuesString( KeyValues *pKeyValues, const char *pKeyName );
  1265. template < >
  1266. const char *GetTypedKeyValuesString<char>( KeyValues *pKeyValues, const char *pKeyName )
  1267. {
  1268. return pKeyValues->GetString( pKeyName, "[unknown]" );
  1269. }
  1270. template < >
  1271. const wchar_t *GetTypedKeyValuesString<wchar_t>( KeyValues *pKeyValues, const char *pKeyName )
  1272. {
  1273. return pKeyValues->GetWString( pKeyName, L"[unknown]" );
  1274. }
  1275. template < typename T >
  1276. void ConstructStringKeyValuesInternal_Impl( T *unicodeOutput, int unicodeBufferSizeInBytes, const T *formatString, KeyValues *localizationVariables )
  1277. {
  1278. T *outputPos = unicodeOutput;
  1279. //assumes we can't have %s10
  1280. //assume both are 0 terminated?
  1281. int unicodeBufferSize = unicodeBufferSizeInBytes / sizeof(T);
  1282. while ( *formatString != '\0' && unicodeBufferSize > 0 )
  1283. {
  1284. bool shouldAdvance = true;
  1285. if ( *formatString == '%' )
  1286. {
  1287. // this is an escape sequence that specifies a variable name
  1288. if ( formatString[1] == 's' && formatString[2] >= '0' && formatString[2] <= '9' )
  1289. {
  1290. // old style escape sequence, ignore
  1291. }
  1292. else if ( formatString[1] == '%' )
  1293. {
  1294. // just a '%' char, just write the second one
  1295. formatString++;
  1296. }
  1297. else if ( localizationVariables )
  1298. {
  1299. // get out the variable name
  1300. const T *varStart = formatString + 1;
  1301. const T *varEnd = StringFuncs<T>::FindChar( varStart, '%' );
  1302. if ( varEnd && *varEnd == '%' )
  1303. {
  1304. shouldAdvance = false;
  1305. // assume variable names must be ascii, do a quick convert
  1306. char variableName[32];
  1307. char *vset = variableName;
  1308. for ( const T *pws = varStart; pws < varEnd && (vset < variableName + sizeof(variableName) - 1); ++pws, ++vset )
  1309. {
  1310. *vset = (char)*pws;
  1311. }
  1312. *vset = 0;
  1313. // look up the variable name
  1314. const T *value = GetTypedKeyValuesString<T>( localizationVariables, variableName );
  1315. int paramSize = StringFuncs<T>::Length( value );
  1316. if (paramSize >= unicodeBufferSize)
  1317. {
  1318. paramSize = MAX( 0, unicodeBufferSize - 1 );
  1319. }
  1320. StringFuncs<T>::Copy( outputPos, value, paramSize );
  1321. unicodeBufferSize -= paramSize;
  1322. outputPos += paramSize;
  1323. formatString = varEnd + 1;
  1324. }
  1325. }
  1326. }
  1327. if (shouldAdvance)
  1328. {
  1329. //copy it over, char by char
  1330. *outputPos = *formatString;
  1331. outputPos++;
  1332. unicodeBufferSize--;
  1333. formatString++;
  1334. }
  1335. }
  1336. // ensure null termination
  1337. *outputPos = '\0';
  1338. }
  1339. void CLocalize::ConstructStringKeyValuesInternal(char *unicodeOutput, int unicodeBufferSizeInBytes, const char *formatString, KeyValues *localizationVariables)
  1340. {
  1341. ConstructStringKeyValuesInternal_Impl<char>( unicodeOutput, unicodeBufferSizeInBytes, formatString, localizationVariables );
  1342. }
  1343. void CLocalize::ConstructStringKeyValuesInternal(wchar_t *unicodeOutput, int unicodeBufferSizeInBytes, const wchar_t *formatString, KeyValues *localizationVariables)
  1344. {
  1345. ConstructStringKeyValuesInternal_Impl<wchar_t>( unicodeOutput, unicodeBufferSizeInBytes, formatString, localizationVariables );
  1346. }