|
|
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// $Header: $
// $NoKeywords: $
//
// Serialization buffer
//===========================================================================//
#pragma warning (disable : 4514)
#include "utlbuffer.h"
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>
#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 = V_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 = V_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 ( !V_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( m_Put ); } 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 = m_Put = nSize; } else { m_nMaxPut = -1; AddNullTermination( m_Put ); } 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( m_Put ); }
//-----------------------------------------------------------------------------
// 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( m_Put ); }
//-----------------------------------------------------------------------------
// Allows the caller to control memory
//-----------------------------------------------------------------------------
void* CUtlBuffer::DetachMemory() { // Reset all indices; we just changed memory
m_Get = 0; m_Put = 0; m_nTab = 0; m_Error = 0; m_nOffset = 0; return m_Memory.DetachMemory( ); }
//-----------------------------------------------------------------------------
// 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 ) ) { memcpy( pMem, &m_Memory[m_Get - m_nOffset], 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 ) ) { memcpy( pMem, &m_Memory[m_Get - m_nOffset], nSize ); m_Get += nSize; return nSize; } return 0; }
//-----------------------------------------------------------------------------
// Eats whitespace
//-----------------------------------------------------------------------------
void CUtlBuffer::EatWhiteSpace() { if ( IsText() && IsValid() ) { while ( CheckGet( sizeof(char) ) ) { if ( !V_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 ( !V_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 ( V_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 !V_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::GetString( char* pString, int nMaxChars ) { if (!IsValid()) { *pString = 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 = PeekStringLength();
if ( IsText() ) { EatWhiteSpace(); }
if ( nLen == 0 ) { *pString = 0; m_Error |= GET_OVERFLOW; return; } // Strip off the terminating NULL
if ( nLen <= nMaxChars ) { Get( pString, nLen - 1 ); pString[ nLen - 1 ] = 0; } else { Get( pString, nMaxChars - 1 ); pString[ nMaxChars - 1 ] = 0; // skip the remaining characters, EXCEPT the terminating null
// thus it's ( nLen - ( nMaxChars - 1 ) - 1 )
SeekGet( SEEK_CURRENT, nLen - nMaxChars ); }
// 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 ) { GetString( 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; return &m_Memory[ m_Get + nOffset - m_nOffset ]; }
//-----------------------------------------------------------------------------
// 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; char c; 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 'h': { if ( *pFmt == 'd' || *pFmt == 'i' ) { if ( !GetTypeText( *va_arg( list, int16 * ) ) ) return numScanned; // only support short ints, don't bother with hex
} else if ( *pFmt == 'u' ) { if ( !GetTypeText( *va_arg( list, uint16 * ) ) ) return numScanned; } else return numScanned; ++pFmt; } break;
case 'I': { if ( *pFmt++ != '6' || *pFmt++ != '4' ) return numScanned; // only support "I64d" and "I64u"
if ( *pFmt == 'd' ) { if ( !GetTypeText( *va_arg( list, int64 * ) ) ) return numScanned; } else if ( *pFmt == 'u' ) { if ( !GetTypeText( *va_arg( list, uint64 * ) ) ) return numScanned; } else { return numScanned; }
++pFmt; } break;
case 'i': case 'd': { int32 *pArg = va_arg( list, int32 * ); if ( !GetTypeText( *pArg ) ) return numScanned; } break;
case 'x': { uint32 *pArg = va_arg( list, uint32 * ); if ( !GetTypeText( *pArg, 16 ) ) return numScanned; } break;
case 'u': { uint32 *pArg = va_arg( list, uint32 * ); if ( !GetTypeText( *pArg ) ) return numScanned; } break;
case 'l': { // we currently support %lf and %lld
if ( *pFmt == 'f' ) { if ( !GetTypeText( *va_arg( list, double * ) ) ) return numScanned; } else if ( *pFmt == 'l' && *++pFmt == 'd' ) { if ( !GetTypeText( *va_arg( list, int64 * ) ) ) return numScanned; } else return numScanned; } break;
case 'f': { float *pArg = va_arg( list, float * ); if ( !GetTypeText( *pArg ) ) return numScanned; } break;
case 's': { char* s = va_arg( list, char * ); GetString( s ); } 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 = V_strlen( pToken );
// First time through on streaming, check what we already have loaded
// if we have enough loaded to do the check
int nMaxSize = Size() - ( TellGet() - m_nOffset ); if ( nMaxSize <= nLen ) { nMaxSize = Size(); } int nSizeRemaining = TellMaxPut() - TellGet();
int nGet = TellGet(); while ( nSizeRemaining >= nLen ) { bool bOverFlow = ( nSizeRemaining > nMaxSize ); int nSizeToCheck = bOverFlow ? nMaxSize : nSizeRemaining; if ( !CheckPeekGet( 0, nSizeToCheck ) ) break;
const char *pBufStart = (const char*)PeekGet(); const char *pFoundEnd = V_strnistr( pBufStart, pToken, nSizeToCheck );
// Time to be careful: if we are in a state of overflow
// (namely, there's more of the buffer beyond the current window)
// we could be looking for 'foo' for example, and find 'foobar'
// if 'foo' happens to be the last 3 characters of the current window
size_t nOffset = (size_t)pFoundEnd - (size_t)pBufStart; bool bPotentialMismatch = ( bOverFlow && ( (int)nOffset == Size() - nLen ) ); if ( !pFoundEnd || bPotentialMismatch ) { nSizeRemaining -= nSizeToCheck; if ( !pFoundEnd && ( nSizeRemaining < nLen ) ) break;
// Second time through, stream as much in as possible
// But keep the last portion of the current buffer
// since we couldn't check it against stuff outside the window
nSizeRemaining += nLen; nMaxSize = Size(); SeekGet( CUtlBuffer::SEEK_CURRENT, nSizeToCheck - nLen ); continue; }
// Seek past the end of the found string
SeekGet( CUtlBuffer::SEEK_CURRENT, (int)( nOffset + nLen ) ); return true; }
// Didn't find a match, leave the get index where it was to start with
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 = V_strlen( pEndingDelim );
int nStartGet = TellGet(); char nCurrChar; int nTokenStart = -1; EatWhiteSpace( ); while ( *pStartingDelim ) { nCurrChar = *pStartingDelim++; if ( !V_isspace((unsigned char)nCurrChar) ) { if ( tolower( GetChar() ) != tolower( nCurrChar ) ) goto parseFailed; } else { EatWhiteSpace(); } }
EatWhiteSpace(); nTokenStart = TellGet(); if ( !GetToken( pEndingDelim ) ) goto parseFailed;
nCurrentGet = TellGet(); nCharsToCopy = (int)( (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 ( !V_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 ) ) { memcpy( &m_Memory[m_Put - m_nOffset], pMem, size ); m_Put += size;
AddNullTermination( m_Put ); } }
//-----------------------------------------------------------------------------
// Writes a null-terminated string
//-----------------------------------------------------------------------------
void CUtlBuffer::PutString( const char* pString ) { if (!IsText()) { if ( pString ) { // Not text? append a null at the end.
int nLen = (int)V_strlen( pString ) + 1; Put( pString, nLen * sizeof(char) ); return; } else { PutTypeBin<char>( 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, (int)nSize ); pString = pEndl + 1; if ( *pString ) { PutTabs(); pEndl = strchr( pString, '\n' ); } else { pEndl = NULL; } } } int nLen = (int)V_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 ? V_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[8192]; int nLen = V_vsnprintf( temp, sizeof( temp ), pFmt, list ); ErrorIfNot( nLen < sizeof( temp ), ( "CUtlBuffer::VaPrintf: String overflowed buffer [%d]\n", sizeof( temp ) ) ); 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( m_Put ); }
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
// NOTE: Pass in nPut here even though it is just a copy of m_Put. This is almost always called immediately
// after modifying m_Put and this lets it stay in a register and avoid LHS on PPC.
//-----------------------------------------------------------------------------
void CUtlBuffer::AddNullTermination( int nPut ) { if ( nPut > m_nMaxPut ) { if ( !IsReadOnly() && ((m_Error & PUT_OVERFLOW) == 0) ) { // Add null termination value
if ( CheckPut( 1 ) ) { m_Memory[nPut - m_nOffset] = 0; } else { // Restore the overflow state, it was valid before...
m_Error &= ~PUT_OVERFLOW; } } m_nMaxPut = nPut; } }
//-----------------------------------------------------------------------------
// 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(); intp nCurrGet = 0; while ( nCurrGet < nInCount ) { const char *pCurr = &pBase[nCurrGet]; if ( bFromCRLF ) { const char *pNext = V_strnistr( pCurr, "\r\n", nInCount - nCurrGet ); if ( !pNext ) { outBuf.Put( pCurr, nInCount - nCurrGet ); break; }
intp nBytes = (intp)pNext - (intp)pCurr; outBuf.Put( pCurr, (int)nBytes ); outBuf.PutChar( '\n' ); nCurrGet += nBytes + 2; if ( nGet >= nCurrGet - 1 ) { --nGetDelta; } if ( nPut >= nCurrGet - 1 ) { --nPutDelta; } } else { const char *pNext = V_strnchr( pCurr, '\n', nInCount - nCurrGet ); if ( !pNext ) { outBuf.Put( pCurr, nInCount - nCurrGet ); break; }
intp nBytes = (intp)pNext - (intp)pCurr; outBuf.Put( pCurr, (int)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; }
//---------------------------------------------------------------------------
// Implementation of CUtlInplaceBuffer
//---------------------------------------------------------------------------
CUtlInplaceBuffer::CUtlInplaceBuffer( int growSize /* = 0 */, int initSize /* = 0 */, int nFlags /* = 0 */ ) : CUtlBuffer( growSize, initSize, nFlags ) { NULL; }
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; }
|