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.

828 lines
26 KiB

  1. //====== Copyright � 1996-2004, Valve Corporation, All rights reserved. =======
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "dmxloader/dmxelement.h"
  7. #include "tier1/utlbuffer.h"
  8. #include "tier2/tier2.h"
  9. #include "tier2/utlstreambuffer.h"
  10. #include "filesystem.h"
  11. #include "datamodel/dmxheader.h"
  12. #include "dmxserializationdictionary.h"
  13. #include "tier1/memstack.h"
  14. #include "tier1/utldict.h"
  15. #define DMX_BINARY_VER_STRINGTABLE 2
  16. #define DMX_BINARY_VER_GLOBAL_STRINGTABLE 4
  17. #define DMX_BINARY_VER_STRINGTABLE_LARGESYMBOLS 5
  18. #define CURRENT_BINARY_ENCODING 5
  19. //-----------------------------------------------------------------------------
  20. // DMX elements/attributes can only be accessed inside a dmx context
  21. //-----------------------------------------------------------------------------
  22. static int s_bInDMXContext;
  23. CMemoryStack s_DMXAllocator;
  24. static bool s_bAllocatorInitialized;
  25. void BeginDMXContext( )
  26. {
  27. Assert( !s_bInDMXContext );
  28. if ( !s_bAllocatorInitialized )
  29. {
  30. #ifdef PLATFORM_64BITS
  31. const int k_nStackSize = 4 * 1024 * 1024;
  32. #else
  33. const int k_nStackSize = 2 * 1024 * 1024;
  34. #endif
  35. s_DMXAllocator.Init( "DMXAlloc", k_nStackSize, 0, 0, 4 );
  36. s_bAllocatorInitialized = true;
  37. }
  38. s_bInDMXContext = true;
  39. }
  40. void EndDMXContext( bool bDecommitMemory )
  41. {
  42. Assert( s_bInDMXContext );
  43. s_bInDMXContext = false;
  44. s_DMXAllocator.FreeAll( bDecommitMemory );
  45. }
  46. void DecommitDMXMemory()
  47. {
  48. s_DMXAllocator.FreeAll( true );
  49. }
  50. //-----------------------------------------------------------------------------
  51. // Used for allocation. All will be freed when we leave the DMX context
  52. //-----------------------------------------------------------------------------
  53. void* DMXAlloc( size_t size )
  54. {
  55. Assert( s_bInDMXContext );
  56. if ( !s_bInDMXContext )
  57. return 0;
  58. MEM_ALLOC_CREDIT_( "DMXAlloc" );
  59. return s_DMXAllocator.Alloc( size, false );
  60. }
  61. //-----------------------------------------------------------------------------
  62. // Forward declarations
  63. //-----------------------------------------------------------------------------
  64. bool UnserializeTextDMX( const char *pFileName, CUtlBuffer &buf, CDmxElement **ppRoot );
  65. bool SerializeTextDMX( const char *pFileName, CUtlBuffer &buf, CDmxElement *pRoot );
  66. //-----------------------------------------------------------------------------
  67. // special element indices
  68. //-----------------------------------------------------------------------------
  69. enum
  70. {
  71. ELEMENT_INDEX_NULL = -1,
  72. ELEMENT_INDEX_EXTERNAL = -2,
  73. };
  74. //-----------------------------------------------------------------------------
  75. // Serialization class for Binary output
  76. //-----------------------------------------------------------------------------
  77. class CDmxSerializer
  78. {
  79. public:
  80. bool Unserialize( CUtlBuffer &buf, int nEncodingVersion, CDmxElement **ppRoot );
  81. bool Serialize( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName );
  82. private:
  83. // For serialize
  84. typedef CUtlDict< int, int > mapSymbolToIndex_t;
  85. // Methods related to serialization
  86. bool ShouldWriteAttribute( const char *pAttributeName, CDmxAttribute *pAttribute );
  87. void SerializeElementIndex( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxElement *pElement );
  88. void SerializeElementAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute );
  89. void SerializeElementArrayAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute );
  90. bool SaveElementDict( CUtlBuffer& buf, mapSymbolToIndex_t *pStringValueSymbols, CDmxElement *pElement );
  91. bool SaveElement( CUtlBuffer& buf, CDmxSerializationDictionary& dict, mapSymbolToIndex_t *pStringValueSymbols, CDmxElement *pElement);
  92. void GatherSymbols( CUtlSymbolTableLarge *pStringValueSymbols, CDmxElement *pElement );
  93. // Methods related to unserialization
  94. CDmxElement* UnserializeElementIndex( CUtlBuffer &buf, CUtlVector<CDmxElement*> &elementList );
  95. void UnserializeElementAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList );
  96. void UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList );
  97. bool UnserializeAttributes( CUtlBuffer &buf, CDmxElement *pElement, CUtlVector<CDmxElement*> &elementList, int nStrings, int *offsetTable, char *stringTable, int nEncodingVersion );
  98. int GetStringOffsetTable( CUtlBuffer &buf, int *offsetTable, int nStrings );
  99. inline char const *Dme_GetStringFromBuffer( CUtlBuffer &buf, bool bUseLargeSymbols, int nStrings, int *offsetTable, char *stringTable )
  100. {
  101. int uSym = ( bUseLargeSymbols ) ? buf.GetInt() : buf.GetShort();
  102. if ( uSym >= nStrings )
  103. return NULL;
  104. return stringTable + offsetTable[ uSym ];
  105. }
  106. };
  107. //-----------------------------------------------------------------------------
  108. // Should we write out the attribute?
  109. //-----------------------------------------------------------------------------
  110. bool CDmxSerializer::ShouldWriteAttribute( const char *pAttributeName, CDmxAttribute *pAttribute )
  111. {
  112. if ( !pAttribute )
  113. return false;
  114. // These are already written in the initial element dictionary
  115. if ( !Q_stricmp( pAttributeName, "name" ) )
  116. return false;
  117. return true;
  118. }
  119. //-----------------------------------------------------------------------------
  120. // Write out the index of the element to avoid looks at read time
  121. //-----------------------------------------------------------------------------
  122. void CDmxSerializer::SerializeElementIndex( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxElement *pElement )
  123. {
  124. if ( !pElement )
  125. {
  126. buf.PutInt( ELEMENT_INDEX_NULL ); // invalid handle
  127. return;
  128. }
  129. buf.PutInt( list.Find( pElement ) );
  130. }
  131. //-----------------------------------------------------------------------------
  132. // Writes out element attributes
  133. //-----------------------------------------------------------------------------
  134. void CDmxSerializer::SerializeElementAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute )
  135. {
  136. SerializeElementIndex( buf, list, pAttribute->GetValue<CDmxElement*>() );
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Writes out element array attributes
  140. //-----------------------------------------------------------------------------
  141. void CDmxSerializer::SerializeElementArrayAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute )
  142. {
  143. const CUtlVector<CDmxElement*> &vec = pAttribute->GetArray<CDmxElement*>();
  144. int nCount = vec.Count();
  145. buf.PutInt( nCount );
  146. for ( int i = 0; i < nCount; ++i )
  147. {
  148. SerializeElementIndex( buf, list, vec[ i ] );
  149. }
  150. }
  151. //-----------------------------------------------------------------------------
  152. // Writes out all attributes
  153. //-----------------------------------------------------------------------------
  154. bool CDmxSerializer::SaveElement( CUtlBuffer& buf, CDmxSerializationDictionary& list, mapSymbolToIndex_t *pStringValueSymbols, CDmxElement *pElement )
  155. {
  156. int nAttributesToSave = 0;
  157. // Count the attributes...
  158. int nCount = pElement->AttributeCount();
  159. for ( int i = 0; i < nCount; ++i )
  160. {
  161. CDmxAttribute *pAttribute = pElement->GetAttribute( i );
  162. const char *pName = pAttribute->GetName( );
  163. if ( !ShouldWriteAttribute( pName, pAttribute ) )
  164. continue;
  165. ++nAttributesToSave;
  166. }
  167. // Now write them all out.
  168. buf.PutInt( nAttributesToSave );
  169. for ( int i = 0; i < nCount; ++i )
  170. {
  171. CDmxAttribute *pAttribute = pElement->GetAttribute( i );
  172. const char *pName = pAttribute->GetName();
  173. if ( !ShouldWriteAttribute( pName, pAttribute ) )
  174. continue;
  175. buf.PutInt( pStringValueSymbols->Find( pName ) );
  176. buf.PutChar( pAttribute->GetType() );
  177. switch( pAttribute->GetType() )
  178. {
  179. default:
  180. pAttribute->Serialize( buf );
  181. break;
  182. case AT_ELEMENT:
  183. SerializeElementAttribute( buf, list, pAttribute );
  184. break;
  185. case AT_ELEMENT_ARRAY:
  186. SerializeElementArrayAttribute( buf, list, pAttribute );
  187. break;
  188. case AT_STRING:
  189. {
  190. buf.PutInt( pStringValueSymbols->Find( pAttribute->GetValueString() ) );
  191. }
  192. break;
  193. }
  194. }
  195. return buf.IsValid();
  196. }
  197. bool CDmxSerializer::SaveElementDict( CUtlBuffer& buf, mapSymbolToIndex_t *pStringValueSymbols, CDmxElement *pElement )
  198. {
  199. buf.PutInt( pStringValueSymbols->Find( pElement->GetTypeString() ) );
  200. buf.PutInt( pStringValueSymbols->Find( pElement->GetName() ) );
  201. buf.Put( &pElement->GetId(), sizeof(DmObjectId_t) );
  202. return buf.IsValid();
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Main entry point for serialization
  206. //-----------------------------------------------------------------------------
  207. void CDmxSerializer::GatherSymbols( CUtlSymbolTableLarge *pStringValueSymbols, CDmxElement *pElement )
  208. {
  209. pStringValueSymbols->AddString( pElement->GetTypeString() );
  210. pStringValueSymbols->AddString( pElement->GetName() );
  211. int nAttributes = pElement->AttributeCount();
  212. for ( int ai = 0; ai < nAttributes; ++ai )
  213. {
  214. CDmxAttribute *pAttr = pElement->GetAttribute( ai );
  215. if ( !pAttr )
  216. continue;
  217. pStringValueSymbols->AddString( pAttr->GetName() );
  218. if ( pAttr->GetType() == AT_STRING )
  219. {
  220. pStringValueSymbols->AddString( pAttr->GetValueString() );
  221. }
  222. }
  223. }
  224. bool CDmxSerializer::Serialize( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName )
  225. {
  226. DmxSerializationHandle_t i;
  227. // Save elements, attribute links
  228. CDmxSerializationDictionary dict;
  229. dict.BuildElementList( pRoot, true );
  230. // collect string table
  231. CUtlSymbolTableLarge stringSymbols;
  232. for ( i = dict.FirstRootElement(); i != DMX_SERIALIZATION_HANDLE_INVALID; i = dict.NextRootElement(i) )
  233. {
  234. CDmxElement *pElement = dict.GetRootElement( i );
  235. if ( !pElement )
  236. return false;
  237. GatherSymbols( &stringSymbols, pElement );
  238. }
  239. // write out the symbol table for this file (may be significantly smaller than datamodel's full symbol table)
  240. int nSymbols = stringSymbols.GetNumStrings();
  241. buf.PutInt( nSymbols );
  242. CUtlVector< CUtlSymbolLarge > symbols;
  243. symbols.EnsureCount( nSymbols );
  244. stringSymbols.GetElements( 0, nSymbols, symbols.Base() );
  245. // It's case sensitive
  246. mapSymbolToIndex_t symbolToIndexMap( k_eDictCompareTypeCaseSensitive );
  247. for ( int si = 0; si < nSymbols; ++si )
  248. {
  249. CUtlSymbolLarge sym = symbols[ si ];
  250. const char *pStr = sym.String();
  251. symbolToIndexMap.Insert( pStr, si );
  252. buf.PutString( pStr );
  253. }
  254. // First write out the dictionary of all elements (to avoid later stitching up in unserialize)
  255. buf.PutInt( dict.RootElementCount() );
  256. for ( i = dict.FirstRootElement(); i != DMX_SERIALIZATION_HANDLE_INVALID; i = dict.NextRootElement(i) )
  257. {
  258. if ( !SaveElementDict( buf, &symbolToIndexMap, dict.GetRootElement( i ) ) )
  259. return false;
  260. }
  261. // Now write out the attributes of each of those elements
  262. for ( i = dict.FirstRootElement(); i != DMX_SERIALIZATION_HANDLE_INVALID; i = dict.NextRootElement(i) )
  263. {
  264. if ( !SaveElement( buf, dict, &symbolToIndexMap, dict.GetRootElement( i ) ) )
  265. return false;
  266. }
  267. return true;
  268. }
  269. //-----------------------------------------------------------------------------
  270. // Reads an element index and converts it to a handle (local or external)
  271. //-----------------------------------------------------------------------------
  272. CDmxElement* CDmxSerializer::UnserializeElementIndex( CUtlBuffer &buf, CUtlVector<CDmxElement*> &elementList )
  273. {
  274. int nElementIndex = buf.GetInt();
  275. if ( nElementIndex == ELEMENT_INDEX_EXTERNAL )
  276. {
  277. Warning( "Reading externally referenced elements is not supported!\n" );
  278. char idstr[ 40 ];
  279. buf.GetString( idstr, sizeof( idstr ) );
  280. // DmObjectId_t id;
  281. // UniqueIdFromString( &id, idstr, sizeof( idstr ) );
  282. return NULL;
  283. }
  284. Assert( nElementIndex < elementList.Count() );
  285. Assert( nElementIndex >= 0 || nElementIndex == ELEMENT_INDEX_NULL );
  286. if ( nElementIndex < 0 || !elementList[ nElementIndex ] )
  287. return NULL;
  288. return elementList[ nElementIndex ];
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Reads an element attribute
  292. //-----------------------------------------------------------------------------
  293. void CDmxSerializer::UnserializeElementAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList )
  294. {
  295. CDmxElement *pElement = UnserializeElementIndex( buf, elementList );
  296. pAttribute->SetValue( pElement );
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Reads an element array attribute
  300. //-----------------------------------------------------------------------------
  301. void CDmxSerializer::UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList )
  302. {
  303. int nElementCount = buf.GetInt();
  304. CUtlVector< CDmxElement* >& elementArray = pAttribute->GetArrayForEdit< CDmxElement* >();
  305. elementArray.EnsureCapacity( nElementCount );
  306. for ( int i = 0; i < nElementCount; ++i )
  307. {
  308. CDmxElement *pElement = UnserializeElementIndex( buf, elementList );
  309. elementArray.AddToTail( pElement );
  310. }
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Reads a single element
  314. //-----------------------------------------------------------------------------
  315. bool CDmxSerializer::UnserializeAttributes( CUtlBuffer &buf, CDmxElement *pElement, CUtlVector<CDmxElement*> &elementList, int nStrings, int *offsetTable, char *stringTable, int nEncodingVersion )
  316. {
  317. CDmxElementModifyScope modify( pElement );
  318. bool bUseLargeSymbols = nEncodingVersion >= DMX_BINARY_VER_STRINGTABLE_LARGESYMBOLS;
  319. char nameBuf[ 1024 ];
  320. int nAttributeCount = buf.GetInt();
  321. for ( int i = 0; i < nAttributeCount; ++i )
  322. {
  323. const char *pName = NULL;
  324. {
  325. if ( stringTable )
  326. {
  327. pName = Dme_GetStringFromBuffer( buf, bUseLargeSymbols, nStrings, offsetTable, stringTable );
  328. if ( !pName )
  329. return false;
  330. }
  331. else
  332. {
  333. buf.GetString( nameBuf, sizeof( nameBuf ) );
  334. pName = nameBuf;
  335. }
  336. }
  337. DmAttributeType_t nAttributeType = (DmAttributeType_t)buf.GetChar();
  338. Assert( nAttributeType >= AT_FIRST_VALUE_TYPE && nAttributeType < AT_TYPE_COUNT );
  339. CDmxAttribute *pAttribute = pElement->AddAttribute( pName );
  340. if ( !pAttribute )
  341. {
  342. Warning( "WARNING: Failed to create attribute '%s' - likely out of memory in s_DMXAllocator!\n", pName );
  343. return false;
  344. }
  345. switch( nAttributeType )
  346. {
  347. default:
  348. pAttribute->Unserialize( nAttributeType, buf );
  349. break;
  350. case AT_ELEMENT:
  351. UnserializeElementAttribute( buf, pAttribute, elementList );
  352. break;
  353. case AT_ELEMENT_ARRAY:
  354. UnserializeElementArrayAttribute( buf, pAttribute, elementList );
  355. break;
  356. case AT_STRING:
  357. if ( stringTable && nEncodingVersion >= DMX_BINARY_VER_GLOBAL_STRINGTABLE )
  358. {
  359. const char *pValue = Dme_GetStringFromBuffer( buf, bUseLargeSymbols, nStrings, offsetTable, stringTable );
  360. if ( !pValue )
  361. return false;
  362. pAttribute->SetValue( pValue );
  363. }
  364. else
  365. {
  366. pAttribute->Unserialize( nAttributeType, buf );
  367. }
  368. break;
  369. }
  370. }
  371. return buf.IsValid();
  372. }
  373. int CDmxSerializer::GetStringOffsetTable( CUtlBuffer &buf, int *offsetTable, int nStrings )
  374. {
  375. int nBytes = buf.GetBytesRemaining();
  376. char *pBegin = ( char* )buf.PeekGet();
  377. char *pBytes = pBegin;
  378. for ( int i = 0; i < nStrings; ++i )
  379. {
  380. offsetTable[ i ] = pBytes - pBegin;
  381. do
  382. {
  383. // grow/shift utlbuffer if it hasn't loaded the entire string table into memory
  384. if ( pBytes - pBegin >= nBytes )
  385. {
  386. pBegin = ( char* )buf.PeekGet( nBytes + 1, 0 );
  387. if ( !pBegin )
  388. return false;
  389. pBytes = pBegin + nBytes;
  390. nBytes = buf.GetBytesRemaining();
  391. }
  392. }
  393. while ( *pBytes++ );
  394. }
  395. return pBytes - pBegin;
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Main entry point for the unserialization
  399. //-----------------------------------------------------------------------------
  400. bool CDmxSerializer::Unserialize( CUtlBuffer &buf, int nEncodingVersion, CDmxElement **ppRoot )
  401. {
  402. if ( nEncodingVersion < 0 || nEncodingVersion > CURRENT_BINARY_ENCODING )
  403. return false;
  404. bool bReadStringTable = nEncodingVersion >= DMX_BINARY_VER_STRINGTABLE;
  405. bool bUseLargeSymbols = nEncodingVersion >= DMX_BINARY_VER_STRINGTABLE_LARGESYMBOLS;
  406. // Keep reading until we read a NULL terminator
  407. while( buf.GetChar() != 0 )
  408. {
  409. if ( !buf.IsValid() )
  410. return false;
  411. }
  412. // Read string table
  413. int nStrings = 0;
  414. int *offsetTable = NULL;
  415. char *stringTable = NULL;
  416. if ( bReadStringTable )
  417. {
  418. if ( nEncodingVersion >= DMX_BINARY_VER_GLOBAL_STRINGTABLE )
  419. {
  420. nStrings = buf.GetInt();
  421. }
  422. else
  423. {
  424. nStrings = buf.GetShort();
  425. }
  426. if ( nStrings > 0 )
  427. {
  428. offsetTable = ( int* )stackalloc( nStrings * sizeof( int ) );
  429. // this causes entire string table to be mapped in memory at once
  430. int nStringMemoryUsage = GetStringOffsetTable( buf, offsetTable, nStrings );
  431. stringTable = ( char* )stackalloc( nStringMemoryUsage * sizeof( char ) );
  432. buf.Get( stringTable, nStringMemoryUsage );
  433. }
  434. }
  435. // Read in the element count.
  436. int nElementCount = buf.GetInt();
  437. if ( !nElementCount )
  438. {
  439. // Empty (but valid) file
  440. return true;
  441. }
  442. if ( nElementCount < 0 || ( bReadStringTable && !stringTable ) )
  443. {
  444. // Invalid file. Non-empty files with a string table need at least one to associate with elements.
  445. return false;
  446. }
  447. char pTypeBuf[256];
  448. char pNameBuf[2048];
  449. DmObjectId_t id;
  450. // Read + create all elements
  451. CUtlVector<CDmxElement*> elementList( 0, nElementCount );
  452. for ( int i = 0; i < nElementCount; ++i )
  453. {
  454. const char *pType = NULL;
  455. if ( stringTable )
  456. {
  457. pType = Dme_GetStringFromBuffer( buf, bUseLargeSymbols, nStrings, offsetTable, stringTable );
  458. if ( !pType )
  459. return false;
  460. }
  461. else
  462. {
  463. buf.GetString( pTypeBuf, sizeof( pTypeBuf ) );
  464. pType = pTypeBuf;
  465. }
  466. const char *pName = NULL;
  467. if ( bReadStringTable && nEncodingVersion >= DMX_BINARY_VER_GLOBAL_STRINGTABLE )
  468. {
  469. pName = Dme_GetStringFromBuffer( buf, bUseLargeSymbols, nStrings, offsetTable, stringTable );
  470. if ( !pName )
  471. return false;
  472. }
  473. else
  474. {
  475. buf.GetString( pNameBuf, sizeof( pNameBuf ) );
  476. pName = pNameBuf;
  477. }
  478. buf.Get( &id, sizeof(DmObjectId_t) );
  479. CDmxElement *pElement = new CDmxElement( pType );
  480. {
  481. CDmxElementModifyScope modify( pElement );
  482. CDmxAttribute *pAttribute = pElement->AddAttribute( "name" );
  483. pAttribute->SetValue( pName );
  484. pElement->SetId( id );
  485. }
  486. elementList.AddToTail( pElement );
  487. }
  488. // The root is the 0th element
  489. *ppRoot = elementList[ 0 ];
  490. // Now read all attributes
  491. for ( int i = 0; i < nElementCount; ++i )
  492. {
  493. if ( !UnserializeAttributes( buf, elementList[ i ], elementList, nStrings, offsetTable, stringTable, nEncodingVersion ) )
  494. {
  495. return false;
  496. }
  497. }
  498. return buf.IsValid();
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Serialization main entry point
  502. //-----------------------------------------------------------------------------
  503. bool SerializeDMX( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName )
  504. {
  505. // Write the format name into the file using XML format so that
  506. // 3rd-party XML readers can read the file without fail
  507. const char *pEncodingName = buf.IsText() ? "keyvalues2" : "binary";
  508. int nEncodingVersion = buf.IsText() ? 1 : CURRENT_BINARY_ENCODING;
  509. const char *pFormatName = GENERIC_DMX_FORMAT;
  510. int nFormatVersion = 1; // HACK - we should have some way of automatically updating this when the encoding version changes!
  511. buf.Printf( "%s encoding %s %d format %s %d %s\n", DMX_VERSION_STARTING_TOKEN, pEncodingName, nEncodingVersion, pFormatName, nFormatVersion, DMX_VERSION_ENDING_TOKEN );
  512. if ( buf.IsText() )
  513. return SerializeTextDMX( pFileName ? pFileName : "<no file>", buf, pRoot );
  514. CDmxSerializer dmxSerializer;
  515. return dmxSerializer.Serialize( buf, pRoot, pFileName );
  516. }
  517. bool SerializeDMX( const char *pFileName, const char *pPathID, bool bTextMode, CDmxElement *pRoot )
  518. {
  519. // NOTE: This guarantees full path names for pathids
  520. char pBuf[MAX_PATH];
  521. const char *pFullPath = pFileName;
  522. if ( !Q_IsAbsolutePath( pFullPath ) && !pPathID )
  523. {
  524. char pDir[MAX_PATH];
  525. if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
  526. {
  527. Q_ComposeFileName( pDir, pFileName, pBuf, sizeof(pBuf) );
  528. Q_RemoveDotSlashes( pBuf );
  529. pFullPath = pBuf;
  530. }
  531. }
  532. if ( !bTextMode )
  533. {
  534. CUtlStreamBuffer buf( pFullPath, pPathID, 0 );
  535. if ( !buf.IsValid() )
  536. {
  537. Warning( "SerializeDMX: Unable to open file \"%s\"\n", pFullPath );
  538. return false;
  539. }
  540. return SerializeDMX( buf, pRoot, pFullPath );
  541. }
  542. else
  543. {
  544. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  545. bool bOk = SerializeDMX( buf, pRoot, pFullPath );
  546. if ( !bOk )
  547. return false;
  548. if ( !g_pFullFileSystem->WriteFile( pFullPath, pPathID, buf ) )
  549. {
  550. Warning( "SerializeDMX: Unable to open file \"%s\"\n", pFullPath );
  551. return false;
  552. }
  553. }
  554. return true;
  555. }
  556. //-----------------------------------------------------------------------------
  557. // Read the header, return the version (or false if it's not a DMX file)
  558. //-----------------------------------------------------------------------------
  559. bool ReadDMXHeader( CUtlBuffer &buf, char *pEncodingName, int nEncodingNameLen, int &nEncodingVersion, char *pFormatName, int nFormatNameLen, int &nFormatVersion )
  560. {
  561. // Make the buffer capable of being read as text
  562. bool bBufIsText = buf.IsText();
  563. bool bBufHasCRLF = buf.ContainsCRLF();
  564. buf.SetBufferType( true, !bBufIsText || bBufHasCRLF );
  565. char header[ DMX_MAX_HEADER_LENGTH ] = { 0 };
  566. bool bOk = buf.ParseToken( DMX_VERSION_STARTING_TOKEN, DMX_VERSION_ENDING_TOKEN, header, sizeof( header ) );
  567. if ( bOk )
  568. {
  569. #ifdef _WIN32
  570. int nAssigned = sscanf_s( header, "encoding %s %d format %s %d\n", pEncodingName, nEncodingNameLen, &nEncodingVersion, pFormatName, nFormatNameLen, &nFormatVersion );
  571. #else
  572. // sscanf considered harmful. We don't have POSIX 2008 support on OS X and "C11 Annex K" is optional... (optional specs considered useful)
  573. char pTmpEncodingName[ sizeof( header ) ] = { 0 };
  574. char pTmpFormatName [ sizeof( header ) ] = { 0 };
  575. int nAssigned = sscanf( header, "encoding %s %d format %s %d\n", pTmpEncodingName, &nEncodingVersion, pTmpFormatName, &nFormatVersion );
  576. bOk = ( V_strlen( pTmpEncodingName ) < nEncodingNameLen ) && ( V_strlen( pTmpFormatName ) < nFormatNameLen );
  577. V_strncpy( pEncodingName, pTmpEncodingName, nEncodingNameLen );
  578. V_strncpy( pFormatName, pTmpFormatName, nFormatNameLen );
  579. #endif
  580. bOk = bOk && ( nAssigned == 4 );
  581. if ( bOk )
  582. {
  583. bOk = !V_stricmp( pEncodingName, bBufIsText ? "keyvalues2" : "binary" );
  584. }
  585. }
  586. // TODO - retire legacy format version reading
  587. if ( !bOk )
  588. {
  589. buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  590. bOk = buf.ParseToken( DMX_LEGACY_VERSION_STARTING_TOKEN, DMX_LEGACY_VERSION_ENDING_TOKEN, pFormatName, nFormatNameLen );
  591. if ( bOk )
  592. {
  593. nEncodingVersion = 0;
  594. nFormatVersion = 0; // format version is encoded in the format name string
  595. if ( !V_stricmp( pFormatName, "binary_v1" ) || !V_stricmp( pFormatName, "binary_v2" ) )
  596. {
  597. bOk = !bBufIsText;
  598. V_strncpy( pEncodingName, "binary", nEncodingNameLen );
  599. }
  600. else if ( !V_stricmp( pFormatName, "keyvalues2_v1" ) || !V_stricmp( pFormatName, "keyvalues2_flat_v1" ) )
  601. {
  602. bOk = bBufIsText;
  603. V_strncpy( pEncodingName, "keyvalues2", nEncodingNameLen );
  604. }
  605. else
  606. {
  607. bOk = false;
  608. }
  609. }
  610. }
  611. // Restore the buffer type
  612. buf.SetBufferType( bBufIsText, bBufHasCRLF );
  613. return bOk && buf.IsValid();
  614. }
  615. //-----------------------------------------------------------------------------
  616. // Unserialization main entry point
  617. //-----------------------------------------------------------------------------
  618. bool UnserializeDMX( CUtlBuffer &buf, CDmxElement **ppRoot, const char *pFileName )
  619. {
  620. // NOTE: Checking the format name string for a version check here is how you'd do it
  621. *ppRoot = NULL;
  622. // Read the standard buffer header
  623. int nEncodingVersion, nFormatVersion;
  624. char pEncodingName[ DMX_MAX_FORMAT_NAME_MAX_LENGTH ];
  625. char pFormatName [ DMX_MAX_FORMAT_NAME_MAX_LENGTH ];
  626. if ( !ReadDMXHeader( buf,
  627. pEncodingName, sizeof( pEncodingName ), nEncodingVersion,
  628. pFormatName, sizeof( pFormatName ), nFormatVersion ) )
  629. return false;
  630. // TODO - retire legacy format version reading
  631. if ( nFormatVersion == 0 ) // legacy formats store format version in their format name string
  632. {
  633. Warning( "reading file '%s' of legacy format '%s' - dmxconvert this file to a newer format!\n", pFileName ? pFileName : "<no file>", pFormatName );
  634. }
  635. // Only allow binary protocol files
  636. bool bIsBinary = ( buf.GetFlags() & CUtlBuffer::TEXT_BUFFER ) == 0;
  637. if ( bIsBinary )
  638. {
  639. CDmxSerializer dmxUnserializer;
  640. return dmxUnserializer.Unserialize( buf, nEncodingVersion, ppRoot );
  641. }
  642. return UnserializeTextDMX( pFileName ? pFileName : "<no file>", buf, ppRoot );
  643. }
  644. bool UnserializeDMX( const char *pFileName, const char *pPathID, bool bTextMode, CDmxElement **ppRoot )
  645. {
  646. // NOTE: This guarantees full path names for pathids
  647. char pBuf[MAX_PATH];
  648. const char *pFullPath = pFileName;
  649. if ( !Q_IsAbsolutePath( pFullPath ) && !pPathID )
  650. {
  651. char pDir[MAX_PATH];
  652. if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
  653. {
  654. Q_ComposeFileName( pDir, pFileName, pBuf, sizeof(pBuf) );
  655. Q_RemoveDotSlashes( pBuf );
  656. pFullPath = pBuf;
  657. }
  658. }
  659. int nFlags = CUtlBuffer::READ_ONLY;
  660. if ( !bTextMode )
  661. {
  662. CUtlStreamBuffer buf( pFullPath, pPathID, nFlags );
  663. if ( !buf.IsValid() )
  664. {
  665. Warning( "UnserializeDMX: Unable to open file \"%s\"\n", pFullPath );
  666. return false;
  667. }
  668. return UnserializeDMX( buf, ppRoot, pFullPath );
  669. }
  670. else
  671. {
  672. nFlags |= CUtlBuffer::TEXT_BUFFER;
  673. CUtlBuffer buf( 0, 0, nFlags );
  674. if ( !g_pFullFileSystem->ReadFile( pFullPath, pPathID, buf ) )
  675. {
  676. Warning( "UnserializeDMX: Unable to open file \"%s\"\n", pFullPath );
  677. return false;
  678. }
  679. return UnserializeDMX( buf, ppRoot, pFullPath );
  680. }
  681. }
  682. //-----------------------------------------------------------------------------
  683. // Cleans up read-in elements
  684. //-----------------------------------------------------------------------------
  685. void CleanupDMX( CDmxElement *pRoot )
  686. {
  687. if ( pRoot )
  688. {
  689. pRoot->RemoveAllElementsRecursive();
  690. }
  691. }