//========= Copyright Valve Corporation, All rights reserved. ============// // // $Header: $ // $NoKeywords: $ // // Serialization buffer //===========================================================================// #pragma warning (disable : 4514) #include "utlbuffer.h" #include #include #include #include #include #include "tier1/strtools.h" #include "tier1/characterset.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Character conversions for C strings //----------------------------------------------------------------------------- class CUtlCStringConversion : public CUtlCharConversion { public: CUtlCStringConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ); // Finds a conversion for the passed-in string, returns length virtual char FindConversion( const char *pString, int *pLength ); private: char m_pConversion[256]; }; //----------------------------------------------------------------------------- // Character conversions for no-escape sequence strings //----------------------------------------------------------------------------- class CUtlNoEscConversion : public CUtlCharConversion { public: CUtlNoEscConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) : CUtlCharConversion( nEscapeChar, pDelimiter, nCount, pArray ) {} // Finds a conversion for the passed-in string, returns length virtual char FindConversion( const char *pString, int *pLength ) { *pLength = 0; return 0; } }; //----------------------------------------------------------------------------- // List of character conversions //----------------------------------------------------------------------------- BEGIN_CUSTOM_CHAR_CONVERSION( CUtlCStringConversion, s_StringCharConversion, "\"", '\\' ) { '\n', "n" }, { '\t', "t" }, { '\v', "v" }, { '\b', "b" }, { '\r', "r" }, { '\f', "f" }, { '\a', "a" }, { '\\', "\\" }, { '\?', "\?" }, { '\'', "\'" }, { '\"', "\"" }, END_CUSTOM_CHAR_CONVERSION( CUtlCStringConversion, s_StringCharConversion, "\"", '\\' ) CUtlCharConversion *GetCStringCharConversion() { return &s_StringCharConversion; } BEGIN_CUSTOM_CHAR_CONVERSION( CUtlNoEscConversion, s_NoEscConversion, "\"", 0x7F ) { 0x7F, "" }, END_CUSTOM_CHAR_CONVERSION( CUtlNoEscConversion, s_NoEscConversion, "\"", 0x7F ) CUtlCharConversion *GetNoEscCharConversion() { return &s_NoEscConversion; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CUtlCStringConversion::CUtlCStringConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) : CUtlCharConversion( nEscapeChar, pDelimiter, nCount, pArray ) { memset( m_pConversion, 0x0, sizeof(m_pConversion) ); for ( int i = 0; i < nCount; ++i ) { m_pConversion[ (unsigned char) pArray[i].m_pReplacementString[0] ] = pArray[i].m_nActualChar; } } // Finds a conversion for the passed-in string, returns length char CUtlCStringConversion::FindConversion( const char *pString, int *pLength ) { char c = m_pConversion[ (unsigned char) pString[0] ]; *pLength = (c != '\0') ? 1 : 0; return c; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CUtlCharConversion::CUtlCharConversion( char nEscapeChar, const char *pDelimiter, int nCount, ConversionArray_t *pArray ) { m_nEscapeChar = nEscapeChar; m_pDelimiter = pDelimiter; m_nCount = nCount; m_nDelimiterLength = Q_strlen( pDelimiter ); m_nMaxConversionLength = 0; memset( m_pReplacements, 0, sizeof(m_pReplacements) ); for ( int i = 0; i < nCount; ++i ) { m_pList[i] = pArray[i].m_nActualChar; ConversionInfo_t &info = m_pReplacements[ (unsigned char) m_pList[i] ]; Assert( info.m_pReplacementString == 0 ); info.m_pReplacementString = pArray[i].m_pReplacementString; info.m_nLength = Q_strlen( info.m_pReplacementString ); if ( info.m_nLength > m_nMaxConversionLength ) { m_nMaxConversionLength = info.m_nLength; } } } //----------------------------------------------------------------------------- // Escape character + delimiter //----------------------------------------------------------------------------- char CUtlCharConversion::GetEscapeChar() const { return m_nEscapeChar; } const char *CUtlCharConversion::GetDelimiter() const { return m_pDelimiter; } int CUtlCharConversion::GetDelimiterLength() const { return m_nDelimiterLength; } //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- const char *CUtlCharConversion::GetConversionString( char c ) const { return m_pReplacements[ (unsigned char) c ].m_pReplacementString; } int CUtlCharConversion::GetConversionLength( char c ) const { return m_pReplacements[ (unsigned char) c ].m_nLength; } int CUtlCharConversion::MaxConversionLength() const { return m_nMaxConversionLength; } //----------------------------------------------------------------------------- // Finds a conversion for the passed-in string, returns length //----------------------------------------------------------------------------- char CUtlCharConversion::FindConversion( const char *pString, int *pLength ) { for ( int i = 0; i < m_nCount; ++i ) { if ( !Q_strcmp( pString, m_pReplacements[ (unsigned char) m_pList[i] ].m_pReplacementString ) ) { *pLength = m_pReplacements[ (unsigned char) m_pList[i] ].m_nLength; return m_pList[i]; } } *pLength = 0; return '\0'; } //----------------------------------------------------------------------------- // constructors //----------------------------------------------------------------------------- CUtlBuffer::CUtlBuffer( int growSize, int initSize, int nFlags ) : m_Error(0) { MEM_ALLOC_CREDIT(); m_Memory.Init( growSize, initSize ); m_Get = 0; m_Put = 0; m_nTab = 0; m_nOffset = 0; m_Flags = nFlags; if ( (initSize != 0) && !IsReadOnly() ) { m_nMaxPut = -1; AddNullTermination(); } else { m_nMaxPut = 0; } SetOverflowFuncs( &CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow ); } CUtlBuffer::CUtlBuffer( const void *pBuffer, int nSize, int nFlags ) : m_Memory( (unsigned char*)pBuffer, nSize ), m_Error(0) { Assert( nSize != 0 ); m_Get = 0; m_Put = 0; m_nTab = 0; m_nOffset = 0; m_Flags = nFlags; if ( IsReadOnly() ) { m_nMaxPut = nSize; } else { m_nMaxPut = -1; AddNullTermination(); } SetOverflowFuncs( &CUtlBuffer::GetOverflow, &CUtlBuffer::PutOverflow ); } //----------------------------------------------------------------------------- // Modifies the buffer to be binary or text; Blows away the buffer and the CONTAINS_CRLF value. //----------------------------------------------------------------------------- void CUtlBuffer::SetBufferType( bool bIsText, bool bContainsCRLF ) { #ifdef _DEBUG // If the buffer is empty, there is no opportunity for this stuff to fail if ( TellMaxPut() != 0 ) { if ( IsText() ) { if ( bIsText ) { Assert( ContainsCRLF() == bContainsCRLF ); } else { Assert( ContainsCRLF() ); } } else { if ( bIsText ) { Assert( bContainsCRLF ); } } } #endif if ( bIsText ) { m_Flags |= TEXT_BUFFER; } else { m_Flags &= ~TEXT_BUFFER; } if ( bContainsCRLF ) { m_Flags |= CONTAINS_CRLF; } else { m_Flags &= ~CONTAINS_CRLF; } } //----------------------------------------------------------------------------- // Attaches the buffer to external memory.... //----------------------------------------------------------------------------- void CUtlBuffer::SetExternalBuffer( void* pMemory, int nSize, int nInitialPut, int nFlags ) { m_Memory.SetExternalBuffer( (unsigned char*)pMemory, nSize ); // Reset all indices; we just changed memory m_Get = 0; m_Put = nInitialPut; m_nTab = 0; m_Error = 0; m_nOffset = 0; m_Flags = nFlags; m_nMaxPut = -1; AddNullTermination(); } //----------------------------------------------------------------------------- // Assumes an external buffer but manages its deletion //----------------------------------------------------------------------------- void CUtlBuffer::AssumeMemory( void *pMemory, int nSize, int nInitialPut, int nFlags ) { m_Memory.AssumeMemory( (unsigned char*) pMemory, nSize ); // Reset all indices; we just changed memory m_Get = 0; m_Put = nInitialPut; m_nTab = 0; m_Error = 0; m_nOffset = 0; m_Flags = nFlags; m_nMaxPut = -1; AddNullTermination(); } //----------------------------------------------------------------------------- // Makes sure we've got at least this much memory //----------------------------------------------------------------------------- void CUtlBuffer::EnsureCapacity( int num ) { MEM_ALLOC_CREDIT(); // Add one extra for the null termination num += 1; if ( m_Memory.IsExternallyAllocated() ) { if ( IsGrowable() && ( m_Memory.NumAllocated() < num ) ) { m_Memory.ConvertToGrowableMemory( 0 ); } else { num -= 1; } } m_Memory.EnsureCapacity( num ); } //----------------------------------------------------------------------------- // Base get method from which all others derive //----------------------------------------------------------------------------- void CUtlBuffer::Get( void* pMem, int size ) { if ( size > 0 && CheckGet( size ) ) { int Index = m_Get - m_nOffset; Assert( m_Memory.IsIdxValid( Index ) && m_Memory.IsIdxValid( Index + size - 1 ) ); memcpy( pMem, &m_Memory[ Index ], size ); m_Get += size; } } //----------------------------------------------------------------------------- // This will get at least 1 byte and up to nSize bytes. // It will return the number of bytes actually read. //----------------------------------------------------------------------------- int CUtlBuffer::GetUpTo( void *pMem, int nSize ) { if ( CheckArbitraryPeekGet( 0, nSize ) ) { int Index = m_Get - m_nOffset; Assert( m_Memory.IsIdxValid( Index ) && m_Memory.IsIdxValid( Index + nSize - 1 ) ); memcpy( pMem, &m_Memory[ Index ], nSize ); m_Get += nSize; return nSize; } return 0; } //----------------------------------------------------------------------------- // Eats whitespace //----------------------------------------------------------------------------- void CUtlBuffer::EatWhiteSpace() { if ( IsText() && IsValid() ) { while ( CheckGet( sizeof(char) ) ) { if ( !isspace( *(const unsigned char*)PeekGet() ) ) break; m_Get += sizeof(char); } } } //----------------------------------------------------------------------------- // Eats C++ style comments //----------------------------------------------------------------------------- bool CUtlBuffer::EatCPPComment() { if ( IsText() && IsValid() ) { // If we don't have a a c++ style comment next, we're done const char *pPeek = (const char *)PeekGet( 2 * sizeof(char), 0 ); if ( !pPeek || ( pPeek[0] != '/' ) || ( pPeek[1] != '/' ) ) return false; // Deal with c++ style comments m_Get += 2; // read complete line for ( char c = GetChar(); IsValid(); c = GetChar() ) { if ( c == '\n' ) break; } return true; } return false; } //----------------------------------------------------------------------------- // Peeks how much whitespace to eat //----------------------------------------------------------------------------- int CUtlBuffer::PeekWhiteSpace( int nOffset ) { if ( !IsText() || !IsValid() ) return 0; while ( CheckPeekGet( nOffset, sizeof(char) ) ) { if ( !isspace( *(unsigned char*)PeekGet( nOffset ) ) ) break; nOffset += sizeof(char); } return nOffset; } //----------------------------------------------------------------------------- // Peek size of sting to come, check memory bound //----------------------------------------------------------------------------- int CUtlBuffer::PeekStringLength() { if ( !IsValid() ) return 0; // Eat preceeding whitespace int nOffset = 0; if ( IsText() ) { nOffset = PeekWhiteSpace( nOffset ); } int nStartingOffset = nOffset; do { int nPeekAmount = 128; // NOTE: Add 1 for the terminating zero! if ( !CheckArbitraryPeekGet( nOffset, nPeekAmount ) ) { if ( nOffset == nStartingOffset ) return 0; return nOffset - nStartingOffset + 1; } const char *pTest = (const char *)PeekGet( nOffset ); if ( !IsText() ) { for ( int i = 0; i < nPeekAmount; ++i ) { // The +1 here is so we eat the terminating 0 if ( pTest[i] == 0 ) return (i + nOffset - nStartingOffset + 1); } } else { for ( int i = 0; i < nPeekAmount; ++i ) { // The +1 here is so we eat the terminating 0 if ( isspace((unsigned char)pTest[i]) || (pTest[i] == 0) ) return (i + nOffset - nStartingOffset + 1); } } nOffset += nPeekAmount; } while ( true ); } //----------------------------------------------------------------------------- // Peek size of line to come, check memory bound //----------------------------------------------------------------------------- int CUtlBuffer::PeekLineLength() { if ( !IsValid() ) return 0; int nOffset = 0; int nStartingOffset = nOffset; do { int nPeekAmount = 128; // NOTE: Add 1 for the terminating zero! if ( !CheckArbitraryPeekGet( nOffset, nPeekAmount ) ) { if ( nOffset == nStartingOffset ) return 0; return nOffset - nStartingOffset + 1; } const char *pTest = (const char *)PeekGet( nOffset ); for ( int i = 0; i < nPeekAmount; ++i ) { // The +2 here is so we eat the terminating '\n' and 0 if ( pTest[i] == '\n' || pTest[i] == '\r' ) return (i + nOffset - nStartingOffset + 2); // The +1 here is so we eat the terminating 0 if ( pTest[i] == 0 ) return (i + nOffset - nStartingOffset + 1); } nOffset += nPeekAmount; } while ( true ); } //----------------------------------------------------------------------------- // Does the next bytes of the buffer match a pattern? //----------------------------------------------------------------------------- bool CUtlBuffer::PeekStringMatch( int nOffset, const char *pString, int nLen ) { if ( !CheckPeekGet( nOffset, nLen ) ) return false; return !Q_strncmp( (const char*)PeekGet(nOffset), pString, nLen ); } //----------------------------------------------------------------------------- // This version of PeekStringLength converts \" to \\ and " to \, etc. // It also reads a " at the beginning and end of the string //----------------------------------------------------------------------------- int CUtlBuffer::PeekDelimitedStringLength( CUtlCharConversion *pConv, bool bActualSize ) { if ( !IsText() || !pConv ) return PeekStringLength(); // Eat preceeding whitespace int nOffset = 0; if ( IsText() ) { nOffset = PeekWhiteSpace( nOffset ); } if ( !PeekStringMatch( nOffset, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) return 0; // Try to read ending ", but don't accept \" int nActualStart = nOffset; nOffset += pConv->GetDelimiterLength(); int nLen = 1; // Starts at 1 for the '\0' termination do { if ( PeekStringMatch( nOffset, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) break; if ( !CheckPeekGet( nOffset, 1 ) ) break; char c = *(const char*)PeekGet( nOffset ); ++nLen; ++nOffset; if ( c == pConv->GetEscapeChar() ) { int nLength = pConv->MaxConversionLength(); if ( !CheckArbitraryPeekGet( nOffset, nLength ) ) break; pConv->FindConversion( (const char*)PeekGet(nOffset), &nLength ); nOffset += nLength; } } while (true); return bActualSize ? nLen : nOffset - nActualStart + pConv->GetDelimiterLength() + 1; } //----------------------------------------------------------------------------- // Reads a null-terminated string //----------------------------------------------------------------------------- void CUtlBuffer::GetStringInternal( char *pString, size_t maxLenInChars ) { if ( !IsValid() ) { *pString = 0; return; } // This can legitimately be zero if we were told that the buffer is zero length, and // we're asking to duplicate the buffer, so let that pass, too. Assert( maxLenInChars != 0 || PeekStringLength() == 0 ); if ( maxLenInChars == 0 ) { return; } // Remember, this *includes* the null character // It will be 0, however, if the buffer is empty. int nLen = PeekStringLength(); if ( IsText() ) { EatWhiteSpace(); } if ( nLen <= 0 ) { *pString = 0; m_Error |= GET_OVERFLOW; return; } const size_t nCharsToRead = min( (size_t)nLen, maxLenInChars ) - 1; Get( pString, nCharsToRead ); pString[nCharsToRead] = 0; if ( (size_t)nLen > (nCharsToRead + 1) ) { SeekGet( SEEK_CURRENT, nLen - (nCharsToRead + 1) ); } // Read the terminating NULL in binary formats if ( !IsText() ) { VerifyEquals( GetChar(), 0 ); } } //----------------------------------------------------------------------------- // Reads up to and including the first \n //----------------------------------------------------------------------------- void CUtlBuffer::GetLine( char* pLine, int nMaxChars ) { Assert( IsText() && !ContainsCRLF() ); if ( !IsValid() ) { *pLine = 0; return; } if ( nMaxChars == 0 ) { nMaxChars = INT_MAX; } // Remember, this *includes* the null character // It will be 0, however, if the buffer is empty. int nLen = PeekLineLength(); if ( nLen == 0 ) { *pLine = 0; m_Error |= GET_OVERFLOW; return; } // Strip off the terminating NULL if ( nLen <= nMaxChars ) { Get( pLine, nLen - 1 ); pLine[ nLen - 1 ] = 0; } else { Get( pLine, nMaxChars - 1 ); pLine[ nMaxChars - 1 ] = 0; SeekGet( SEEK_CURRENT, nLen - 1 - nMaxChars ); } } //----------------------------------------------------------------------------- // This version of GetString converts \ to \\ and " to \", etc. // It also places " at the beginning and end of the string //----------------------------------------------------------------------------- char CUtlBuffer::GetDelimitedCharInternal( CUtlCharConversion *pConv ) { char c = GetChar(); if ( c == pConv->GetEscapeChar() ) { int nLength = pConv->MaxConversionLength(); if ( !CheckArbitraryPeekGet( 0, nLength ) ) return '\0'; c = pConv->FindConversion( (const char *)PeekGet(), &nLength ); SeekGet( SEEK_CURRENT, nLength ); } return c; } char CUtlBuffer::GetDelimitedChar( CUtlCharConversion *pConv ) { if ( !IsText() || !pConv ) return GetChar( ); return GetDelimitedCharInternal( pConv ); } void CUtlBuffer::GetDelimitedString( CUtlCharConversion *pConv, char *pString, int nMaxChars ) { if ( !IsText() || !pConv ) { GetStringInternal( pString, nMaxChars ); return; } if (!IsValid()) { *pString = 0; return; } if ( nMaxChars == 0 ) { nMaxChars = INT_MAX; } EatWhiteSpace(); if ( !PeekStringMatch( 0, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) return; // Pull off the starting delimiter SeekGet( SEEK_CURRENT, pConv->GetDelimiterLength() ); int nRead = 0; while ( IsValid() ) { if ( PeekStringMatch( 0, pConv->GetDelimiter(), pConv->GetDelimiterLength() ) ) { SeekGet( SEEK_CURRENT, pConv->GetDelimiterLength() ); break; } char c = GetDelimitedCharInternal( pConv ); if ( nRead < nMaxChars ) { pString[nRead] = c; ++nRead; } } if ( nRead >= nMaxChars ) { nRead = nMaxChars - 1; } pString[nRead] = '\0'; } //----------------------------------------------------------------------------- // Checks if a get is ok //----------------------------------------------------------------------------- bool CUtlBuffer::CheckGet( int nSize ) { if ( m_Error & GET_OVERFLOW ) return false; if ( TellMaxPut() < m_Get + nSize ) { m_Error |= GET_OVERFLOW; return false; } if ( ( m_Get < m_nOffset ) || ( m_Memory.NumAllocated() < m_Get - m_nOffset + nSize ) ) { if ( !OnGetOverflow( nSize ) ) { m_Error |= GET_OVERFLOW; return false; } } return true; } //----------------------------------------------------------------------------- // Checks if a peek get is ok //----------------------------------------------------------------------------- bool CUtlBuffer::CheckPeekGet( int nOffset, int nSize ) { if ( m_Error & GET_OVERFLOW ) return false; // Checking for peek can't set the overflow flag bool bOk = CheckGet( nOffset + nSize ); m_Error &= ~GET_OVERFLOW; return bOk; } //----------------------------------------------------------------------------- // Call this to peek arbitrarily long into memory. It doesn't fail unless // it can't read *anything* new //----------------------------------------------------------------------------- bool CUtlBuffer::CheckArbitraryPeekGet( int nOffset, int &nIncrement ) { if ( TellGet() + nOffset >= TellMaxPut() ) { nIncrement = 0; return false; } if ( TellGet() + nOffset + nIncrement > TellMaxPut() ) { nIncrement = TellMaxPut() - TellGet() - nOffset; } // NOTE: CheckPeekGet could modify TellMaxPut for streaming files // We have to call TellMaxPut again here CheckPeekGet( nOffset, nIncrement ); int nMaxGet = TellMaxPut() - TellGet(); if ( nMaxGet < nIncrement ) { nIncrement = nMaxGet; } return (nIncrement != 0); } //----------------------------------------------------------------------------- // Peek part of the butt //----------------------------------------------------------------------------- const void* CUtlBuffer::PeekGet( int nMaxSize, int nOffset ) { if ( !CheckPeekGet( nOffset, nMaxSize ) ) return NULL; int Index = m_Get + nOffset - m_nOffset; Assert( m_Memory.IsIdxValid( Index ) && m_Memory.IsIdxValid( Index + nMaxSize - 1 ) ); return &m_Memory[ Index ]; } //----------------------------------------------------------------------------- // Change where I'm reading //----------------------------------------------------------------------------- void CUtlBuffer::SeekGet( SeekType_t type, int offset ) { switch( type ) { case SEEK_HEAD: m_Get = offset; break; case SEEK_CURRENT: m_Get += offset; break; case SEEK_TAIL: m_Get = m_nMaxPut - offset; break; } if ( m_Get > m_nMaxPut ) { m_Error |= GET_OVERFLOW; } else { m_Error &= ~GET_OVERFLOW; if ( m_Get < m_nOffset || m_Get >= m_nOffset + Size() ) { OnGetOverflow( -1 ); } } } //----------------------------------------------------------------------------- // Parse... //----------------------------------------------------------------------------- #pragma warning ( disable : 4706 ) int CUtlBuffer::VaScanf( const char* pFmt, va_list list ) { Assert( pFmt ); if ( m_Error || !IsText() ) return 0; int numScanned = 0; int nLength; char c; char* pEnd; while ( (c = *pFmt++) ) { // Stop if we hit the end of the buffer if ( m_Get >= TellMaxPut() ) { m_Error |= GET_OVERFLOW; break; } switch (c) { case ' ': // eat all whitespace EatWhiteSpace(); break; case '%': { // Conversion character... try to convert baby! char type = *pFmt++; if (type == 0) return numScanned; switch(type) { case 'c': { char* ch = va_arg( list, char * ); if ( CheckPeekGet( 0, sizeof(char) ) ) { *ch = *(const char*)PeekGet(); ++m_Get; } else { *ch = 0; return numScanned; } } break; case 'i': case 'd': { int* i = va_arg( list, int * ); // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters nLength = 128; if ( !CheckArbitraryPeekGet( 0, nLength ) ) { *i = 0; return numScanned; } *i = strtol( (char*)PeekGet(), &pEnd, 10 ); int nBytesRead = (int)( pEnd - (char*)PeekGet() ); if ( nBytesRead == 0 ) return numScanned; m_Get += nBytesRead; } break; case 'x': { int* i = va_arg( list, int * ); // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters nLength = 128; if ( !CheckArbitraryPeekGet( 0, nLength ) ) { *i = 0; return numScanned; } *i = strtol( (char*)PeekGet(), &pEnd, 16 ); int nBytesRead = (int)( pEnd - (char*)PeekGet() ); if ( nBytesRead == 0 ) return numScanned; m_Get += nBytesRead; } break; case 'u': { unsigned int* u = va_arg( list, unsigned int *); // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters nLength = 128; if ( !CheckArbitraryPeekGet( 0, nLength ) ) { *u = 0; return numScanned; } *u = strtoul( (char*)PeekGet(), &pEnd, 10 ); int nBytesRead = (int)( pEnd - (char*)PeekGet() ); if ( nBytesRead == 0 ) return numScanned; m_Get += nBytesRead; } break; case 'f': { float* f = va_arg( list, float *); // NOTE: This is not bullet-proof; it assumes numbers are < 128 characters nLength = 128; if ( !CheckArbitraryPeekGet( 0, nLength ) ) { *f = 0.0f; return numScanned; } *f = (float)strtod( (char*)PeekGet(), &pEnd ); int nBytesRead = (int)( pEnd - (char*)PeekGet() ); if ( nBytesRead == 0 ) return numScanned; m_Get += nBytesRead; } break; case 's': { char* s = va_arg( list, char * ); GetStringInternal( s, 256 ); } break; default: { // unimplemented scanf type Assert(0); return numScanned; } break; } ++numScanned; } break; default: { // Here we have to match the format string character // against what's in the buffer or we're done. if ( !CheckPeekGet( 0, sizeof(char) ) ) return numScanned; if ( c != *(const char*)PeekGet() ) return numScanned; ++m_Get; } } } return numScanned; } #pragma warning ( default : 4706 ) int CUtlBuffer::Scanf( const char* pFmt, ... ) { va_list args; va_start( args, pFmt ); int count = VaScanf( pFmt, args ); va_end( args ); return count; } //----------------------------------------------------------------------------- // Advance the get index until after the particular string is found // Do not eat whitespace before starting. Return false if it failed //----------------------------------------------------------------------------- bool CUtlBuffer::GetToken( const char *pToken ) { Assert( pToken ); // Look for the token int nLen = Q_strlen( pToken ); int nSizeToCheck = Size() - TellGet() - m_nOffset; int nGet = TellGet(); do { int nMaxSize = TellMaxPut() - TellGet(); if ( nMaxSize < nSizeToCheck ) { nSizeToCheck = nMaxSize; } if ( nLen > nSizeToCheck ) break; if ( !CheckPeekGet( 0, nSizeToCheck ) ) break; const char *pBufStart = (const char*)PeekGet(); const char *pFoundEnd = Q_strnistr( pBufStart, pToken, nSizeToCheck ); if ( pFoundEnd ) { size_t nOffset = (size_t)pFoundEnd - (size_t)pBufStart; SeekGet( CUtlBuffer::SEEK_CURRENT, nOffset + nLen ); return true; } SeekGet( CUtlBuffer::SEEK_CURRENT, nSizeToCheck - nLen - 1 ); nSizeToCheck = Size() - (nLen-1); } while ( true ); SeekGet( CUtlBuffer::SEEK_HEAD, nGet ); return false; } //----------------------------------------------------------------------------- // (For text buffers only) // Parse a token from the buffer: // Grab all text that lies between a starting delimiter + ending delimiter // (skipping whitespace that leads + trails both delimiters). // Note the delimiter checks are case-insensitive. // If successful, the get index is advanced and the function returns true, // otherwise the index is not advanced and the function returns false. //----------------------------------------------------------------------------- bool CUtlBuffer::ParseToken( const char *pStartingDelim, const char *pEndingDelim, char* pString, int nMaxLen ) { int nCharsToCopy = 0; int nCurrentGet = 0; size_t nEndingDelimLen; // Starting delimiter is optional char emptyBuf = '\0'; if ( !pStartingDelim ) { pStartingDelim = &emptyBuf; } // Ending delimiter is not Assert( pEndingDelim && pEndingDelim[0] ); nEndingDelimLen = Q_strlen( pEndingDelim ); int nStartGet = TellGet(); char nCurrChar; int nTokenStart = -1; EatWhiteSpace( ); while ( *pStartingDelim ) { nCurrChar = *pStartingDelim++; if ( !isspace((unsigned char)nCurrChar) ) { if ( tolower( GetChar() ) != tolower( nCurrChar ) ) goto parseFailed; } else { EatWhiteSpace(); } } EatWhiteSpace(); nTokenStart = TellGet(); if ( !GetToken( pEndingDelim ) ) goto parseFailed; nCurrentGet = TellGet(); nCharsToCopy = (nCurrentGet - nEndingDelimLen) - nTokenStart; if ( nCharsToCopy >= nMaxLen ) { nCharsToCopy = nMaxLen - 1; } if ( nCharsToCopy > 0 ) { SeekGet( CUtlBuffer::SEEK_HEAD, nTokenStart ); Get( pString, nCharsToCopy ); if ( !IsValid() ) goto parseFailed; // Eat trailing whitespace for ( ; nCharsToCopy > 0; --nCharsToCopy ) { if ( !isspace( (unsigned char)pString[ nCharsToCopy-1 ] ) ) break; } } pString[ nCharsToCopy ] = '\0'; // Advance the Get index SeekGet( CUtlBuffer::SEEK_HEAD, nCurrentGet ); return true; parseFailed: // Revert the get index SeekGet( SEEK_HEAD, nStartGet ); pString[0] = '\0'; return false; } //----------------------------------------------------------------------------- // Parses the next token, given a set of character breaks to stop at //----------------------------------------------------------------------------- int CUtlBuffer::ParseToken( characterset_t *pBreaks, char *pTokenBuf, int nMaxLen, bool bParseComments ) { Assert( nMaxLen > 0 ); pTokenBuf[0] = 0; // skip whitespace + comments while ( true ) { if ( !IsValid() ) return -1; EatWhiteSpace(); if ( bParseComments ) { if ( !EatCPPComment() ) break; } else { break; } } char c = GetChar(); // End of buffer if ( c == 0 ) return -1; // handle quoted strings specially if ( c == '\"' ) { int nLen = 0; while( IsValid() ) { c = GetChar(); if ( c == '\"' || !c ) { pTokenBuf[nLen] = 0; return nLen; } pTokenBuf[nLen] = c; if ( ++nLen == nMaxLen ) { pTokenBuf[nLen-1] = 0; return nMaxLen; } } // In this case, we hit the end of the buffer before hitting the end qoute pTokenBuf[nLen] = 0; return nLen; } // parse single characters if ( IN_CHARACTERSET( *pBreaks, c ) ) { pTokenBuf[0] = c; pTokenBuf[1] = 0; return 1; } // parse a regular word int nLen = 0; while ( true ) { pTokenBuf[nLen] = c; if ( ++nLen == nMaxLen ) { pTokenBuf[nLen-1] = 0; return nMaxLen; } c = GetChar(); if ( !IsValid() ) break; if ( IN_CHARACTERSET( *pBreaks, c ) || c == '\"' || c <= ' ' ) { SeekGet( SEEK_CURRENT, -1 ); break; } } pTokenBuf[nLen] = 0; return nLen; } //----------------------------------------------------------------------------- // Serialization //----------------------------------------------------------------------------- void CUtlBuffer::Put( const void *pMem, int size ) { if ( size && CheckPut( size ) ) { int Index = m_Put - m_nOffset; Assert( m_Memory.IsIdxValid( Index ) && m_Memory.IsIdxValid( Index + size - 1 ) ); if( Index >= 0 ) { memcpy( &m_Memory[ Index ], pMem, size ); m_Put += size; AddNullTermination(); } } } //----------------------------------------------------------------------------- // Writes a null-terminated string //----------------------------------------------------------------------------- void CUtlBuffer::PutString( const char* pString ) { if (!IsText()) { if ( pString ) { // Not text? append a null at the end. size_t nLen = Q_strlen( pString ) + 1; Put( pString, nLen * sizeof(char) ); return; } else { PutTypeBin( 0 ); } } else if (pString) { int nTabCount = ( m_Flags & AUTO_TABS_DISABLED ) ? 0 : m_nTab; if ( nTabCount > 0 ) { if ( WasLastCharacterCR() ) { PutTabs(); } const char* pEndl = strchr( pString, '\n' ); while ( pEndl ) { size_t nSize = (size_t)pEndl - (size_t)pString + sizeof(char); Put( pString, nSize ); pString = pEndl + 1; if ( *pString ) { PutTabs(); pEndl = strchr( pString, '\n' ); } else { pEndl = NULL; } } } size_t nLen = Q_strlen( pString ); if ( nLen ) { Put( pString, nLen * sizeof(char) ); } } } //----------------------------------------------------------------------------- // This version of PutString converts \ to \\ and " to \", etc. // It also places " at the beginning and end of the string //----------------------------------------------------------------------------- inline void CUtlBuffer::PutDelimitedCharInternal( CUtlCharConversion *pConv, char c ) { int l = pConv->GetConversionLength( c ); if ( l == 0 ) { PutChar( c ); } else { PutChar( pConv->GetEscapeChar() ); Put( pConv->GetConversionString( c ), l ); } } void CUtlBuffer::PutDelimitedChar( CUtlCharConversion *pConv, char c ) { if ( !IsText() || !pConv ) { PutChar( c ); return; } PutDelimitedCharInternal( pConv, c ); } void CUtlBuffer::PutDelimitedString( CUtlCharConversion *pConv, const char *pString ) { if ( !IsText() || !pConv ) { PutString( pString ); return; } if ( WasLastCharacterCR() ) { PutTabs(); } Put( pConv->GetDelimiter(), pConv->GetDelimiterLength() ); int nLen = pString ? Q_strlen( pString ) : 0; for ( int i = 0; i < nLen; ++i ) { PutDelimitedCharInternal( pConv, pString[i] ); } if ( WasLastCharacterCR() ) { PutTabs(); } Put( pConv->GetDelimiter(), pConv->GetDelimiterLength() ); } void CUtlBuffer::VaPrintf( const char* pFmt, va_list list ) { char temp[2048]; #ifdef DBGFLAG_ASSERT int nLen = #endif Q_vsnprintf( temp, sizeof( temp ), pFmt, list ); Assert( nLen < 2048 ); PutString( temp ); } void CUtlBuffer::Printf( const char* pFmt, ... ) { va_list args; va_start( args, pFmt ); VaPrintf( pFmt, args ); va_end( args ); } //----------------------------------------------------------------------------- // Calls the overflow functions //----------------------------------------------------------------------------- void CUtlBuffer::SetOverflowFuncs( UtlBufferOverflowFunc_t getFunc, UtlBufferOverflowFunc_t putFunc ) { m_GetOverflowFunc = getFunc; m_PutOverflowFunc = putFunc; } //----------------------------------------------------------------------------- // Calls the overflow functions //----------------------------------------------------------------------------- bool CUtlBuffer::OnPutOverflow( int nSize ) { return (this->*m_PutOverflowFunc)( nSize ); } bool CUtlBuffer::OnGetOverflow( int nSize ) { return (this->*m_GetOverflowFunc)( nSize ); } //----------------------------------------------------------------------------- // Checks if a put is ok //----------------------------------------------------------------------------- bool CUtlBuffer::PutOverflow( int nSize ) { MEM_ALLOC_CREDIT(); if ( m_Memory.IsExternallyAllocated() ) { if ( !IsGrowable() ) return false; m_Memory.ConvertToGrowableMemory( 0 ); } while( Size() < m_Put - m_nOffset + nSize ) { m_Memory.Grow(); } return true; } bool CUtlBuffer::GetOverflow( int nSize ) { return false; } //----------------------------------------------------------------------------- // Checks if a put is ok //----------------------------------------------------------------------------- bool CUtlBuffer::CheckPut( int nSize ) { if ( ( m_Error & PUT_OVERFLOW ) || IsReadOnly() ) return false; if ( ( m_Put < m_nOffset ) || ( m_Memory.NumAllocated() < m_Put - m_nOffset + nSize ) ) { if ( !OnPutOverflow( nSize ) ) { m_Error |= PUT_OVERFLOW; return false; } } return true; } void CUtlBuffer::SeekPut( SeekType_t type, int offset ) { int nNextPut = m_Put; switch( type ) { case SEEK_HEAD: nNextPut = offset; break; case SEEK_CURRENT: nNextPut += offset; break; case SEEK_TAIL: nNextPut = m_nMaxPut - offset; break; } // Force a write of the data // FIXME: We could make this more optimal potentially by writing out // the entire buffer if you seek outside the current range // NOTE: This call will write and will also seek the file to nNextPut. OnPutOverflow( -nNextPut-1 ); m_Put = nNextPut; AddNullTermination(); } void CUtlBuffer::ActivateByteSwapping( bool bActivate ) { m_Byteswap.ActivateByteSwapping( bActivate ); } void CUtlBuffer::SetBigEndian( bool bigEndian ) { m_Byteswap.SetTargetBigEndian( bigEndian ); } bool CUtlBuffer::IsBigEndian( void ) { return m_Byteswap.IsTargetBigEndian(); } //----------------------------------------------------------------------------- // null terminate the buffer //----------------------------------------------------------------------------- void CUtlBuffer::AddNullTermination( void ) { if ( m_Put > m_nMaxPut ) { if ( !IsReadOnly() && ((m_Error & PUT_OVERFLOW) == 0) ) { // Add null termination value if ( CheckPut( 1 ) ) { int Index = m_Put - m_nOffset; Assert( m_Memory.IsIdxValid( Index ) ); if( Index >= 0 ) { m_Memory[ Index ] = 0; } } else { // Restore the overflow state, it was valid before... m_Error &= ~PUT_OVERFLOW; } } m_nMaxPut = m_Put; } } //----------------------------------------------------------------------------- // Converts a buffer from a CRLF buffer to a CR buffer (and back) // Returns false if no conversion was necessary (and outBuf is left untouched) // If the conversion occurs, outBuf will be cleared. //----------------------------------------------------------------------------- bool CUtlBuffer::ConvertCRLF( CUtlBuffer &outBuf ) { if ( !IsText() || !outBuf.IsText() ) return false; if ( ContainsCRLF() == outBuf.ContainsCRLF() ) return false; int nInCount = TellMaxPut(); outBuf.Purge(); outBuf.EnsureCapacity( nInCount ); bool bFromCRLF = ContainsCRLF(); // Start reading from the beginning int nGet = TellGet(); int nPut = TellPut(); int nGetDelta = 0; int nPutDelta = 0; const char *pBase = (const char*)Base(); int nCurrGet = 0; while ( nCurrGet < nInCount ) { const char *pCurr = &pBase[nCurrGet]; if ( bFromCRLF ) { const char *pNext = Q_strnistr( pCurr, "\r\n", nInCount - nCurrGet ); if ( !pNext ) { outBuf.Put( pCurr, nInCount - nCurrGet ); break; } int nBytes = (size_t)pNext - (size_t)pCurr; outBuf.Put( pCurr, nBytes ); outBuf.PutChar( '\n' ); nCurrGet += nBytes + 2; if ( nGet >= nCurrGet - 1 ) { --nGetDelta; } if ( nPut >= nCurrGet - 1 ) { --nPutDelta; } } else { const char *pNext = Q_strnchr( pCurr, '\n', nInCount - nCurrGet ); if ( !pNext ) { outBuf.Put( pCurr, nInCount - nCurrGet ); break; } int nBytes = (size_t)pNext - (size_t)pCurr; outBuf.Put( pCurr, nBytes ); outBuf.PutChar( '\r' ); outBuf.PutChar( '\n' ); nCurrGet += nBytes + 1; if ( nGet >= nCurrGet ) { ++nGetDelta; } if ( nPut >= nCurrGet ) { ++nPutDelta; } } } Assert( nPut + nPutDelta <= outBuf.TellMaxPut() ); outBuf.SeekGet( SEEK_HEAD, nGet + nGetDelta ); outBuf.SeekPut( SEEK_HEAD, nPut + nPutDelta ); return true; } //----------------------------------------------------------------------------- // Fast swap //----------------------------------------------------------------------------- void CUtlBuffer::Swap( CUtlBuffer &buf ) { V_swap( m_Get, buf.m_Get ); V_swap( m_Put, buf.m_Put ); V_swap( m_nMaxPut, buf.m_nMaxPut ); V_swap( m_Error, buf.m_Error ); m_Memory.Swap( buf.m_Memory ); } //----------------------------------------------------------------------------- // Fast swap w/ a CUtlMemory. //----------------------------------------------------------------------------- void CUtlBuffer::Swap( CUtlMemory &mem ) { m_Get = 0; m_Put = mem.Count(); m_nMaxPut = mem.Count(); m_Error = 0; m_Memory.Swap( mem ); } //--------------------------------------------------------------------------- // Implementation of CUtlInplaceBuffer //--------------------------------------------------------------------------- CUtlInplaceBuffer::CUtlInplaceBuffer( int growSize /* = 0 */, int initSize /* = 0 */, int nFlags /* = 0 */ ) : CUtlBuffer( growSize, initSize, nFlags ) { } bool CUtlInplaceBuffer::InplaceGetLinePtr( char **ppszInBufferPtr, int *pnLineLength ) { Assert( IsText() && !ContainsCRLF() ); int nLineLen = PeekLineLength(); if ( nLineLen <= 1 ) { SeekGet( SEEK_TAIL, 0 ); return false; } -- nLineLen; // because it accounts for putting a terminating null-character char *pszLine = ( char * ) const_cast< void * >( PeekGet() ); SeekGet( SEEK_CURRENT, nLineLen ); // Set the out args if ( ppszInBufferPtr ) *ppszInBufferPtr = pszLine; if ( pnLineLength ) *pnLineLength = nLineLen; return true; } char * CUtlInplaceBuffer::InplaceGetLinePtr( void ) { char *pszLine = NULL; int nLineLen = 0; if ( InplaceGetLinePtr( &pszLine, &nLineLen ) ) { Assert( nLineLen >= 1 ); switch ( pszLine[ nLineLen - 1 ] ) { case '\n': case '\r': pszLine[ nLineLen - 1 ] = 0; if ( -- nLineLen ) { switch ( pszLine[ nLineLen - 1 ] ) { case '\n': case '\r': pszLine[ nLineLen - 1 ] = 0; break; } } break; default: Assert( pszLine[ nLineLen ] == 0 ); break; } } return pszLine; }