698 lines
22 KiB
C++
698 lines
22 KiB
C++
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
//=============================================================================
|
||
|
|
||
|
#include "dmxloader/dmxelement.h"
|
||
|
#include "tier1/utlbuffer.h"
|
||
|
#include "tier2/tier2.h"
|
||
|
#include "tier2/utlstreambuffer.h"
|
||
|
#include "filesystem.h"
|
||
|
#include "datamodel/idatamodel.h" // for the file format #defines
|
||
|
#include "dmxserializationdictionary.h"
|
||
|
#include "tier1/memstack.h"
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// DMX elements/attributes can only be accessed inside a dmx context
|
||
|
//-----------------------------------------------------------------------------
|
||
|
static int s_bInDMXContext;
|
||
|
CMemoryStack s_DMXAllocator;
|
||
|
static bool s_bAllocatorInitialized;
|
||
|
|
||
|
void BeginDMXContext( )
|
||
|
{
|
||
|
Assert( !s_bInDMXContext );
|
||
|
|
||
|
if ( !s_bAllocatorInitialized )
|
||
|
{
|
||
|
s_DMXAllocator.Init( 2 * 1024 * 1024, 0, 0, 4 );
|
||
|
s_bAllocatorInitialized = true;
|
||
|
}
|
||
|
|
||
|
s_bInDMXContext = true;
|
||
|
}
|
||
|
|
||
|
void EndDMXContext( bool bDecommitMemory )
|
||
|
{
|
||
|
Assert( s_bInDMXContext );
|
||
|
s_bInDMXContext = false;
|
||
|
s_DMXAllocator.FreeAll( bDecommitMemory );
|
||
|
}
|
||
|
|
||
|
void DecommitDMXMemory()
|
||
|
{
|
||
|
s_DMXAllocator.FreeAll( true );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Used for allocation. All will be freed when we leave the DMX context
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void* DMXAlloc( size_t size )
|
||
|
{
|
||
|
Assert( s_bInDMXContext );
|
||
|
if ( !s_bInDMXContext )
|
||
|
return 0;
|
||
|
return s_DMXAllocator.Alloc( size, false );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Forward declarations
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool UnserializeTextDMX( const char *pFileName, CUtlBuffer &buf, CDmxElement **ppRoot );
|
||
|
bool SerializeTextDMX( const char *pFileName, CUtlBuffer &buf, CDmxElement *pRoot );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// special element indices
|
||
|
//-----------------------------------------------------------------------------
|
||
|
enum
|
||
|
{
|
||
|
ELEMENT_INDEX_NULL = -1,
|
||
|
ELEMENT_INDEX_EXTERNAL = -2,
|
||
|
};
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Serialization class for Binary output
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CDmxSerializer
|
||
|
{
|
||
|
public:
|
||
|
bool Unserialize( CUtlBuffer &buf, int nEncodingVersion, CDmxElement **ppRoot );
|
||
|
bool Serialize( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName );
|
||
|
|
||
|
private:
|
||
|
// Methods related to serialization
|
||
|
bool ShouldWriteAttribute( const char *pAttributeName, CDmxAttribute *pAttribute );
|
||
|
void SerializeElementIndex( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxElement *pElement );
|
||
|
void SerializeElementAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute );
|
||
|
void SerializeElementArrayAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute );
|
||
|
bool SaveElementDict( CUtlBuffer& buf, CUtlRBTree< const char* > &stringTable, CDmxElement *pElement );
|
||
|
bool SaveElement( CUtlBuffer& buf, CDmxSerializationDictionary& dict, CUtlRBTree< const char* > &stringTable, CDmxElement *pElement);
|
||
|
|
||
|
// Methods related to unserialization
|
||
|
CDmxElement* UnserializeElementIndex( CUtlBuffer &buf, CUtlVector<CDmxElement*> &elementList );
|
||
|
void UnserializeElementAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList );
|
||
|
void UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList );
|
||
|
bool UnserializeAttributes( CUtlBuffer &buf, CDmxElement *pElement, CUtlVector<CDmxElement*> &elementList, int nStrings, int *offsetTable, char *stringTable );
|
||
|
int GetStringOffsetTable( CUtlBuffer &buf, int *offsetTable, int nStrings );
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Should we write out the attribute?
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CDmxSerializer::ShouldWriteAttribute( const char *pAttributeName, CDmxAttribute *pAttribute )
|
||
|
{
|
||
|
if ( !pAttribute )
|
||
|
return false;
|
||
|
|
||
|
// These are already written in the initial element dictionary
|
||
|
if ( !Q_stricmp( pAttributeName, "name" ) )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Write out the index of the element to avoid looks at read time
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CDmxSerializer::SerializeElementIndex( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxElement *pElement )
|
||
|
{
|
||
|
if ( !pElement )
|
||
|
{
|
||
|
buf.PutInt( ELEMENT_INDEX_NULL ); // invalid handle
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
buf.PutInt( list.Find( pElement ) );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Writes out element attributes
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CDmxSerializer::SerializeElementAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute )
|
||
|
{
|
||
|
SerializeElementIndex( buf, list, pAttribute->GetValue<CDmxElement*>() );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Writes out element array attributes
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CDmxSerializer::SerializeElementArrayAttribute( CUtlBuffer& buf, CDmxSerializationDictionary& list, CDmxAttribute *pAttribute )
|
||
|
{
|
||
|
const CUtlVector<CDmxElement*> &vec = pAttribute->GetArray<CDmxElement*>();
|
||
|
|
||
|
int nCount = vec.Count();
|
||
|
buf.PutInt( nCount );
|
||
|
for ( int i = 0; i < nCount; ++i )
|
||
|
{
|
||
|
SerializeElementIndex( buf, list, vec[ i ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Writes out all attributes
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CDmxSerializer::SaveElement( CUtlBuffer& buf, CDmxSerializationDictionary& list, CUtlRBTree< const char* > &stringTable, CDmxElement *pElement )
|
||
|
{
|
||
|
int nAttributesToSave = 0;
|
||
|
|
||
|
// Count the attributes...
|
||
|
int nCount = pElement->AttributeCount();
|
||
|
for ( int i = 0; i < nCount; ++i )
|
||
|
{
|
||
|
CDmxAttribute *pAttribute = pElement->GetAttribute( i );
|
||
|
const char *pName = pAttribute->GetName( );
|
||
|
if ( !ShouldWriteAttribute( pName, pAttribute ) )
|
||
|
continue;
|
||
|
|
||
|
++nAttributesToSave;
|
||
|
}
|
||
|
|
||
|
// Now write them all out.
|
||
|
buf.PutInt( nAttributesToSave );
|
||
|
for ( int i = 0; i < nCount; ++i )
|
||
|
{
|
||
|
CDmxAttribute *pAttribute = pElement->GetAttribute( i );
|
||
|
const char *pName = pAttribute->GetName();
|
||
|
if ( !ShouldWriteAttribute( pName, pAttribute ) )
|
||
|
continue;
|
||
|
|
||
|
unsigned short sym = stringTable.Find( pName );
|
||
|
if ( sym == stringTable.InvalidIndex() )
|
||
|
return false;
|
||
|
|
||
|
buf.PutShort( sym );
|
||
|
buf.PutChar( pAttribute->GetType() );
|
||
|
switch( pAttribute->GetType() )
|
||
|
{
|
||
|
default:
|
||
|
pAttribute->Serialize( buf );
|
||
|
break;
|
||
|
|
||
|
case AT_ELEMENT:
|
||
|
SerializeElementAttribute( buf, list, pAttribute );
|
||
|
break;
|
||
|
|
||
|
case AT_ELEMENT_ARRAY:
|
||
|
SerializeElementArrayAttribute( buf, list, pAttribute );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return buf.IsValid();
|
||
|
}
|
||
|
|
||
|
bool CDmxSerializer::SaveElementDict( CUtlBuffer& buf, CUtlRBTree< const char* > &stringTable, CDmxElement *pElement )
|
||
|
{
|
||
|
unsigned short sym = stringTable.Find( pElement->GetTypeString() );
|
||
|
if ( sym == stringTable.InvalidIndex() )
|
||
|
return false;
|
||
|
|
||
|
buf.PutShort( sym );
|
||
|
buf.PutString( pElement->GetName() );
|
||
|
buf.Put( &pElement->GetId(), sizeof(DmObjectId_t) );
|
||
|
return buf.IsValid();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Main entry point for serialization
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CDmxSerializer::Serialize( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName )
|
||
|
{
|
||
|
// Save elements, attribute links
|
||
|
CDmxSerializationDictionary dict;
|
||
|
dict.BuildElementList( pRoot, true );
|
||
|
|
||
|
// collect list of attribute names and element types into string table
|
||
|
CUtlRBTree< const char* > stringTable( CaselessStringLessThan );
|
||
|
DmxSerializationHandle_t i;
|
||
|
for ( i = dict.FirstRootElement(); i != DMX_SERIALIZATION_HANDLE_INVALID; i = dict.NextRootElement(i) )
|
||
|
{
|
||
|
CDmxElement *pElement = dict.GetRootElement( i );
|
||
|
if ( !pElement )
|
||
|
return false;
|
||
|
stringTable.InsertIfNotFound( pElement->GetTypeString() );
|
||
|
int nAttributes = pElement->AttributeCount();
|
||
|
for ( int ai = 0; ai < nAttributes; ++ai )
|
||
|
{
|
||
|
CDmxAttribute *pAttr = pElement->GetAttribute( ai );
|
||
|
if ( !pAttr )
|
||
|
return false;
|
||
|
stringTable.InsertIfNotFound( pAttr->GetName() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// write out the string table
|
||
|
int nStrings = stringTable.Count();
|
||
|
if ( nStrings > 65535 )
|
||
|
return false;
|
||
|
buf.PutShort( nStrings );
|
||
|
for ( int si = 0; si < nStrings; ++si )
|
||
|
{
|
||
|
buf.PutString( stringTable[ si ] );
|
||
|
}
|
||
|
|
||
|
// First write out the dictionary of all elements (to avoid later stitching up in unserialize)
|
||
|
buf.PutInt( dict.RootElementCount() );
|
||
|
for ( i = dict.FirstRootElement(); i != DMX_SERIALIZATION_HANDLE_INVALID; i = dict.NextRootElement(i) )
|
||
|
{
|
||
|
if ( !SaveElementDict( buf, stringTable, dict.GetRootElement( i ) ) )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Now write out the attributes of each of those elements
|
||
|
for ( i = dict.FirstRootElement(); i != DMX_SERIALIZATION_HANDLE_INVALID; i = dict.NextRootElement(i) )
|
||
|
{
|
||
|
if ( !SaveElement( buf, dict, stringTable, dict.GetRootElement( i ) ) )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Reads an element index and converts it to a handle (local or external)
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CDmxElement* CDmxSerializer::UnserializeElementIndex( CUtlBuffer &buf, CUtlVector<CDmxElement*> &elementList )
|
||
|
{
|
||
|
int nElementIndex = buf.GetInt();
|
||
|
if ( nElementIndex == ELEMENT_INDEX_EXTERNAL )
|
||
|
{
|
||
|
Warning( "Reading externally referenced elements is not supported!\n" );
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
Assert( nElementIndex < elementList.Count() );
|
||
|
Assert( nElementIndex >= 0 || nElementIndex == ELEMENT_INDEX_NULL );
|
||
|
if ( nElementIndex < 0 || !elementList[ nElementIndex ] )
|
||
|
return NULL;
|
||
|
|
||
|
return elementList[ nElementIndex ];
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Reads an element attribute
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CDmxSerializer::UnserializeElementAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList )
|
||
|
{
|
||
|
CDmxElement *pElement = UnserializeElementIndex( buf, elementList );
|
||
|
pAttribute->SetValue( pElement );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Reads an element array attribute
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CDmxSerializer::UnserializeElementArrayAttribute( CUtlBuffer &buf, CDmxAttribute *pAttribute, CUtlVector<CDmxElement*> &elementList )
|
||
|
{
|
||
|
int nElementCount = buf.GetInt();
|
||
|
CUtlVector< CDmxElement* >& elementArray = pAttribute->GetArrayForEdit< CDmxElement* >();
|
||
|
elementArray.EnsureCapacity( nElementCount );
|
||
|
|
||
|
for ( int i = 0; i < nElementCount; ++i )
|
||
|
{
|
||
|
CDmxElement *pElement = UnserializeElementIndex( buf, elementList );
|
||
|
elementArray.AddToTail( pElement );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Reads a single element
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CDmxSerializer::UnserializeAttributes( CUtlBuffer &buf, CDmxElement *pElement, CUtlVector<CDmxElement*> &elementList, int nStrings, int *offsetTable, char *stringTable )
|
||
|
{
|
||
|
CDmxElementModifyScope modify( pElement );
|
||
|
|
||
|
char nameBuf[ 1024 ];
|
||
|
int nAttributeCount = buf.GetInt();
|
||
|
for ( int i = 0; i < nAttributeCount; ++i )
|
||
|
{
|
||
|
const char *pName = NULL;
|
||
|
if ( stringTable )
|
||
|
{
|
||
|
int si = buf.GetShort();
|
||
|
if ( si >= nStrings )
|
||
|
return false;
|
||
|
pName = stringTable + offsetTable[ si ];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
buf.GetString( nameBuf );
|
||
|
pName = nameBuf;
|
||
|
}
|
||
|
DmAttributeType_t nAttributeType = (DmAttributeType_t)buf.GetChar();
|
||
|
|
||
|
CDmxAttribute *pAttribute = pElement->AddAttribute( pName );
|
||
|
if ( !pAttribute )
|
||
|
return false;
|
||
|
|
||
|
switch( nAttributeType )
|
||
|
{
|
||
|
default:
|
||
|
pAttribute->Unserialize( nAttributeType, buf );
|
||
|
break;
|
||
|
|
||
|
case AT_ELEMENT:
|
||
|
UnserializeElementAttribute( buf, pAttribute, elementList );
|
||
|
break;
|
||
|
|
||
|
case AT_ELEMENT_ARRAY:
|
||
|
UnserializeElementArrayAttribute( buf, pAttribute, elementList );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return buf.IsValid();
|
||
|
}
|
||
|
|
||
|
|
||
|
int CDmxSerializer::GetStringOffsetTable( CUtlBuffer &buf, int *offsetTable, int nStrings )
|
||
|
{
|
||
|
int nBytes = buf.GetBytesRemaining();
|
||
|
char *pBegin = ( char* )buf.PeekGet();
|
||
|
char *pBytes = pBegin;
|
||
|
for ( int i = 0; i < nStrings; ++i )
|
||
|
{
|
||
|
offsetTable[ i ] = pBytes - pBegin;
|
||
|
|
||
|
do
|
||
|
{
|
||
|
// grow/shift utlbuffer if it hasn't loaded the entire string table into memory
|
||
|
if ( pBytes - pBegin >= nBytes )
|
||
|
{
|
||
|
pBegin = ( char* )buf.PeekGet( nBytes + 1, 0 );
|
||
|
if ( !pBegin )
|
||
|
return false;
|
||
|
pBytes = pBegin + nBytes;
|
||
|
nBytes = buf.GetBytesRemaining();
|
||
|
}
|
||
|
}
|
||
|
while ( *pBytes++ );
|
||
|
}
|
||
|
|
||
|
return pBytes - pBegin;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Main entry point for the unserialization
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CDmxSerializer::Unserialize( CUtlBuffer &buf, int nEncodingVersion, CDmxElement **ppRoot )
|
||
|
{
|
||
|
if ( nEncodingVersion < 0 || nEncodingVersion > 2 )
|
||
|
return false;
|
||
|
|
||
|
bool bReadStringTable = nEncodingVersion >= 2;
|
||
|
|
||
|
// Keep reading until we read a NULL terminator
|
||
|
while( buf.GetChar() != 0 )
|
||
|
{
|
||
|
if ( !buf.IsValid() )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Read string table
|
||
|
int nStrings = 0;
|
||
|
int *offsetTable = NULL;
|
||
|
char *stringTable = NULL;
|
||
|
if ( bReadStringTable )
|
||
|
{
|
||
|
nStrings = buf.GetShort();
|
||
|
if ( nStrings > 0 )
|
||
|
{
|
||
|
offsetTable = ( int* )stackalloc( nStrings * sizeof( int ) );
|
||
|
|
||
|
// this causes entire string table to be mapped in memory at once
|
||
|
int nStringMemoryUsage = GetStringOffsetTable( buf, offsetTable, nStrings );
|
||
|
stringTable = ( char* )stackalloc( nStringMemoryUsage * sizeof( char ) );
|
||
|
buf.Get( stringTable, nStringMemoryUsage );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Read in the element count.
|
||
|
int nElementCount = buf.GetInt();
|
||
|
if ( !nElementCount )
|
||
|
{
|
||
|
// Empty (but valid) file
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( nElementCount < 0 || ( bReadStringTable && !stringTable ) )
|
||
|
{
|
||
|
// Invalid file. Non-empty files with a string table need at least one to associate with elements.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
char pTypeBuf[256];
|
||
|
char pName[2048];
|
||
|
DmObjectId_t id;
|
||
|
|
||
|
// Read + create all elements
|
||
|
CUtlVector<CDmxElement*> elementList( 0, nElementCount );
|
||
|
for ( int i = 0; i < nElementCount; ++i )
|
||
|
{
|
||
|
const char *pType = NULL;
|
||
|
if ( stringTable )
|
||
|
{
|
||
|
int si = buf.GetShort();
|
||
|
if ( si >= nStrings )
|
||
|
return false;
|
||
|
pType = stringTable + offsetTable[ si ];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
buf.GetString( pTypeBuf );
|
||
|
pType = pTypeBuf;
|
||
|
}
|
||
|
buf.GetString( pName );
|
||
|
buf.Get( &id, sizeof(DmObjectId_t) );
|
||
|
|
||
|
CDmxElement *pElement = new CDmxElement( pType );
|
||
|
{
|
||
|
CDmxElementModifyScope modify( pElement );
|
||
|
CDmxAttribute *pAttribute = pElement->AddAttribute( "name" );
|
||
|
pAttribute->SetValue( (char const *) pName );
|
||
|
pElement->SetId( id );
|
||
|
}
|
||
|
elementList.AddToTail( pElement );
|
||
|
}
|
||
|
|
||
|
// The root is the 0th element
|
||
|
*ppRoot = elementList[ 0 ];
|
||
|
|
||
|
// Now read all attributes
|
||
|
for ( int i = 0; i < nElementCount; ++i )
|
||
|
{
|
||
|
UnserializeAttributes( buf, elementList[ i ], elementList, nStrings, offsetTable, stringTable );
|
||
|
}
|
||
|
|
||
|
return buf.IsValid();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Serialization main entry point
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool SerializeDMX( CUtlBuffer &buf, CDmxElement *pRoot, const char *pFileName )
|
||
|
{
|
||
|
// Write the format name into the file using XML format so that
|
||
|
// 3rd-party XML readers can read the file without fail
|
||
|
const char *pEncodingName = buf.IsText() ? "keyvalues2" : "binary";
|
||
|
int nEncodingVersion = buf.IsText() ? 1 : 2; // HACK - we should have some way of automatically updating this when the encoding version changes!
|
||
|
const char *pFormatName = GENERIC_DMX_FORMAT;
|
||
|
int nFormatVersion = 1; // HACK - we should have some way of automatically updating this when the encoding version changes!
|
||
|
buf.Printf( "%s encoding %s %d format %s %d %s\n", DMX_VERSION_STARTING_TOKEN, pEncodingName, nEncodingVersion, pFormatName, nFormatVersion, DMX_VERSION_ENDING_TOKEN );
|
||
|
|
||
|
if ( buf.IsText() )
|
||
|
return SerializeTextDMX( pFileName ? pFileName : "<no file>", buf, pRoot );
|
||
|
|
||
|
CDmxSerializer dmxSerializer;
|
||
|
return dmxSerializer.Serialize( buf, pRoot, pFileName );
|
||
|
}
|
||
|
|
||
|
bool SerializeDMX( const char *pFileName, const char *pPathID, bool bTextMode, CDmxElement *pRoot )
|
||
|
{
|
||
|
// NOTE: This guarantees full path names for pathids
|
||
|
char pBuf[MAX_PATH];
|
||
|
const char *pFullPath = pFileName;
|
||
|
if ( !Q_IsAbsolutePath( pFullPath ) && !pPathID )
|
||
|
{
|
||
|
char pDir[MAX_PATH];
|
||
|
if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
|
||
|
{
|
||
|
Q_ComposeFileName( pDir, pFileName, pBuf, sizeof(pBuf) );
|
||
|
Q_RemoveDotSlashes( pBuf );
|
||
|
pFullPath = pBuf;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::READ_ONLY );
|
||
|
g_pFullFileSystem->ReadFile( pFullPath, pPathID, buf );
|
||
|
|
||
|
if ( !buf.IsValid() )
|
||
|
{
|
||
|
Warning( "SerializeDMX: Unable to open file \"%s\"\n", pFullPath );
|
||
|
return DMFILEID_INVALID;
|
||
|
}
|
||
|
|
||
|
return SerializeDMX( buf, pRoot, pFullPath );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Read the header, return the version (or false if it's not a DMX file)
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool ReadDMXHeader( CUtlBuffer &buf, char *pEncodingName, int nEncodingNameLen, int &nEncodingVersion, char *pFormatName, int nFormatNameLen, int &nFormatVersion )
|
||
|
{
|
||
|
// Make the buffer capable of being read as text
|
||
|
bool bBufIsText = buf.IsText();
|
||
|
bool bBufHasCRLF = buf.ContainsCRLF();
|
||
|
buf.SetBufferType( true, !bBufIsText || bBufHasCRLF );
|
||
|
|
||
|
char header[ DMX_MAX_HEADER_LENGTH ] = { 0 };
|
||
|
bool bOk = buf.ParseToken( DMX_VERSION_STARTING_TOKEN, DMX_VERSION_ENDING_TOKEN, header, sizeof( header ) );
|
||
|
if ( bOk )
|
||
|
{
|
||
|
#ifdef _WIN32
|
||
|
int nAssigned = sscanf_s( header, "encoding %s %d format %s %d\n", pEncodingName, nEncodingNameLen, &nEncodingVersion, pFormatName, nFormatNameLen, &nFormatVersion );
|
||
|
#else
|
||
|
// sscanf considered harmful. We don't have POSIX 2008 support on OS X and "C11 Annex K" is optional... (optional specs considered useful)
|
||
|
char pTmpEncodingName[ sizeof( header ) ] = { 0 };
|
||
|
char pTmpFormatName [ sizeof( header ) ] = { 0 };
|
||
|
int nAssigned = sscanf( header, "encoding %s %d format %s %d\n", pTmpEncodingName, &nEncodingVersion, pTmpFormatName, &nFormatVersion );
|
||
|
bOk = ( V_strlen( pTmpEncodingName ) < nEncodingNameLen ) && ( V_strlen( pTmpFormatName ) < nFormatNameLen );
|
||
|
V_strncpy( pEncodingName, pTmpEncodingName, nEncodingNameLen );
|
||
|
V_strncpy( pFormatName, pTmpFormatName, nFormatNameLen );
|
||
|
#endif
|
||
|
bOk = bOk && ( nAssigned == 4 );
|
||
|
if ( bOk )
|
||
|
{
|
||
|
bOk = !V_stricmp( pEncodingName, bBufIsText ? "keyvalues2" : "binary" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO - retire legacy format version reading
|
||
|
if ( !bOk )
|
||
|
{
|
||
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
|
||
|
bOk = buf.ParseToken( DMX_LEGACY_VERSION_STARTING_TOKEN, DMX_LEGACY_VERSION_ENDING_TOKEN, pFormatName, sizeof( pFormatName ) );
|
||
|
if ( bOk )
|
||
|
{
|
||
|
nEncodingVersion = 0;
|
||
|
nFormatVersion = 0; // format version is encoded in the format name string
|
||
|
|
||
|
if ( !V_stricmp( pFormatName, "binary_v1" ) || !V_stricmp( pFormatName, "binary_v2" ) )
|
||
|
{
|
||
|
bOk = !bBufIsText;
|
||
|
V_strncpy( pEncodingName, "binary", nEncodingNameLen );
|
||
|
}
|
||
|
else if ( !V_stricmp( pFormatName, "keyvalues2_v1" ) || !V_stricmp( pFormatName, "keyvalues2_flat_v1" ) )
|
||
|
{
|
||
|
bOk = bBufIsText;
|
||
|
V_strncpy( pEncodingName, "keyvalues2", nEncodingNameLen );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bOk = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Restore the buffer type
|
||
|
buf.SetBufferType( bBufIsText, bBufHasCRLF );
|
||
|
return bOk && buf.IsValid();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Unserialization main entry point
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool UnserializeDMX( CUtlBuffer &buf, CDmxElement **ppRoot, const char *pFileName )
|
||
|
{
|
||
|
// NOTE: Checking the format name string for a version check here is how you'd do it
|
||
|
*ppRoot = NULL;
|
||
|
|
||
|
// Read the standard buffer header
|
||
|
int nEncodingVersion, nFormatVersion;
|
||
|
char pEncodingName[ DMX_MAX_FORMAT_NAME_MAX_LENGTH ];
|
||
|
char pFormatName [ DMX_MAX_FORMAT_NAME_MAX_LENGTH ];
|
||
|
if ( !ReadDMXHeader( buf,
|
||
|
pEncodingName, sizeof( pEncodingName ), nEncodingVersion,
|
||
|
pFormatName, sizeof( pFormatName ), nFormatVersion ) )
|
||
|
return false;
|
||
|
|
||
|
// TODO - retire legacy format version reading
|
||
|
if ( nFormatVersion == 0 ) // legacy formats store format version in their format name string
|
||
|
{
|
||
|
Warning( "reading file '%s' of legacy format '%s' - dmxconvert this file to a newer format!\n", pFileName ? pFileName : "<no file>", pFormatName );
|
||
|
}
|
||
|
|
||
|
// Only allow binary protocol files
|
||
|
bool bIsBinary = ( buf.GetFlags() & CUtlBuffer::TEXT_BUFFER ) == 0;
|
||
|
if ( bIsBinary )
|
||
|
{
|
||
|
CDmxSerializer dmxUnserializer;
|
||
|
return dmxUnserializer.Unserialize( buf, nEncodingVersion, ppRoot );
|
||
|
}
|
||
|
return UnserializeTextDMX( pFileName ? pFileName : "<no file>", buf, ppRoot );
|
||
|
}
|
||
|
|
||
|
bool UnserializeDMX( const char *pFileName, const char *pPathID, bool bTextMode, CDmxElement **ppRoot )
|
||
|
{
|
||
|
// NOTE: This guarantees full path names for pathids
|
||
|
char pBuf[MAX_PATH];
|
||
|
const char *pFullPath = pFileName;
|
||
|
if ( !Q_IsAbsolutePath( pFullPath ) && !pPathID )
|
||
|
{
|
||
|
char pDir[MAX_PATH];
|
||
|
if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
|
||
|
{
|
||
|
Q_ComposeFileName( pDir, pFileName, pBuf, sizeof(pBuf) );
|
||
|
Q_RemoveDotSlashes( pBuf );
|
||
|
pFullPath = pBuf;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int nFlags = CUtlBuffer::READ_ONLY;
|
||
|
if ( bTextMode )
|
||
|
{
|
||
|
nFlags |= CUtlBuffer::TEXT_BUFFER;
|
||
|
}
|
||
|
|
||
|
CUtlBuffer buf( 0, 0, nFlags );
|
||
|
g_pFullFileSystem->ReadFile( pFullPath, pPathID, buf );
|
||
|
|
||
|
if ( !buf.IsValid() )
|
||
|
{
|
||
|
Warning( "UnserializeDMX: Unable to open file \"%s\"\n", pFullPath );
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return UnserializeDMX( buf, ppRoot, pFullPath );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Cleans up read-in elements
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CleanupDMX( CDmxElement *pRoot )
|
||
|
{
|
||
|
if ( pRoot )
|
||
|
{
|
||
|
pRoot->RemoveAllElementsRecursive();
|
||
|
}
|
||
|
}
|