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.

2259 lines
60 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "host.h"
  8. #include "sysexternal.h"
  9. #include "networkstringtable.h"
  10. #include "utlbuffer.h"
  11. #include "bitbuf.h"
  12. #include "netmessages.h"
  13. #include "net.h"
  14. #include "filesystem_engine.h"
  15. #include "baseclient.h"
  16. #include "vprof.h"
  17. #include "tier2/utlstreambuffer.h"
  18. #include "checksum_engine.h"
  19. #include "MapReslistGenerator.h"
  20. #include "lzma/lzma.h"
  21. #include "../utils/common/bsplib.h"
  22. #include "ibsppack.h"
  23. #include "tier0/icommandline.h"
  24. #include "tier1/lzmaDecoder.h"
  25. #include "server.h"
  26. #include "eiface.h"
  27. #include "cdll_engine_int.h"
  28. // memdbgon must be the last include file in a .cpp file!!!
  29. #include "tier0/memdbgon.h"
  30. ConVar sv_dumpstringtables( "sv_dumpstringtables", "0", FCVAR_CHEAT );
  31. #define BSPPACK_STRINGTABLE_DICTIONARY "stringtable_dictionary.dct"
  32. #define BSPPACK_STRINGTABLE_DICTIONARY_FALLBACK "stringtable_dictionary_fallback.dct"
  33. // These are automatically added by vbsp. Should be removed when adding the real files here
  34. // These must match what is used in utils\vbsp\vbsp.cpp!!!
  35. #define BSPPACK_STRINGTABLE_DICTIONARY_GAMECONSOLE "stringtable_dictionary_xbox.dct"
  36. #define BSPPACK_STRINGTABLE_DICTIONARY_GAMECONSOLE_FALLBACK "stringtable_dictionary_fallback_xbox.dct"
  37. #define RESLISTS_FOLDER "reslists"
  38. #define RESLISTS_FOLDER_X360 "reslists_xbox"
  39. #define SUBSTRING_BITS 5
  40. struct StringHistoryEntry
  41. {
  42. char string[ (1<<SUBSTRING_BITS) ];
  43. };
  44. static int CountSimilarCharacters( char const *str1, char const *str2 )
  45. {
  46. int c = 0;
  47. while ( *str1 && *str2 &&
  48. *str1 == *str2 && c < ((1<<SUBSTRING_BITS) -1 ))
  49. {
  50. str1++;
  51. str2++;
  52. c++;
  53. }
  54. return c;
  55. }
  56. static int GetBestPreviousString( CUtlVector< StringHistoryEntry >& history, char const *newstring, int& substringsize )
  57. {
  58. int bestindex = -1;
  59. int bestcount = 0;
  60. int c = history.Count();
  61. for ( int i = 0; i < c; i++ )
  62. {
  63. char const *prev = history[ i ].string;
  64. int similar = CountSimilarCharacters( prev, newstring );
  65. if ( similar < 3 )
  66. continue;
  67. if ( similar > bestcount )
  68. {
  69. bestcount = similar;
  70. bestindex = i;
  71. }
  72. }
  73. substringsize = bestcount;
  74. return bestindex;
  75. }
  76. static ConVar stringtable_usedictionaries( "stringtable_usedictionaries",
  77. #if defined( PORTAL2 )
  78. "0", // Don't use dictionaries on portal2, its only two player! Just send them.
  79. #else
  80. "1", // On CS:GO we disable stringtable dictionaries for community servers for maps to be downloadable in code (see: CNetworkStringTable::WriteUpdate)
  81. #endif // PORTAL2
  82. 0, "Use dictionaries for string table networking\n" );
  83. static ConVar stringtable_alwaysrebuilddictionaries( "stringtable_alwaysrebuilddictionaries", "0", 0, "Rebuild dictionary file on every level load\n" );
  84. static ConVar stringtable_showsizes( "stringtable_showsizes", "0", 0, "Show sizes of string tables when building for signon\n" );
  85. class CNetworkStringTableDictionaryManager: public INetworkStringTableDictionaryMananger
  86. {
  87. public:
  88. CNetworkStringTableDictionaryManager();
  89. // INetworkStringTableDictionaryMananger
  90. virtual bool OnLevelLoadStart( char const *pchMapName, CRC32_t *pStringTableCRC );
  91. virtual void OnBSPFullyUnloaded();
  92. virtual CRC32_t GetCRC() { return m_CRC; }
  93. // Returns -1 if string can't be found in db
  94. int Find( char const *pchString ) const;
  95. char const *Lookup( int index ) const;
  96. int GetEncodeBits() const;
  97. bool ShouldRecreateDictionary( char const *pchMapName );
  98. bool IsValid() const;
  99. void ProcessBuffer( CUtlBuffer &buf );
  100. void SetLoadedFallbacks( bool bLoadedFromFallbacks );
  101. bool WriteDictionaryToBSP( char const *pchMapName, CUtlBuffer &buf, bool bCreatingFor360 );
  102. void CacheNewStringTableForWriteToBSPOnLevelShutdown( char const *pchMapName, CUtlBuffer &buf, bool bCreatingFor360 );
  103. private:
  104. void LoadMapStrings( char const *pchMapName, bool bServer );
  105. void Clear();
  106. bool LoadDictionaryFile( CUtlBuffer &buf, char const *pchMapName );
  107. CRC32_t HashStringCaselessIgnoreSlashes( char const *pString ) const
  108. {
  109. if ( !pString )
  110. {
  111. pString = "";
  112. }
  113. int len = Q_strlen( pString ) + 1;
  114. char *name = (char *)stackalloc( len );
  115. Q_strncpy( name, pString, len );
  116. Q_FixSlashes( name );
  117. Q_strlower( name );
  118. return CRC32_ProcessSingleBuffer( (const void *)name, len );
  119. }
  120. CUtlString m_sCurrentMap;
  121. // NOTE NOTE: These need to be 'persistent' objects since our code stores off raw ptrs to these strings when
  122. // precaching, etc. Can't be constructed on stack buffer or with va( "%s", xxx ), etc.!!!!!
  123. // Otherwise we could use FileNameHandle_t type stuff.
  124. // Raw strings in order from the data file
  125. CUtlVector< CUtlString > m_Strings;
  126. // Mapping of string back to index in CUtlVector
  127. CUtlMap< CRC32_t, int > m_StringHashToIndex;
  128. CRC32_t m_CRC;
  129. int m_nEncodeBits;
  130. bool m_bForceRebuildDictionaries;
  131. bool m_bLoadedFallbacks;
  132. struct CStringTableDictionaryCache
  133. {
  134. CStringTableDictionaryCache()
  135. {
  136. Reset();
  137. }
  138. void Reset()
  139. {
  140. m_bActive = false;
  141. m_sBSPName = "";
  142. m_bCreatingForX360 = false;
  143. m_Buffer.Purge();
  144. }
  145. bool m_bActive;
  146. CUtlString m_sBSPName;
  147. CUtlBuffer m_Buffer;
  148. bool m_bCreatingForX360;
  149. };
  150. CStringTableDictionaryCache m_BuildStringTableDictionaryCache;
  151. };
  152. static CNetworkStringTableDictionaryManager g_StringTableDictionary;
  153. // Expose to rest of engine
  154. INetworkStringTableDictionaryMananger *g_pStringTableDictionary = &g_StringTableDictionary;
  155. CNetworkStringTableDictionaryManager::CNetworkStringTableDictionaryManager() :
  156. m_StringHashToIndex( 0, 0, DefLessFunc( CRC32_t ) ),
  157. m_nEncodeBits( 1 ),
  158. m_bForceRebuildDictionaries( false ),
  159. m_bLoadedFallbacks( false ),
  160. m_CRC( 0 )
  161. {
  162. }
  163. void CNetworkStringTableDictionaryManager::Clear()
  164. {
  165. m_StringHashToIndex.Purge();
  166. m_Strings.Purge();
  167. m_CRC = 0;
  168. m_nEncodeBits = 1;
  169. }
  170. bool CNetworkStringTableDictionaryManager::IsValid() const
  171. {
  172. return m_Strings.Count() > 0;
  173. }
  174. int CNetworkStringTableDictionaryManager::GetEncodeBits() const
  175. {
  176. return m_nEncodeBits;
  177. }
  178. void CNetworkStringTableDictionaryManager::SetLoadedFallbacks( bool bLoadedFromFallbacks )
  179. {
  180. m_bLoadedFallbacks = bLoadedFromFallbacks;
  181. }
  182. bool CNetworkStringTableDictionaryManager::OnLevelLoadStart( char const *pchMapName, CRC32_t *pStringTableCRC )
  183. {
  184. m_BuildStringTableDictionaryCache.Reset();
  185. // INFESTED_DLL - disable string dictionaries for Alien Swarm random maps. TODO: Move a check for this into the game interface
  186. static char gamedir[MAX_OSPATH];
  187. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  188. if ( !Q_stricmp( gamedir, "infested" ) )
  189. {
  190. return true;
  191. }
  192. m_bForceRebuildDictionaries = stringtable_alwaysrebuilddictionaries.GetBool() || CommandLine()->FindParm( "-stringtables" );
  193. if ( pchMapName )
  194. LoadMapStrings( pchMapName, pStringTableCRC == NULL );
  195. else
  196. return true; // assume that stringtables will match since we will download the map later
  197. if ( pStringTableCRC )
  198. return ( *pStringTableCRC == m_CRC );
  199. return true;
  200. }
  201. bool CNetworkStringTableDictionaryManager::ShouldRecreateDictionary( char const *pchMapName )
  202. {
  203. // INFESTED_DLL - disable string dictionaries for Alien Swarm random maps. TODO: Move a check for this into the game interface
  204. static char gamedir[MAX_OSPATH];
  205. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  206. if ( !Q_stricmp( gamedir, "infested" ) )
  207. {
  208. return false;
  209. }
  210. if ( m_bForceRebuildDictionaries || // being forced with -stringtables or stringtable_alwaysrebuilddictionaries
  211. (
  212. MapReslistGenerator().IsEnabled() && // true if -makereslists
  213. ( m_Strings.Count() == 0 || // True if we couldn't load the file up before...
  214. m_bLoadedFallbacks ) // True if we loaded fallback file instead
  215. )
  216. )
  217. {
  218. return m_Strings.Count() == 0 || // True if we couldn't load the file up before...
  219. m_bLoadedFallbacks || // True if we loaded fallback file instead
  220. MapReslistGenerator().IsEnabled(); // true if -makereslists
  221. }
  222. return false;
  223. }
  224. inline char const *GetStringTableDictionaryFileName( bool bIsFallback )
  225. {
  226. if ( bIsFallback )
  227. {
  228. if ( IsGameConsole() || NET_IsDedicatedForXbox() )
  229. {
  230. return BSPPACK_STRINGTABLE_DICTIONARY_GAMECONSOLE_FALLBACK;
  231. }
  232. return BSPPACK_STRINGTABLE_DICTIONARY_FALLBACK;
  233. }
  234. if ( IsGameConsole() || NET_IsDedicatedForXbox() )
  235. {
  236. return BSPPACK_STRINGTABLE_DICTIONARY_GAMECONSOLE;
  237. }
  238. return BSPPACK_STRINGTABLE_DICTIONARY;
  239. }
  240. bool CNetworkStringTableDictionaryManager::LoadDictionaryFile( CUtlBuffer &buf, char const *pchMapName )
  241. {
  242. m_bLoadedFallbacks = false;
  243. if ( g_pFileSystem->ReadFile( GetStringTableDictionaryFileName( false ), "BSP", buf ) )
  244. {
  245. return true;
  246. }
  247. // Try backup file
  248. if ( g_pFileSystem->ReadFile( GetStringTableDictionaryFileName( true ), "BSP", buf ) )
  249. {
  250. Warning( "#######################################\n" );
  251. Warning( "Map %s using default stringtable dictionary, don't ship this way!!!\n", pchMapName );
  252. Warning( "Run with -stringtables on the command line or convar\n" );
  253. Warning( "stringtable_alwaysrebuilddictionaries enabled to build the string table\n" );
  254. Warning( "#######################################\n" );
  255. m_bLoadedFallbacks = true;
  256. return true;
  257. }
  258. // Try fallback file
  259. char szFallback[ 256 ];
  260. Q_snprintf( szFallback, sizeof( szFallback ), "reslists/%s.dict", pchMapName );
  261. if ( g_pFileSystem->ReadFile( szFallback, "GAME", buf ) )
  262. {
  263. Warning( "#######################################\n" );
  264. Warning( "Map %s using fallback stringtable dictionary, don't ship this way!!!\n", pchMapName );
  265. Warning( "Run with -stringtables on the command line or convar\n" );
  266. Warning( "stringtable_alwaysrebuilddictionaries enabled to build the string table\n" );
  267. Warning( "#######################################\n" );
  268. m_bLoadedFallbacks = true;
  269. return true;
  270. }
  271. Warning( "#######################################\n" );
  272. Warning( "Map %s missing stringtable dictionary, don't ship this way!!!\n", pchMapName );
  273. Warning( "Run with -stringtables on the command line or convar\n" );
  274. Warning( "stringtable_alwaysrebuilddictionaries enabled to build the string table\n" );
  275. Warning( "#######################################\n" );
  276. return false;
  277. }
  278. void CNetworkStringTableDictionaryManager::ProcessBuffer( CUtlBuffer &buf )
  279. {
  280. Clear();
  281. m_CRC = CRC32_ProcessSingleBuffer( buf.Base(), buf.TellMaxPut() );
  282. while ( buf.GetBytesRemaining() > 0 )
  283. {
  284. char line[ MAX_PATH ];
  285. buf.GetString( line, sizeof( line ) );
  286. if ( !line[ 0 ] )
  287. continue;
  288. MEM_ALLOC_CREDIT();
  289. CUtlString str;
  290. str = line;
  291. int index = m_Strings.AddToTail( str );
  292. CRC32_t hash = HashStringCaselessIgnoreSlashes( str );
  293. m_StringHashToIndex.Insert( hash, index );
  294. }
  295. m_nEncodeBits = Q_log2( m_Strings.Count() ) + 1;
  296. }
  297. void CNetworkStringTableDictionaryManager::LoadMapStrings( char const *pchMapName, bool bServer )
  298. {
  299. if ( m_sCurrentMap == pchMapName )
  300. return;
  301. m_sCurrentMap = pchMapName;
  302. Clear();
  303. // On the client we need to add the bsp to the file search path before loading the dictionary file...
  304. // It's added on the server in CGameServer::SpawnServer
  305. char szModelName[MAX_PATH];
  306. char szNameOnDisk[MAX_PATH];
  307. Q_snprintf( szModelName, sizeof( szModelName ), "maps/%s.bsp", pchMapName );
  308. GetMapPathNameOnDisk( szNameOnDisk, szModelName, sizeof( szNameOnDisk ) );
  309. if ( !bServer )
  310. {
  311. // Add to file system
  312. g_pFileSystem->AddSearchPath( szNameOnDisk, "GAME", PATH_ADD_TO_HEAD );
  313. // Force reload all materials since BSP has changed
  314. // TODO: modelloader->UnloadUnreferencedModels();
  315. materials->ReloadMaterials();
  316. }
  317. if ( stringtable_usedictionaries.GetBool() )
  318. {
  319. // Load the data file
  320. CUtlBuffer buf;
  321. if ( LoadDictionaryFile( buf, pchMapName ) )
  322. {
  323. // LZMA decompress it if needed
  324. CLZMA lzma;
  325. if ( lzma.IsCompressed( (byte *)buf.Base() ) )
  326. {
  327. unsigned int decompressedSize = lzma.GetActualSize( (byte *)buf.Base() );
  328. byte *work = new byte[ decompressedSize ];
  329. int outputLength = lzma.Uncompress( (byte *)buf.Base(), work );
  330. buf.Clear();
  331. buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  332. buf.Put( work, outputLength );
  333. delete[] work;
  334. }
  335. ProcessBuffer( buf );
  336. }
  337. }
  338. }
  339. int CNetworkStringTableDictionaryManager::Find( char const *pchString ) const
  340. {
  341. CRC32_t hash = HashStringCaselessIgnoreSlashes( pchString );
  342. // Note that a string can contain subparts that together both exist as a valid filename, but they may be from separate paths
  343. int idx = m_StringHashToIndex.Find( hash );
  344. if ( idx == m_StringHashToIndex.InvalidIndex() )
  345. return -1;
  346. return m_StringHashToIndex[ idx ];
  347. }
  348. char const *CNetworkStringTableDictionaryManager::Lookup( int index ) const
  349. {
  350. if ( index < 0 || index >= m_Strings.Count() )
  351. {
  352. Assert( 0 );
  353. return "";
  354. }
  355. const CUtlString &string = m_Strings[ index ];
  356. return string.String();
  357. }
  358. void CNetworkStringTableDictionaryManager::OnBSPFullyUnloaded()
  359. {
  360. if ( !m_BuildStringTableDictionaryCache.m_bActive )
  361. return;
  362. WriteDictionaryToBSP( m_BuildStringTableDictionaryCache.m_sBSPName, m_BuildStringTableDictionaryCache.m_Buffer, m_BuildStringTableDictionaryCache.m_bCreatingForX360 );
  363. m_BuildStringTableDictionaryCache.Reset();
  364. }
  365. bool CNetworkStringTableDictionaryManager::WriteDictionaryToBSP( char const *pchMapName, CUtlBuffer &buf, bool bCreatingFor360 )
  366. {
  367. char mapPath[ MAX_PATH ];
  368. Q_snprintf( mapPath, sizeof( mapPath ), "maps/%s.bsp", pchMapName );
  369. // We shouldn't ever fail here since we don't queue this up if it might fail
  370. // Make sure that the file is writable before building stringtable dictionary.
  371. if ( !g_pFileSystem->IsFileWritable( mapPath, "GAME" ) )
  372. {
  373. return false;
  374. }
  375. // load the bsppack dll
  376. IBSPPack *iBSPPack = NULL;
  377. CSysModule *pModule = FileSystem_LoadModule( "bsppack" );
  378. if ( pModule )
  379. {
  380. CreateInterfaceFn factory = Sys_GetFactory( pModule );
  381. if ( factory )
  382. {
  383. iBSPPack = ( IBSPPack * )factory( IBSPPACK_VERSION_STRING, NULL );
  384. }
  385. }
  386. if( !iBSPPack )
  387. {
  388. if ( pModule )
  389. {
  390. Sys_UnloadModule( pModule );
  391. }
  392. ConMsg( "Can't load bsppack.dll\n" );
  393. return false;
  394. }
  395. iBSPPack->LoadBSPFile( g_pFileSystem, mapPath );
  396. char const *relative = bCreatingFor360 ? BSPPACK_STRINGTABLE_DICTIONARY_GAMECONSOLE : BSPPACK_STRINGTABLE_DICTIONARY;
  397. // Remove the fallback that VBSP adds by default
  398. iBSPPack->RemoveFileFromPack( bCreatingFor360 ? BSPPACK_STRINGTABLE_DICTIONARY_GAMECONSOLE_FALLBACK : BSPPACK_STRINGTABLE_DICTIONARY_FALLBACK );
  399. // Now add in the up to date information
  400. iBSPPack->AddBufferToPack( relative, buf.Base(), buf.TellPut(), false );
  401. iBSPPack->WriteBSPFile( mapPath );
  402. iBSPPack->ClearPackFile();
  403. FileSystem_UnloadModule( pModule );
  404. Msg( "Updated stringtable dictionary saved to %s\n", mapPath );
  405. return true;
  406. }
  407. void CNetworkStringTableDictionaryManager::CacheNewStringTableForWriteToBSPOnLevelShutdown( char const *pchMapName, CUtlBuffer &buf, bool bCreatingFor360 )
  408. {
  409. m_BuildStringTableDictionaryCache.m_bActive = true;
  410. m_BuildStringTableDictionaryCache.m_sBSPName = pchMapName;
  411. m_BuildStringTableDictionaryCache.m_Buffer.Purge();
  412. m_BuildStringTableDictionaryCache.m_Buffer.Put( buf.Base(), buf.TellPut() );
  413. m_BuildStringTableDictionaryCache.m_bCreatingForX360 = bCreatingFor360;
  414. }
  415. //-----------------------------------------------------------------------------
  416. // Implementation for general purpose strings
  417. //-----------------------------------------------------------------------------
  418. class CNetworkStringDict : public INetworkStringDict
  419. {
  420. public:
  421. CNetworkStringDict( bool bUseDictionary ) :
  422. m_bUseDictionary( bUseDictionary ),
  423. m_Items( 0, 0, CTableItem::Less )
  424. {
  425. }
  426. virtual ~CNetworkStringDict()
  427. {
  428. Purge();
  429. }
  430. unsigned int Count()
  431. {
  432. return m_Items.Count();
  433. }
  434. void Purge()
  435. {
  436. m_Items.Purge();
  437. }
  438. const char *String( int index )
  439. {
  440. return m_Items.Key( index ).GetName();
  441. }
  442. bool IsValidIndex( int index )
  443. {
  444. return m_Items.IsValidIndex( index );
  445. }
  446. int Insert( const char *pString )
  447. {
  448. CTableItem item;
  449. item.SetName( m_bUseDictionary, pString );
  450. return m_Items.Insert( item );
  451. }
  452. int Find( const char *pString )
  453. {
  454. CTableItem search;
  455. search.SetName( false, pString );
  456. int iResult = m_Items.Find( search );
  457. if ( iResult == m_Items.InvalidIndex() )
  458. {
  459. return -1;
  460. }
  461. return iResult;
  462. }
  463. CNetworkStringTableItem &Element( int index )
  464. {
  465. return m_Items.Element( index );
  466. }
  467. const CNetworkStringTableItem &Element( int index ) const
  468. {
  469. return m_Items.Element( index );
  470. }
  471. virtual void UpdateDictionary( int index )
  472. {
  473. if ( !m_bUseDictionary )
  474. return;
  475. CTableItem &item = m_Items.Key( index );
  476. item.Update();
  477. }
  478. virtual int DictionaryIndex( int index )
  479. {
  480. if ( !m_bUseDictionary )
  481. return -1;
  482. CTableItem &item = m_Items.Key( index );
  483. return item.GetDictionaryIndex();
  484. }
  485. private:
  486. bool m_bUseDictionary;
  487. // We use this type of item to avoid having two copies of the strings in memory --
  488. // either we have a dictionary slot and point to that, or we have a m_Name CUtlString that gets
  489. // wiped between levels
  490. class CTableItem
  491. {
  492. public:
  493. CTableItem() : m_DictionaryIndex( -1 ), m_StringHash( 0u )
  494. {
  495. }
  496. char const *GetName() const
  497. {
  498. return m_Name.String();
  499. }
  500. void Update()
  501. {
  502. m_DictionaryIndex = g_StringTableDictionary.Find( m_Name.String() );
  503. ComputeHash();
  504. }
  505. int GetDictionaryIndex() const
  506. {
  507. return m_DictionaryIndex;
  508. }
  509. void SetName( bool bUseDictionary, char const *pString )
  510. {
  511. m_Name = pString;
  512. m_DictionaryIndex = bUseDictionary ? g_StringTableDictionary.Find( pString ) : -1;
  513. ComputeHash();
  514. }
  515. static bool Less( const CTableItem &lhs, const CTableItem &rhs )
  516. {
  517. return lhs.m_StringHash < rhs.m_StringHash;
  518. }
  519. private:
  520. void ComputeHash()
  521. {
  522. char const *pName = GetName();
  523. int len = Q_strlen( pName ) + 1;
  524. char *name = (char *)stackalloc( len );
  525. Q_strncpy( name, pName, len );
  526. Q_FixSlashes( name );
  527. Q_strlower( name );
  528. m_StringHash = CRC32_ProcessSingleBuffer( (const void *)name, len );
  529. }
  530. int m_DictionaryIndex;
  531. CUtlString m_Name;
  532. CRC32_t m_StringHash;
  533. };
  534. CUtlMap< CTableItem, CNetworkStringTableItem > m_Items;
  535. };
  536. void CNetworkStringTable::CheckDictionary( int stringNumber )
  537. {
  538. m_pItems->UpdateDictionary( stringNumber );
  539. }
  540. //-----------------------------------------------------------------------------
  541. // Purpose:
  542. // Input : id -
  543. // *tableName -
  544. // maxentries -
  545. //-----------------------------------------------------------------------------
  546. CNetworkStringTable::CNetworkStringTable( TABLEID id, const char *tableName, int maxentries, int userdatafixedsize, int userdatanetworkbits, int flags ) :
  547. m_bAllowClientSideAddString( false ),
  548. m_pItemsClientSide( NULL ),
  549. m_nFlags( flags )
  550. {
  551. if ( maxentries < 0 || userdatafixedsize < 0 || userdatanetworkbits < 0 )
  552. {
  553. Host_Error( "String tables negative constructor unsupported %i %i %i\n",
  554. maxentries, userdatafixedsize, userdatanetworkbits );
  555. }
  556. m_id = id;
  557. int len = strlen( tableName ) + 1;
  558. m_pszTableName = new char[ len ];
  559. Assert( m_pszTableName );
  560. Assert( tableName );
  561. Q_strncpy( m_pszTableName, tableName, len );
  562. m_changeFunc = NULL;
  563. m_pObject = NULL;
  564. m_nTickCount = 0;
  565. for ( int i = 0; i < MIRROR_TABLE_MAX_COUNT; ++i )
  566. m_pMirrorTable[ i ] = NULL;
  567. m_nLastChangedTick = 0;
  568. m_bChangeHistoryEnabled = false;
  569. m_bLocked = false;
  570. m_nMaxEntries = maxentries;
  571. m_nEntryBits = Q_log2( m_nMaxEntries );
  572. m_bUserDataFixedSize = userdatafixedsize != 0;
  573. m_nUserDataSize = userdatafixedsize;
  574. m_nUserDataSizeBits = userdatanetworkbits;
  575. if ( m_nUserDataSizeBits > CNetworkStringTableItem::MAX_USERDATA_BITS )
  576. {
  577. Host_Error( "String tables user data bits restricted to %i bits, requested %i is too large\n",
  578. CNetworkStringTableItem::MAX_USERDATA_BITS,
  579. m_nUserDataSizeBits );
  580. }
  581. if ( m_nUserDataSize > CNetworkStringTableItem::MAX_USERDATA_SIZE )
  582. {
  583. Host_Error( "String tables user data size restricted to %i bytes, requested %i is too large\n",
  584. CNetworkStringTableItem::MAX_USERDATA_SIZE,
  585. m_nUserDataSize );
  586. }
  587. // Make sure maxentries is power of 2
  588. if ( ( 1 << m_nEntryBits ) != maxentries )
  589. {
  590. Host_Error( "String tables must be powers of two in size!, %i is not a power of 2 [%s]\n", maxentries, tableName );
  591. }
  592. m_pItems = new CNetworkStringDict( m_nFlags & NSF_DICTIONARY_ENABLED );
  593. }
  594. void CNetworkStringTable::SetAllowClientSideAddString( bool state )
  595. {
  596. if ( state == m_bAllowClientSideAddString )
  597. return;
  598. m_bAllowClientSideAddString = state;
  599. if ( m_pItemsClientSide )
  600. {
  601. delete m_pItemsClientSide;
  602. m_pItemsClientSide = NULL;
  603. }
  604. if ( m_bAllowClientSideAddString )
  605. {
  606. m_pItemsClientSide = new CNetworkStringDict( NSF_NONE );
  607. m_pItemsClientSide->Insert( "___clientsideitemsplaceholder0___" ); // 0 slot can't be used
  608. m_pItemsClientSide->Insert( "___clientsideitemsplaceholder1___" ); // -1 can't be used since it looks like the "invalid" index from other string lookups
  609. }
  610. }
  611. //-----------------------------------------------------------------------------
  612. // Purpose:
  613. // Output : Returns true on success, false on failure.
  614. //-----------------------------------------------------------------------------
  615. bool CNetworkStringTable::IsUserDataFixedSize() const
  616. {
  617. return m_bUserDataFixedSize;
  618. }
  619. bool CNetworkStringTable::IsUsingDictionary() const
  620. {
  621. return m_nFlags & NSF_DICTIONARY_ENABLED;
  622. }
  623. //-----------------------------------------------------------------------------
  624. // Purpose:
  625. //-----------------------------------------------------------------------------
  626. int CNetworkStringTable::GetUserDataSize() const
  627. {
  628. return m_nUserDataSize;
  629. }
  630. //-----------------------------------------------------------------------------
  631. // Purpose:
  632. //-----------------------------------------------------------------------------
  633. int CNetworkStringTable::GetUserDataSizeBits() const
  634. {
  635. return m_nUserDataSizeBits;
  636. }
  637. //-----------------------------------------------------------------------------
  638. // Purpose:
  639. //-----------------------------------------------------------------------------
  640. CNetworkStringTable::~CNetworkStringTable( void )
  641. {
  642. delete[] m_pszTableName;
  643. delete m_pItems;
  644. delete m_pItemsClientSide;
  645. }
  646. //-----------------------------------------------------------------------------
  647. // Purpose:
  648. //-----------------------------------------------------------------------------
  649. void CNetworkStringTable::DeleteAllStrings( void )
  650. {
  651. delete m_pItems;
  652. m_pItems = new CNetworkStringDict( m_nFlags & NSF_DICTIONARY_ENABLED );
  653. if ( m_pItemsClientSide )
  654. {
  655. delete m_pItemsClientSide;
  656. m_pItemsClientSide = new CNetworkStringDict( NSF_NONE );
  657. m_pItemsClientSide->Insert( "___clientsideitemsplaceholder0___" ); // 0 slot can't be used
  658. m_pItemsClientSide->Insert( "___clientsideitemsplaceholder1___" ); // -1 can't be used since it looks like the "invalid" index from other string lookups
  659. }
  660. }
  661. //-----------------------------------------------------------------------------
  662. // Purpose:
  663. // Input : i -
  664. // Output : CNetworkStringTableItem
  665. //-----------------------------------------------------------------------------
  666. CNetworkStringTableItem *CNetworkStringTable::GetItem( int i )
  667. {
  668. if ( i >= 0 )
  669. {
  670. return &m_pItems->Element( i );
  671. }
  672. Assert( m_pItemsClientSide );
  673. return &m_pItemsClientSide->Element( -i );
  674. }
  675. //-----------------------------------------------------------------------------
  676. // Purpose: Returns the table identifier
  677. // Output : TABLEID
  678. //-----------------------------------------------------------------------------
  679. TABLEID CNetworkStringTable::GetTableId( void ) const
  680. {
  681. return m_id;
  682. }
  683. //-----------------------------------------------------------------------------
  684. // Purpose: Returns the max size of the table
  685. // Output : int
  686. //-----------------------------------------------------------------------------
  687. int CNetworkStringTable::GetMaxStrings( void ) const
  688. {
  689. return m_nMaxEntries;
  690. }
  691. //-----------------------------------------------------------------------------
  692. // Purpose: Returns a table, by name
  693. // Output : const char
  694. //-----------------------------------------------------------------------------
  695. const char *CNetworkStringTable::GetTableName( void ) const
  696. {
  697. return m_pszTableName;
  698. }
  699. //-----------------------------------------------------------------------------
  700. // Purpose: Returns the number of bits needed to encode an entry index
  701. // Output : int
  702. //-----------------------------------------------------------------------------
  703. int CNetworkStringTable::GetEntryBits( void ) const
  704. {
  705. return m_nEntryBits;
  706. }
  707. void CNetworkStringTable::SetTick(int tick_count)
  708. {
  709. Assert( tick_count >= m_nTickCount );
  710. m_nTickCount = tick_count;
  711. }
  712. bool CNetworkStringTable::Lock( bool bLock )
  713. {
  714. bool bState = m_bLocked;
  715. m_bLocked = bLock;
  716. return bState;
  717. }
  718. pfnStringChanged CNetworkStringTable::GetCallback()
  719. {
  720. return m_changeFunc;
  721. }
  722. #ifndef SHARED_NET_STRING_TABLES
  723. //-----------------------------------------------------------------------------
  724. // Purpose:
  725. //-----------------------------------------------------------------------------
  726. void CNetworkStringTable::EnableRollback()
  727. {
  728. // stringtable must be empty
  729. Assert( m_pItems->Count() == 0);
  730. m_bChangeHistoryEnabled = true;
  731. }
  732. void CNetworkStringTable::SetMirrorTable(uint nIndex, INetworkStringTable *table)
  733. {
  734. Assert( nIndex < MIRROR_TABLE_MAX_COUNT );
  735. m_pMirrorTable[ nIndex ] = table;
  736. }
  737. void CNetworkStringTable::RestoreTick(int tick)
  738. {
  739. // TODO optimize this, most of the time the tables doens't really change
  740. m_nLastChangedTick = 0;
  741. int count = m_pItems->Count();
  742. for ( int i = 0; i < count; i++ )
  743. {
  744. // restore tick in all entries
  745. int tickChanged = m_pItems->Element( i ).RestoreTick( tick );
  746. if ( tickChanged > m_nLastChangedTick )
  747. m_nLastChangedTick = tickChanged;
  748. }
  749. }
  750. //-----------------------------------------------------------------------------
  751. // Purpose: updates the mirror table, if set one
  752. // Output : return true if some entries were updates
  753. //-----------------------------------------------------------------------------
  754. void CNetworkStringTable::UpdateMirrorTable( int tick_ack )
  755. {
  756. for ( int nMirrorTableIndex = 0; nMirrorTableIndex < MIRROR_TABLE_MAX_COUNT; ++nMirrorTableIndex )
  757. {
  758. if ( !m_pMirrorTable[ nMirrorTableIndex ] )
  759. continue;
  760. m_pMirrorTable[ nMirrorTableIndex ]->SetTick( m_nTickCount ); // use same tick
  761. int count = m_pItems->Count();
  762. for ( int i = 0; i < count; i++ )
  763. {
  764. CNetworkStringTableItem *p = &m_pItems->Element( i );
  765. // mirror is up to date
  766. if ( p->GetTickChanged() <= tick_ack )
  767. continue;
  768. const void *pUserData = p->GetUserData();
  769. int nBytes = p->GetUserDataLength();
  770. if ( !nBytes || !pUserData )
  771. {
  772. nBytes = 0;
  773. pUserData = NULL;
  774. }
  775. // Check if we are updating an old entry or adding a new one
  776. if ( i < m_pMirrorTable[ nMirrorTableIndex ]->GetNumStrings() )
  777. {
  778. m_pMirrorTable[ nMirrorTableIndex ]->SetStringUserData( i, nBytes, pUserData );
  779. }
  780. else
  781. {
  782. // Grow the table (entryindex must be the next empty slot)
  783. Assert( i == m_pMirrorTable[ nMirrorTableIndex ]->GetNumStrings() );
  784. char const *pName = m_pItems->String( i );
  785. m_pMirrorTable[ nMirrorTableIndex ]->AddString( true, pName, nBytes, pUserData );
  786. }
  787. }
  788. }
  789. }
  790. int CNetworkStringTable::WriteUpdate( CBaseClient *client, bf_write &buf, int tick_ack ) const
  791. {
  792. CUtlVector< StringHistoryEntry > history;
  793. int entriesUpdated = 0;
  794. int lastEntry = -1;
  795. int lastDictionaryIndex = -1;
  796. int nDictionaryEncodeBits = g_StringTableDictionary.GetEncodeBits();
  797. bool bUseDictionaries = IsUsingDictionary();
  798. bool bEncodeUsingDictionaries = bUseDictionaries && stringtable_usedictionaries.GetBool() && g_StringTableDictionary.IsValid();
  799. // INFESTED_DLL - disable string dictionaries for Alien Swarm random maps. TODO: Move a check for this into the game interface
  800. static char gamedir[MAX_OSPATH];
  801. Q_FileBase( com_gamedir, gamedir, sizeof( gamedir ) );
  802. if ( !Q_stricmp( gamedir, "infested" ) )
  803. {
  804. bEncodeUsingDictionaries = false;
  805. }
  806. // CSGO_DLL - disable string dictionaries for CS:GO community servers to allow for map downloading,
  807. // keep it enabled on Valve official servers since clients always have all maps or on listen servers
  808. if ( bEncodeUsingDictionaries && !Q_stricmp( gamedir, "csgo" ) )
  809. {
  810. bEncodeUsingDictionaries = IsGameConsole();
  811. }
  812. int count = m_pItems->Count();
  813. int nDictionaryCount = 0;
  814. buf.WriteOneBit( bEncodeUsingDictionaries ? 1 : 0 );
  815. for ( int i = 0; i < count; i++ )
  816. {
  817. CNetworkStringTableItem *p = &m_pItems->Element( i );
  818. // Client is up to date
  819. if ( p->GetTickChanged() <= tick_ack )
  820. continue;
  821. int nStartBit = buf.GetNumBitsWritten();
  822. // Write Entry index
  823. if ( (lastEntry+1) == i )
  824. {
  825. buf.WriteOneBit( 1 );
  826. }
  827. else
  828. {
  829. buf.WriteOneBit( 0 );
  830. buf.WriteUBitLong( i, m_nEntryBits );
  831. }
  832. // check if string can use older string as base eg "models/weapons/gun1" & "models/weapons/gun2"
  833. char const *pEntry = m_pItems->String( i );
  834. if ( p->GetTickCreated() >= tick_ack )
  835. {
  836. // this item has just been created, send string itself
  837. buf.WriteOneBit( 1 );
  838. int nCurrentDictionaryIndex = m_pItems->DictionaryIndex( i );
  839. if ( bEncodeUsingDictionaries &&
  840. nCurrentDictionaryIndex != -1 )
  841. {
  842. ++nDictionaryCount;
  843. buf.WriteOneBit( 1 );
  844. if ( ( lastDictionaryIndex +1 ) == nCurrentDictionaryIndex )
  845. {
  846. buf.WriteOneBit( 1 );
  847. }
  848. else
  849. {
  850. buf.WriteOneBit( 0 );
  851. buf.WriteUBitLong( nCurrentDictionaryIndex, nDictionaryEncodeBits );
  852. }
  853. lastDictionaryIndex = nCurrentDictionaryIndex;
  854. }
  855. else
  856. {
  857. if ( bEncodeUsingDictionaries )
  858. {
  859. buf.WriteOneBit( 0 );
  860. }
  861. int substringsize = 0;
  862. int bestprevious = GetBestPreviousString( history, pEntry, substringsize );
  863. if ( bestprevious != -1 )
  864. {
  865. buf.WriteOneBit( 1 );
  866. buf.WriteUBitLong( bestprevious, 5 ); // history never has more than 32 entries
  867. buf.WriteUBitLong( substringsize, SUBSTRING_BITS );
  868. buf.WriteString( pEntry + substringsize );
  869. }
  870. else
  871. {
  872. buf.WriteOneBit( 0 );
  873. buf.WriteString( pEntry );
  874. }
  875. }
  876. }
  877. else
  878. {
  879. buf.WriteOneBit( 0 );
  880. }
  881. // Write the item's user data.
  882. int len;
  883. const void *pUserData = GetStringUserData( i, &len );
  884. if ( pUserData && len > 0 )
  885. {
  886. buf.WriteOneBit( 1 );
  887. if ( IsUserDataFixedSize() )
  888. {
  889. // Don't have to send length, it was sent as part of the table definition
  890. buf.WriteBits( pUserData, GetUserDataSizeBits() );
  891. }
  892. else
  893. {
  894. buf.WriteUBitLong( len, CNetworkStringTableItem::MAX_USERDATA_BITS );
  895. buf.WriteBits( pUserData, len*8 );
  896. }
  897. }
  898. else
  899. {
  900. buf.WriteOneBit( 0 );
  901. }
  902. // limit string history to 32 entries
  903. if ( history.Count() > 31 )
  904. {
  905. history.Remove( 0 );
  906. }
  907. // add string to string history
  908. StringHistoryEntry she;
  909. Q_strncpy( she.string, pEntry, sizeof( she.string ) );
  910. history.AddToTail( she );
  911. entriesUpdated++;
  912. lastEntry = i;
  913. if ( client && client->IsTracing() )
  914. {
  915. int nBits = buf.GetNumBitsWritten() - nStartBit;
  916. client->TraceNetworkMsg( nBits, " [%s] %d:%s ", GetTableName(), i, GetString( i ) );
  917. }
  918. }
  919. // If writing the baseline, and using dictionaries, and less than 90% of strings are in the dictionaries,
  920. // then we need to consider rebuild the dictionaries
  921. if ( tick_ack == -1 &&
  922. count > 20 &&
  923. bEncodeUsingDictionaries &&
  924. nDictionaryCount < 0.9f * count )
  925. {
  926. if ( IsGameConsole() )
  927. {
  928. Warning( "String Table dictionary for %s should be rebuilt, only found %d of %d strings in dictionary\n", GetTableName(), nDictionaryCount, count );
  929. }
  930. }
  931. return entriesUpdated;
  932. }
  933. //-----------------------------------------------------------------------------
  934. // Purpose: Parse string update
  935. //-----------------------------------------------------------------------------
  936. void CNetworkStringTable::ParseUpdate( bf_read &buf, int entries )
  937. {
  938. int lastEntry = -1;
  939. int lastDictionaryIndex = -1;
  940. int nDictionaryEncodeBits = g_StringTableDictionary.GetEncodeBits();
  941. // bool bUseDictionaries = IsUsingDictionary();
  942. bool bEncodeUsingDictionaries = buf.ReadOneBit() ? true : false;
  943. CUtlVector< StringHistoryEntry > history;
  944. for (int i=0; i<entries; i++)
  945. {
  946. int entryIndex = lastEntry + 1;
  947. if ( !buf.ReadOneBit() )
  948. {
  949. entryIndex = buf.ReadUBitLong( GetEntryBits() );
  950. }
  951. lastEntry = entryIndex;
  952. if ( entryIndex < 0 || entryIndex >= GetMaxStrings() )
  953. {
  954. Host_Error( "Server sent bogus string index %i for table %s\n", entryIndex, GetTableName() );
  955. }
  956. const char *pEntry = NULL;
  957. char entry[ 1024 ];
  958. char substr[ 1024 ];
  959. if ( buf.ReadOneBit() )
  960. {
  961. // It's using dictionary
  962. if ( bEncodeUsingDictionaries && buf.ReadOneBit() )
  963. {
  964. // It's sequential, no need to encode full dictionary index
  965. if ( buf.ReadOneBit() )
  966. {
  967. lastDictionaryIndex++;
  968. }
  969. else
  970. {
  971. lastDictionaryIndex = buf.ReadUBitLong( nDictionaryEncodeBits );
  972. }
  973. char const *lookup = g_StringTableDictionary.Lookup( lastDictionaryIndex );
  974. Q_strncpy( entry, lookup, sizeof( entry ) );
  975. }
  976. else
  977. {
  978. bool substringcheck = buf.ReadOneBit() ? true : false;
  979. if ( substringcheck )
  980. {
  981. int index = buf.ReadUBitLong( 5 );
  982. int bytestocopy = buf.ReadUBitLong( SUBSTRING_BITS );
  983. Q_strncpy( entry, history[ index ].string, bytestocopy + 1 );
  984. buf.ReadString( substr, sizeof(substr) );
  985. Q_strncat( entry, substr, sizeof(entry), COPY_ALL_CHARACTERS );
  986. }
  987. else
  988. {
  989. buf.ReadString( entry, sizeof( entry ) );
  990. }
  991. }
  992. pEntry = entry;
  993. }
  994. // Read in the user data.
  995. unsigned char tempbuf[ CNetworkStringTableItem::MAX_USERDATA_SIZE ];
  996. memset( tempbuf, 0, sizeof( tempbuf ) );
  997. const void *pUserData = NULL;
  998. int nBytes = 0;
  999. if ( buf.ReadOneBit() )
  1000. {
  1001. if ( IsUserDataFixedSize() )
  1002. {
  1003. // Don't need to read length, it's fixed length and the length was networked down already.
  1004. nBytes = GetUserDataSize();
  1005. Assert( nBytes > 0 );
  1006. tempbuf[nBytes-1] = 0; // be safe, clear last byte
  1007. buf.ReadBits( tempbuf, GetUserDataSizeBits() );
  1008. }
  1009. else
  1010. {
  1011. nBytes = buf.ReadUBitLong( CNetworkStringTableItem::MAX_USERDATA_BITS );
  1012. ErrorIfNot( nBytes <= sizeof( tempbuf ),
  1013. ("CNetworkStringTableClient::ParseUpdate: message too large (%d bytes).", nBytes)
  1014. );
  1015. buf.ReadBytes( tempbuf, nBytes );
  1016. }
  1017. pUserData = tempbuf;
  1018. }
  1019. // Check if we are updating an old entry or adding a new one
  1020. if ( entryIndex < GetNumStrings() )
  1021. {
  1022. SetStringUserData( entryIndex, nBytes, pUserData );
  1023. #ifdef _DEBUG
  1024. if ( pEntry )
  1025. {
  1026. Assert( !Q_strcmp( pEntry, GetString( entryIndex ) ) ); // make sure string didn't change
  1027. }
  1028. #endif
  1029. pEntry = GetString( entryIndex ); // string didn't change
  1030. }
  1031. else
  1032. {
  1033. // Grow the table (entryindex must be the next empty slot)
  1034. Assert( (entryIndex == GetNumStrings()) && (pEntry != NULL) );
  1035. if ( pEntry == NULL )
  1036. {
  1037. Msg("CNetworkStringTable::ParseUpdate: NULL pEntry, table %s, index %i\n", GetTableName(), entryIndex );
  1038. pEntry = "";// avoid crash because of NULL strings
  1039. }
  1040. AddString( true, pEntry, nBytes, pUserData );
  1041. }
  1042. if ( history.Count() > 31 )
  1043. {
  1044. history.Remove( 0 );
  1045. }
  1046. StringHistoryEntry she;
  1047. Q_strncpy( she.string, pEntry, sizeof( she.string ) );
  1048. history.AddToTail( she );
  1049. }
  1050. }
  1051. void CNetworkStringTable::CopyStringTable(CNetworkStringTable * table)
  1052. {
  1053. Assert (m_pItems->Count() == 0); // table must be empty before coping
  1054. for ( unsigned int i = 0; i < table->m_pItems->Count() ; ++i )
  1055. {
  1056. CNetworkStringTableItem *item = &table->m_pItems->Element( i );
  1057. m_nTickCount = item->m_nTickChanged;
  1058. AddString( true, table->GetString( i ), item->m_nUserDataLength, item->m_pUserData );
  1059. }
  1060. }
  1061. #endif
  1062. void CNetworkStringTable::TriggerCallbacks( int tick_ack )
  1063. {
  1064. if ( m_changeFunc == NULL )
  1065. return;
  1066. COM_TimestampedLog( "Change(%s):Start", GetTableName() );
  1067. int count = m_pItems->Count();
  1068. for ( int i = 0; i < count; i++ )
  1069. {
  1070. CNetworkStringTableItem *pItem = &m_pItems->Element( i );
  1071. // mirror is up to date
  1072. if ( pItem->GetTickChanged() <= tick_ack )
  1073. continue;
  1074. int userDataSize;
  1075. const void *pUserData = pItem->GetUserData( &userDataSize );
  1076. // fire the callback function
  1077. ( *m_changeFunc )( m_pObject, this, i, GetString( i ), pUserData );
  1078. }
  1079. COM_TimestampedLog( "Change(%s):End", GetTableName() );
  1080. }
  1081. //-----------------------------------------------------------------------------
  1082. // Purpose:
  1083. // Input : changeFunc -
  1084. //-----------------------------------------------------------------------------
  1085. void CNetworkStringTable::SetStringChangedCallback( void *object, pfnStringChanged changeFunc )
  1086. {
  1087. m_changeFunc = changeFunc;
  1088. m_pObject = object;
  1089. }
  1090. //-----------------------------------------------------------------------------
  1091. // Purpose:
  1092. // Input : *client -
  1093. // Output : Returns true on success, false on failure.
  1094. //-----------------------------------------------------------------------------
  1095. bool CNetworkStringTable::ChangedSinceTick( int tick ) const
  1096. {
  1097. return ( m_nLastChangedTick > tick );
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. // Purpose:
  1101. // Input : *value -
  1102. // Output : int
  1103. //-----------------------------------------------------------------------------
  1104. int CNetworkStringTable::AddString( bool bIsServer, const char *string, int length /*= -1*/, const void *userdata /*= NULL*/ )
  1105. {
  1106. bool bHasChanged;
  1107. CNetworkStringTableItem *item;
  1108. if ( !string )
  1109. {
  1110. Assert( string );
  1111. ConMsg( "Warning: Can't add NULL string to table %s\n", GetTableName() );
  1112. return INVALID_STRING_INDEX;
  1113. }
  1114. #ifdef _DEBUG
  1115. if ( m_bLocked )
  1116. {
  1117. DevMsg("Warning! CNetworkStringTable::AddString: adding '%s' while locked.\n", string );
  1118. }
  1119. #endif
  1120. int i = m_pItems->Find( string );
  1121. if ( !bIsServer )
  1122. {
  1123. if ( m_pItems->IsValidIndex( i ) && !m_pItemsClientSide )
  1124. {
  1125. bIsServer = true;
  1126. }
  1127. else if ( !m_pItemsClientSide )
  1128. {
  1129. // NOTE: YWB 9/11/2008
  1130. // This case is probably a bug the since the server "owns" the state of the string table and if the client adds
  1131. // some extra value in and then the server comes along and uses that slot, then all hell breaks loose (probably).
  1132. // SetAllowClientSideAddString was added to allow for client side only precaching to be in a separate chunk of the table -- it should be used in this case.
  1133. // TODO: Make this a Sys_Error?
  1134. DevMsg( "CNetworkStringTable::AddString: client added string which server didn't put into table (consider SetAllowClientSideAddString?): %s %s\n", GetTableName(), string );
  1135. }
  1136. }
  1137. if ( !bIsServer && m_pItemsClientSide )
  1138. {
  1139. i = m_pItemsClientSide->Find( string );
  1140. if ( !m_pItemsClientSide->IsValidIndex( i ) )
  1141. {
  1142. // not in list yet, create it now
  1143. if ( m_pItemsClientSide->Count() >= (unsigned int)GetMaxStrings() )
  1144. {
  1145. // Too many strings, FIXME: Print warning message
  1146. ConMsg( "Warning: Table %s is full, can't add %s\n", GetTableName(), string );
  1147. return INVALID_STRING_INDEX;
  1148. }
  1149. // create new item
  1150. {
  1151. MEM_ALLOC_CREDIT();
  1152. i = m_pItemsClientSide->Insert( string );
  1153. }
  1154. item = &m_pItemsClientSide->Element( i );
  1155. // set changed ticks
  1156. item->m_nTickChanged = m_nTickCount;
  1157. #ifndef SHARED_NET_STRING_TABLES
  1158. item->m_nTickCreated = m_nTickCount;
  1159. if ( m_bChangeHistoryEnabled )
  1160. {
  1161. item->EnableChangeHistory();
  1162. }
  1163. #endif
  1164. bHasChanged = true;
  1165. }
  1166. else
  1167. {
  1168. item = &m_pItemsClientSide->Element( i ); // item already exists
  1169. bHasChanged = false; // not changed yet
  1170. }
  1171. if ( length > -1 )
  1172. {
  1173. if ( item->SetUserData( m_nTickCount, length, userdata ) )
  1174. {
  1175. bHasChanged = true;
  1176. }
  1177. }
  1178. if ( bHasChanged && !m_bChangeHistoryEnabled )
  1179. {
  1180. DataChanged( -i, item );
  1181. }
  1182. // Negate i for returning to client
  1183. i = -i;
  1184. }
  1185. else
  1186. {
  1187. // See if it's already there
  1188. if ( !m_pItems->IsValidIndex( i ) )
  1189. {
  1190. // not in list yet, create it now
  1191. if ( m_pItems->Count() >= (unsigned int)GetMaxStrings() )
  1192. {
  1193. // Too many strings, FIXME: Print warning message
  1194. ConMsg( "Warning: Table %s is full, can't add %s\n", GetTableName(), string );
  1195. return INVALID_STRING_INDEX;
  1196. }
  1197. // create new item
  1198. {
  1199. MEM_ALLOC_CREDIT();
  1200. i = m_pItems->Insert( string );
  1201. }
  1202. item = &m_pItems->Element( i );
  1203. // set changed ticks
  1204. item->m_nTickChanged = m_nTickCount;
  1205. #ifndef SHARED_NET_STRING_TABLES
  1206. item->m_nTickCreated = m_nTickCount;
  1207. if ( m_bChangeHistoryEnabled )
  1208. {
  1209. item->EnableChangeHistory();
  1210. }
  1211. #endif
  1212. bHasChanged = true;
  1213. }
  1214. else
  1215. {
  1216. item = &m_pItems->Element( i ); // item already exists
  1217. bHasChanged = false; // not changed yet
  1218. }
  1219. if ( length > -1 )
  1220. {
  1221. if ( item->SetUserData( m_nTickCount, length, userdata ) )
  1222. {
  1223. bHasChanged = true;
  1224. }
  1225. }
  1226. if ( bHasChanged && !m_bChangeHistoryEnabled )
  1227. {
  1228. DataChanged( i, item );
  1229. }
  1230. }
  1231. return i;
  1232. }
  1233. //-----------------------------------------------------------------------------
  1234. // Purpose:
  1235. // Input : stringNumber -
  1236. // Output : const char
  1237. //-----------------------------------------------------------------------------
  1238. const char *CNetworkStringTable::GetString( int stringNumber ) const
  1239. {
  1240. INetworkStringDict *dict = m_pItems;
  1241. if ( m_pItemsClientSide && stringNumber < -1 )
  1242. {
  1243. dict = m_pItemsClientSide;
  1244. stringNumber = -stringNumber;
  1245. }
  1246. Assert( dict->IsValidIndex( stringNumber ) );
  1247. if ( dict->IsValidIndex( stringNumber ) )
  1248. {
  1249. return dict->String( stringNumber );
  1250. }
  1251. return NULL;
  1252. }
  1253. //-----------------------------------------------------------------------------
  1254. // Purpose:
  1255. // Input : stringNumber -
  1256. // length -
  1257. // *userdata -
  1258. //-----------------------------------------------------------------------------
  1259. void CNetworkStringTable::SetStringUserData( int stringNumber, int length /*=0*/, const void *userdata /*= 0*/ )
  1260. {
  1261. #ifdef _DEBUG
  1262. if ( m_bLocked )
  1263. {
  1264. DevMsg("Warning! CNetworkStringTable::SetStringUserData: changing entry %i while locked.\n", stringNumber );
  1265. }
  1266. #endif
  1267. INetworkStringDict *dict = m_pItems;
  1268. int saveStringNumber = stringNumber;
  1269. if ( m_pItemsClientSide && stringNumber < -1 )
  1270. {
  1271. dict = m_pItemsClientSide;
  1272. stringNumber = -stringNumber;
  1273. }
  1274. Assert( (length == 0 && userdata == NULL) || ( length > 0 && userdata != NULL) );
  1275. Assert( dict->IsValidIndex( stringNumber ) );
  1276. CNetworkStringTableItem *p = &dict->Element( stringNumber );
  1277. Assert( p );
  1278. if ( p->SetUserData( m_nTickCount, length, userdata ) )
  1279. {
  1280. // Mark changed
  1281. DataChanged( saveStringNumber, p );
  1282. }
  1283. }
  1284. //-----------------------------------------------------------------------------
  1285. // Purpose:
  1286. // Input : *item -
  1287. //-----------------------------------------------------------------------------
  1288. void CNetworkStringTable::DataChanged( int stringNumber, CNetworkStringTableItem *item )
  1289. {
  1290. Assert( item );
  1291. if ( !item )
  1292. return;
  1293. // Mark table as changed
  1294. m_nLastChangedTick = m_nTickCount;
  1295. // Invoke callback if one was installed
  1296. #ifndef SHARED_NET_STRING_TABLES // but not if client & server share the same containers, we trigger that later
  1297. if ( m_changeFunc != NULL )
  1298. {
  1299. int userDataSize;
  1300. const void *pUserData = item->GetUserData( &userDataSize );
  1301. ( *m_changeFunc )( m_pObject, this, stringNumber, GetString( stringNumber ), pUserData );
  1302. }
  1303. #endif
  1304. }
  1305. #ifndef SHARED_NET_STRING_TABLES
  1306. void CNetworkStringTable::WriteStringTable( bf_write& buf )
  1307. {
  1308. int numstrings = m_pItems->Count();
  1309. buf.WriteWord( numstrings );
  1310. for ( int i = 0 ; i < numstrings; i++ )
  1311. {
  1312. buf.WriteString( GetString( i ) );
  1313. int userDataSize;
  1314. const void *pUserData = GetStringUserData( i, &userDataSize );
  1315. if ( userDataSize > 0 )
  1316. {
  1317. buf.WriteOneBit( 1 );
  1318. buf.WriteWord( (short)userDataSize );
  1319. buf.WriteBytes( pUserData, userDataSize );
  1320. }
  1321. else
  1322. {
  1323. buf.WriteOneBit( 0 );
  1324. }
  1325. }
  1326. if ( m_pItemsClientSide )
  1327. {
  1328. buf.WriteOneBit( 1 );
  1329. numstrings = m_pItemsClientSide->Count();
  1330. buf.WriteWord( numstrings );
  1331. for ( int i = 0 ; i < numstrings; i++ )
  1332. {
  1333. buf.WriteString( m_pItemsClientSide->String( i ) );
  1334. int userDataSize;
  1335. const void *pUserData = m_pItemsClientSide->Element( i ).GetUserData( &userDataSize );
  1336. if ( userDataSize > 0 )
  1337. {
  1338. buf.WriteOneBit( 1 );
  1339. buf.WriteWord( (short)userDataSize );
  1340. buf.WriteBytes( pUserData, userDataSize );
  1341. }
  1342. else
  1343. {
  1344. buf.WriteOneBit( 0 );
  1345. }
  1346. }
  1347. }
  1348. else
  1349. {
  1350. buf.WriteOneBit( 0 );
  1351. }
  1352. }
  1353. bool CNetworkStringTable::ReadStringTable( bf_read& buf )
  1354. {
  1355. DeleteAllStrings();
  1356. int numstrings = buf.ReadWord();
  1357. for ( int i = 0 ; i < numstrings; i++ )
  1358. {
  1359. char stringname[4096];
  1360. buf.ReadString( stringname, sizeof( stringname ) );
  1361. Assert( V_strlen( stringname ) < 100 );
  1362. if ( buf.ReadOneBit() == 1 )
  1363. {
  1364. int userDataSize = (int)buf.ReadWord();
  1365. Assert( userDataSize > 0 );
  1366. byte *data = new byte[ userDataSize + 4 ];
  1367. Assert( data );
  1368. buf.ReadBytes( data, userDataSize );
  1369. AddString( true, stringname, userDataSize, data );
  1370. delete[] data;
  1371. Assert( buf.GetNumBytesLeft() > 10 );
  1372. }
  1373. else
  1374. {
  1375. AddString( true, stringname );
  1376. }
  1377. }
  1378. // Client side stuff
  1379. if ( buf.ReadOneBit() == 1 )
  1380. {
  1381. int numstrings = buf.ReadWord();
  1382. for ( int i = 0 ; i < numstrings; i++ )
  1383. {
  1384. char stringname[4096];
  1385. buf.ReadString( stringname, sizeof( stringname ) );
  1386. if ( buf.ReadOneBit() == 1 )
  1387. {
  1388. int userDataSize = (int)buf.ReadWord();
  1389. Assert( userDataSize > 0 );
  1390. byte *data = new byte[ userDataSize + 4 ];
  1391. Assert( data );
  1392. buf.ReadBytes( data, userDataSize );
  1393. if ( i >= 2 )
  1394. {
  1395. AddString( false, stringname, userDataSize, data );
  1396. }
  1397. delete[] data;
  1398. }
  1399. else
  1400. {
  1401. if ( i >= 2 )
  1402. {
  1403. AddString( false, stringname );
  1404. }
  1405. }
  1406. }
  1407. }
  1408. return true;
  1409. }
  1410. #endif
  1411. //-----------------------------------------------------------------------------
  1412. // Purpose:
  1413. // Input : stringNumber -
  1414. // length -
  1415. // Output : const void
  1416. //-----------------------------------------------------------------------------
  1417. const void *CNetworkStringTable::GetStringUserData( int stringNumber, int *length ) const
  1418. {
  1419. INetworkStringDict *dict = m_pItems;
  1420. if ( m_pItemsClientSide && stringNumber < -1 )
  1421. {
  1422. dict = m_pItemsClientSide;
  1423. stringNumber = -stringNumber;
  1424. }
  1425. CNetworkStringTableItem *p;
  1426. Assert( dict->IsValidIndex( stringNumber ) );
  1427. p = &dict->Element( stringNumber );
  1428. Assert( p );
  1429. return p->GetUserData( length );
  1430. }
  1431. //-----------------------------------------------------------------------------
  1432. // Purpose:
  1433. // Output : int
  1434. //-----------------------------------------------------------------------------
  1435. int CNetworkStringTable::GetNumStrings( void ) const
  1436. {
  1437. return m_pItems->Count();
  1438. }
  1439. //-----------------------------------------------------------------------------
  1440. // Purpose:
  1441. // Input : stringTable -
  1442. // *string -
  1443. // Output : int
  1444. //-----------------------------------------------------------------------------
  1445. int CNetworkStringTable::FindStringIndex( char const *string )
  1446. {
  1447. int i = m_pItems->Find( string );
  1448. if ( m_pItems->IsValidIndex( i ) )
  1449. return i;
  1450. if ( m_pItemsClientSide )
  1451. {
  1452. i = m_pItemsClientSide->Find( string );
  1453. if ( m_pItemsClientSide->IsValidIndex( i ) )
  1454. return -i;
  1455. }
  1456. return INVALID_STRING_INDEX;
  1457. }
  1458. void CNetworkStringTable::UpdateDictionaryString( int stringNumber )
  1459. {
  1460. if ( !IsUsingDictionary() )
  1461. {
  1462. return;
  1463. }
  1464. // Client side only items don't need to deal with dictionary
  1465. if ( m_pItemsClientSide && stringNumber < -1 )
  1466. {
  1467. return;
  1468. }
  1469. CheckDictionary( stringNumber );
  1470. }
  1471. //-----------------------------------------------------------------------------
  1472. // Purpose:
  1473. //-----------------------------------------------------------------------------
  1474. void CNetworkStringTable::Dump( void )
  1475. {
  1476. ConMsg( "Table %s\n", GetTableName() );
  1477. ConMsg( " %i/%i items\n", GetNumStrings(), GetMaxStrings() );
  1478. for ( int i = 0; i < GetNumStrings() ; i++ )
  1479. {
  1480. if ( IsUsingDictionary() )
  1481. {
  1482. int nCurrentDictionaryIndex = m_pItems->DictionaryIndex( i );
  1483. if ( nCurrentDictionaryIndex != -1 )
  1484. {
  1485. ConMsg( "d(%05d) %i : %s\n", nCurrentDictionaryIndex, i, m_pItems->String( i ) );
  1486. }
  1487. else
  1488. {
  1489. ConMsg( " %i : %s\n", i, m_pItems->String( i ) );
  1490. }
  1491. }
  1492. else
  1493. {
  1494. ConMsg( " %i : %s\n", i, m_pItems->String( i ) );
  1495. }
  1496. }
  1497. if ( m_pItemsClientSide )
  1498. {
  1499. for ( int i = 0; i < (int)m_pItemsClientSide->Count() ; i++ )
  1500. {
  1501. ConMsg( " (c)%i : %s\n", i, m_pItemsClientSide->String( i ) );
  1502. }
  1503. }
  1504. ConMsg( "\n" );
  1505. }
  1506. #ifndef SHARED_NET_STRING_TABLES
  1507. static ConVar sv_temp_baseline_string_table_buffer_size( "sv_temp_baseline_string_table_buffer_size", "131072", 0, "Buffer size for writing string table baselines" );
  1508. bool CNetworkStringTable::WriteBaselines( CSVCMsg_CreateStringTable_t &msg )
  1509. {
  1510. msg.Clear();
  1511. // allocate the temp buffer for the packet ents
  1512. msg.mutable_string_data()->resize( sv_temp_baseline_string_table_buffer_size.GetInt() );
  1513. bf_write string_data_buf( &(*msg.mutable_string_data())[0], msg.string_data().size() );
  1514. msg.set_flags( m_nFlags );
  1515. msg.set_name( GetTableName() );
  1516. msg.set_max_entries( GetMaxStrings() );
  1517. msg.set_num_entries( GetNumStrings() );
  1518. msg.set_user_data_fixed_size( IsUserDataFixedSize() );
  1519. msg.set_user_data_size( GetUserDataSize() );
  1520. msg.set_user_data_size_bits( GetUserDataSizeBits() );
  1521. // tick = -1 ensures that all entries are updated = baseline
  1522. int entries = WriteUpdate( NULL, string_data_buf, -1 );
  1523. // resize the buffer to the actual byte size
  1524. msg.mutable_string_data()->resize( Bits2Bytes( string_data_buf.GetNumBitsWritten() ) );
  1525. return entries == msg.num_entries();
  1526. }
  1527. #endif
  1528. //-----------------------------------------------------------------------------
  1529. // Purpose:
  1530. //-----------------------------------------------------------------------------
  1531. CNetworkStringTableContainer::CNetworkStringTableContainer( void )
  1532. {
  1533. m_bAllowCreation = false;
  1534. m_bLocked = true;
  1535. m_nTickCount = 0;
  1536. m_bEnableRollback = false;
  1537. }
  1538. //-----------------------------------------------------------------------------
  1539. // Purpose:
  1540. //-----------------------------------------------------------------------------
  1541. CNetworkStringTableContainer::~CNetworkStringTableContainer( void )
  1542. {
  1543. RemoveAllTables();
  1544. }
  1545. //-----------------------------------------------------------------------------
  1546. // Purpose:
  1547. //-----------------------------------------------------------------------------
  1548. void CNetworkStringTableContainer::AllowCreation( bool state )
  1549. {
  1550. m_bAllowCreation = state;
  1551. }
  1552. bool CNetworkStringTableContainer::Lock( bool bLock )
  1553. {
  1554. bool oldLock = m_bLocked;
  1555. m_bLocked = bLock;
  1556. // Determine if an update is needed
  1557. for ( int i = 0; i < m_Tables.Count(); i++ )
  1558. {
  1559. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1560. table->Lock( bLock );
  1561. }
  1562. return oldLock;
  1563. }
  1564. void CNetworkStringTableContainer::SetAllowClientSideAddString( INetworkStringTable *table, bool bAllowClientSideAddString )
  1565. {
  1566. for ( int i = 0; i < m_Tables.Count(); i++ )
  1567. {
  1568. CNetworkStringTable *t = (CNetworkStringTable*) GetTable( i );
  1569. if ( t == table )
  1570. {
  1571. t->SetAllowClientSideAddString( bAllowClientSideAddString );
  1572. return;
  1573. }
  1574. }
  1575. }
  1576. //-----------------------------------------------------------------------------
  1577. // Purpose:
  1578. // Input : *tableName -
  1579. // maxentries -
  1580. // Output : TABLEID
  1581. //-----------------------------------------------------------------------------
  1582. INetworkStringTable *CNetworkStringTableContainer::CreateStringTable( const char *tableName, int maxentries, int userdatafixedsize /*= 0*/, int userdatanetworkbits /*= 0*/, int flags /*= NSF_NONE*/ )
  1583. {
  1584. if ( !m_bAllowCreation )
  1585. {
  1586. Sys_Error( "Tried to create string table '%s' at wrong time\n", tableName );
  1587. return NULL;
  1588. }
  1589. CNetworkStringTable *pTable = (CNetworkStringTable*) FindTable( tableName );
  1590. if ( pTable != NULL )
  1591. {
  1592. Sys_Error( "Tried to create string table '%s' twice\n", tableName );
  1593. return NULL;
  1594. }
  1595. if ( m_Tables.Count() >= MAX_TABLES )
  1596. {
  1597. Sys_Error( "Only %i string tables allowed, can't create'%s'", MAX_TABLES, tableName);
  1598. return NULL;
  1599. }
  1600. TABLEID id = m_Tables.Count();
  1601. pTable = new CNetworkStringTable( id, tableName, maxentries, userdatafixedsize, userdatanetworkbits, flags );
  1602. Assert( pTable );
  1603. #ifndef SHARED_NET_STRING_TABLES
  1604. if ( m_bEnableRollback )
  1605. {
  1606. pTable->EnableRollback();
  1607. }
  1608. #endif
  1609. pTable->SetTick( m_nTickCount );
  1610. m_Tables.AddToTail( pTable );
  1611. return pTable;
  1612. }
  1613. //-----------------------------------------------------------------------------
  1614. // Purpose:
  1615. // Input : *tableName -
  1616. //-----------------------------------------------------------------------------
  1617. INetworkStringTable *CNetworkStringTableContainer::FindTable( const char *tableName ) const
  1618. {
  1619. for ( int i = 0; i < m_Tables.Count(); i++ )
  1620. {
  1621. if ( !Q_stricmp( tableName, m_Tables[ i ]->GetTableName() ) )
  1622. return m_Tables[i];
  1623. }
  1624. return NULL;
  1625. }
  1626. //-----------------------------------------------------------------------------
  1627. // Purpose:
  1628. // Input : stringTable -
  1629. // Output : CNetworkStringTableServer
  1630. //-----------------------------------------------------------------------------
  1631. INetworkStringTable *CNetworkStringTableContainer::GetTable( TABLEID stringTable ) const
  1632. {
  1633. if ( stringTable < 0 || stringTable >= m_Tables.Count() )
  1634. return NULL;
  1635. return m_Tables[ stringTable ];
  1636. }
  1637. int CNetworkStringTableContainer::GetNumTables( void ) const
  1638. {
  1639. return m_Tables.Count();
  1640. }
  1641. #ifndef SHARED_NET_STRING_TABLES
  1642. //-----------------------------------------------------------------------------
  1643. // Purpose:
  1644. //-----------------------------------------------------------------------------
  1645. void CNetworkStringTableContainer::WriteBaselines( char const *pchMapName, bf_write &buf )
  1646. {
  1647. if ( g_StringTableDictionary.ShouldRecreateDictionary( pchMapName ) )
  1648. {
  1649. // Creates dictionary, will write it out after level is exited
  1650. CreateDictionary( pchMapName );
  1651. }
  1652. CSVCMsg_CreateStringTable_t msg;
  1653. for ( int i = 0 ; i < m_Tables.Count() ; i++ )
  1654. {
  1655. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1656. int before = buf.GetNumBytesWritten();
  1657. if ( !table->WriteBaselines( msg ) )
  1658. {
  1659. Host_Error( "Index error writing string table baseline %s\n", table->GetTableName() );
  1660. }
  1661. if ( !msg.WriteToBuffer( buf ) )
  1662. {
  1663. Host_Error( "Overflow error writing string table baseline %s\n", table->GetTableName() );
  1664. }
  1665. int after = buf.GetNumBytesWritten();
  1666. if ( sv_dumpstringtables.GetBool() )
  1667. {
  1668. DevMsg( "CNetworkStringTableContainer::WriteBaselines wrote %d bytes for table %s [space remaining %d bytes]\n", after - before, table->GetTableName(), buf.GetNumBytesLeft() );
  1669. }
  1670. }
  1671. }
  1672. void CNetworkStringTableContainer::WriteStringTables( bf_write& buf )
  1673. {
  1674. int numTables = m_Tables.Count();
  1675. buf.WriteByte( numTables );
  1676. for ( int i = 0; i < numTables; i++ )
  1677. {
  1678. CNetworkStringTable *table = m_Tables[ i ];
  1679. buf.WriteString( table->GetTableName() );
  1680. table->WriteStringTable( buf );
  1681. }
  1682. }
  1683. bool CNetworkStringTableContainer::ReadStringTables( bf_read& buf )
  1684. {
  1685. int numTables = buf.ReadByte();
  1686. for ( int i = 0 ; i < numTables; i++ )
  1687. {
  1688. char tablename[ 256 ];
  1689. buf.ReadString( tablename, sizeof( tablename ) );
  1690. // Find this table by name
  1691. CNetworkStringTable *table = (CNetworkStringTable*)FindTable( tablename );
  1692. Assert( table );
  1693. // Now read the data for the table
  1694. if ( !table->ReadStringTable( buf ) )
  1695. {
  1696. Host_Error( "Error reading string table %s\n", tablename );
  1697. }
  1698. }
  1699. return true;
  1700. }
  1701. //-----------------------------------------------------------------------------
  1702. // Purpose:
  1703. // Input : *cl -
  1704. // *msg -
  1705. //-----------------------------------------------------------------------------
  1706. void CNetworkStringTableContainer::WriteUpdateMessage( CBaseClient *client, int tick_ack, bf_write &buf )
  1707. {
  1708. VPROF_BUDGET( "CNetworkStringTableContainer::WriteUpdateMessage", VPROF_BUDGETGROUP_OTHER_NETWORKING );
  1709. //a working buffer to build our string tables into. Note that this is on the stack, so sanity check that the size doesn't get too large (hence the compile assert below).
  1710. //If it does, a heap allocated solution will be needed
  1711. uint8 StringTableBuff[ NET_MAX_PAYLOAD ];
  1712. COMPILE_TIME_ASSERT( sizeof( StringTableBuff ) < 300 * 1024 );
  1713. // Determine if an update is needed
  1714. for ( int i = 0; i < m_Tables.Count(); i++ )
  1715. {
  1716. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1717. if ( !table )
  1718. continue;
  1719. if ( !table->ChangedSinceTick( tick_ack ) )
  1720. continue;
  1721. CSVCMsg_UpdateStringTable_t msg;
  1722. //setup a writer for the bits that go to our temporary buffer so we can assign it over later
  1723. bf_write string_data_buf( StringTableBuff, sizeof( StringTableBuff ) );
  1724. msg.set_table_id( table->GetTableId() );
  1725. msg.set_num_changed_entries( table->WriteUpdate( client, string_data_buf, tick_ack ) );
  1726. //handle the situation where the data may have been truncated
  1727. if( string_data_buf.IsOverflowed() )
  1728. return;
  1729. Assert( msg.num_changed_entries() > 0 ); // don't send unnecessary empty updates
  1730. //copy over the data we wrote into the actual message
  1731. msg.mutable_string_data()->assign( StringTableBuff, StringTableBuff + Bits2Bytes( string_data_buf.GetNumBitsWritten() ) );
  1732. if ( !msg.WriteToBuffer( buf ) )
  1733. return;
  1734. if ( client &&
  1735. client->IsTracing() )
  1736. {
  1737. client->TraceNetworkData( buf, "StringTable %s", table->GetTableName() );
  1738. }
  1739. }
  1740. }
  1741. //-----------------------------------------------------------------------------
  1742. // Purpose:
  1743. // Input : *cl -
  1744. // *msg -
  1745. //-----------------------------------------------------------------------------
  1746. void CNetworkStringTableContainer::DirectUpdate( int tick_ack )
  1747. {
  1748. VPROF_BUDGET( "CNetworkStringTableContainer::DirectUpdate", VPROF_BUDGETGROUP_OTHER_NETWORKING );
  1749. // Determine if an update is needed
  1750. for ( int i = 0; i < m_Tables.Count(); i++ )
  1751. {
  1752. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1753. Assert( table );
  1754. if ( !table->ChangedSinceTick( tick_ack ) )
  1755. continue;
  1756. table->UpdateMirrorTable( tick_ack );
  1757. }
  1758. }
  1759. void CNetworkStringTableContainer::EnableRollback( bool bState )
  1760. {
  1761. // we can't dis/enable rollback if we already created tabled
  1762. Assert( m_Tables.Count() == 0 );
  1763. m_bEnableRollback = bState;
  1764. }
  1765. void CNetworkStringTableContainer::RestoreTick( int tick )
  1766. {
  1767. for ( int i = 0; i < m_Tables.Count(); i++ )
  1768. {
  1769. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1770. Assert( table );
  1771. table->RestoreTick( tick );
  1772. }
  1773. }
  1774. #endif
  1775. void CNetworkStringTableContainer::TriggerCallbacks( int tick_ack )
  1776. {
  1777. // Determine if an update is needed
  1778. for ( int i = 0; i < m_Tables.Count(); i++ )
  1779. {
  1780. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1781. Assert( table );
  1782. if ( !table->ChangedSinceTick( tick_ack ) )
  1783. continue;
  1784. table->TriggerCallbacks( tick_ack );
  1785. }
  1786. }
  1787. void CNetworkStringTableContainer::SetTick( int tick_count)
  1788. {
  1789. // Assert( tick_count > 0 );
  1790. m_nTickCount = tick_count;
  1791. // Determine if an update is needed
  1792. for ( int i = 0; i < m_Tables.Count(); i++ )
  1793. {
  1794. CNetworkStringTable *table = (CNetworkStringTable*) GetTable( i );
  1795. Assert( table );
  1796. table->SetTick( tick_count );
  1797. }
  1798. }
  1799. //-----------------------------------------------------------------------------
  1800. // Purpose:
  1801. //-----------------------------------------------------------------------------
  1802. void CNetworkStringTableContainer::RemoveAllTables( void )
  1803. {
  1804. while ( m_Tables.Count() > 0 )
  1805. {
  1806. CNetworkStringTable *table = m_Tables[ 0 ];
  1807. m_Tables.Remove( 0 );
  1808. delete table;
  1809. }
  1810. }
  1811. //-----------------------------------------------------------------------------
  1812. // Purpose:
  1813. //-----------------------------------------------------------------------------
  1814. void CNetworkStringTableContainer::Dump( void )
  1815. {
  1816. for ( int i = 0; i < m_Tables.Count(); i++ )
  1817. {
  1818. m_Tables[ i ]->Dump();
  1819. }
  1820. }
  1821. void CNetworkStringTableContainer::CreateDictionary( char const *pchMapName )
  1822. {
  1823. // Don't do this on Game Consoles!!!
  1824. if ( IsGameConsole() )
  1825. {
  1826. Warning( "Map %s missing GameConsole stringtable dictionary!!!\n", pchMapName );
  1827. return;
  1828. }
  1829. char mapPath[ MAX_PATH ];
  1830. Q_snprintf( mapPath, sizeof( mapPath ), "maps/%s.bsp", pchMapName );
  1831. // Make sure that the file is writable before building stringtable dictionary.
  1832. if( !g_pFileSystem->IsFileWritable( mapPath, "GAME" ) )
  1833. {
  1834. Warning( "#####################################################################################\n" );
  1835. Warning( "Can't recreate dictionary for %s, file must be writable!!!\n", mapPath );
  1836. Warning( "#####################################################################################\n" );
  1837. return;
  1838. }
  1839. Msg( "Creating dictionary %s\n", pchMapName );
  1840. // Create dictionary
  1841. CUtlBuffer buf;
  1842. for ( int i = 0; i < m_Tables.Count(); ++i )
  1843. {
  1844. CNetworkStringTable *table = m_Tables[ i ];
  1845. if ( !table->IsUsingDictionary() )
  1846. continue;
  1847. int nNumStrings = table->GetNumStrings();
  1848. for ( int j = 0; j < nNumStrings; ++j )
  1849. {
  1850. char const *str = table->GetString( j );
  1851. // Skip empty strings (slot 0 is sometimes encoded as "")
  1852. if ( !*str )
  1853. continue;
  1854. buf.PutString( str );
  1855. }
  1856. }
  1857. g_StringTableDictionary.CacheNewStringTableForWriteToBSPOnLevelShutdown( pchMapName, buf, MapReslistGenerator().IsCreatingForXbox() );
  1858. }
  1859. void CNetworkStringTableContainer::UpdateDictionaryStrings()
  1860. {
  1861. for ( int i = 0; i < m_Tables.Count(); ++i )
  1862. {
  1863. CNetworkStringTable *table = m_Tables[ i ];
  1864. if ( !table->IsUsingDictionary() )
  1865. continue;
  1866. int nNumStrings = table->GetNumStrings();
  1867. for ( int j = 0; j < nNumStrings; ++j )
  1868. {
  1869. table->UpdateDictionaryString( j );
  1870. }
  1871. }
  1872. }