csgo-2018-source/tier2/utlstreambuffer.cpp

419 lines
11 KiB
C++
Raw Normal View History

2021-07-25 12:11:47 +08:00
//====== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. =======//
//
// Purpose:
//
// $NoKeywords: $
//
// Serialization/unserialization buffer
//=============================================================================//
#include "tier2/utlstreambuffer.h"
#include "tier2/tier2.h"
#include "filesystem.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// default stream chunk size
//-----------------------------------------------------------------------------
enum
{
DEFAULT_STREAM_CHUNK_SIZE = 16 * 1024
};
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CUtlStreamBuffer::CUtlStreamBuffer( ) : BaseClass( DEFAULT_STREAM_CHUNK_SIZE, DEFAULT_STREAM_CHUNK_SIZE, 0 )
{
SetUtlBufferOverflowFuncs( &CUtlStreamBuffer::StreamGetOverflow, &CUtlStreamBuffer::StreamPutOverflow );
m_hFileHandle = FILESYSTEM_INVALID_HANDLE;
m_pFileName = NULL;
m_pPath = NULL;
}
CUtlStreamBuffer::CUtlStreamBuffer( const char *pFileName, const char *pPath, int nFlags, bool bDelayOpen, int nOpenFileFlags ) :
BaseClass( DEFAULT_STREAM_CHUNK_SIZE, DEFAULT_STREAM_CHUNK_SIZE, nFlags )
{
if ( nFlags & TEXT_BUFFER )
{
Warning( "CUtlStreamBuffer does not support TEXT_BUFFER's use CUtlBuffer\n" );
Assert( 0 );
m_Error |= FILE_OPEN_ERROR;
return;
}
SetUtlBufferOverflowFuncs( &CUtlStreamBuffer::StreamGetOverflow, &CUtlStreamBuffer::StreamPutOverflow );
if ( bDelayOpen )
{
int nFileNameLen = Q_strlen( pFileName );
m_pFileName = new char[ nFileNameLen + 1 ];
Q_strcpy( m_pFileName, pFileName );
if ( pPath )
{
int nPathLen = Q_strlen( pPath );
m_pPath = new char[ nPathLen + 1 ];
Q_strcpy( m_pPath, pPath );
}
else
{
m_pPath = new char[ 1 ];
m_pPath[0] = 0;
}
m_nOpenFileFlags = nOpenFileFlags;
m_hFileHandle = FILESYSTEM_INVALID_HANDLE;
}
else
{
m_pFileName = NULL;
m_pPath = NULL;
m_nOpenFileFlags = 0;
m_hFileHandle = OpenFile( pFileName, pPath, nOpenFileFlags );
if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE )
{
m_Error |= FILE_OPEN_ERROR;
return;
}
}
if ( IsReadOnly() )
{
// NOTE: MaxPut may not actually be this exact size for text files;
// it could be slightly less owing to the /r/n -> /n conversion
m_nMaxPut = g_pFullFileSystem->Size( m_hFileHandle );
// Read in the first bytes of the file
if ( Size() > 0 )
{
int nSizeToRead = MIN( Size(), m_nMaxPut );
ReadBytesFromFile( nSizeToRead, 0 );
}
}
}
void CUtlStreamBuffer::Close()
{
if ( !IsReadOnly() )
{
// Write the final bytes
int nBytesToWrite = TellPut() - m_nOffset;
if ( nBytesToWrite > 0 )
{
if ( ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) && m_pFileName )
{
m_hFileHandle = OpenFile( m_pFileName, m_pPath, m_nOpenFileFlags );
}
if ( m_hFileHandle != FILESYSTEM_INVALID_HANDLE )
{
if ( g_pFullFileSystem )
g_pFullFileSystem->Write( Base(), nBytesToWrite, m_hFileHandle );
}
}
}
if ( m_hFileHandle != FILESYSTEM_INVALID_HANDLE )
{
if ( g_pFullFileSystem )
g_pFullFileSystem->Close( m_hFileHandle );
m_hFileHandle = FILESYSTEM_INVALID_HANDLE;
}
if ( m_pFileName )
{
delete[] m_pFileName;
m_pFileName = NULL;
}
if ( m_pPath )
{
delete[] m_pPath;
m_pPath = NULL;
}
m_Error = 0;
}
CUtlStreamBuffer::~CUtlStreamBuffer()
{
Close();
}
//-----------------------------------------------------------------------------
// Open the file. normally done in constructor
//-----------------------------------------------------------------------------
void CUtlStreamBuffer::Open( const char *pFileName, const char *pPath, int nFlags, int nOpenFileFlags )
{
if ( IsOpen() )
{
Close();
}
m_Get = 0;
m_Put = 0;
m_nTab = 0;
m_nOffset = 0;
m_Flags = nFlags;
m_hFileHandle = OpenFile( pFileName, pPath, nOpenFileFlags );
if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE )
{
m_Error |= FILE_OPEN_ERROR;
return;
}
if ( IsReadOnly() )
{
// NOTE: MaxPut may not actually be this exact size for text files;
// it could be slightly less owing to the /r/n -> /n conversion
m_nMaxPut = g_pFullFileSystem->Size( m_hFileHandle );
// Read in the first bytes of the file
if ( Size() > 0 )
{
int nSizeToRead = MIN( Size(), m_nMaxPut );
ReadBytesFromFile( nSizeToRead, 0 );
}
}
else
{
if ( m_Memory.NumAllocated() != 0 )
{
m_nMaxPut = -1;
AddNullTermination( m_Put );
}
else
{
m_nMaxPut = 0;
}
}
}
//-----------------------------------------------------------------------------
// Is the file open?
//-----------------------------------------------------------------------------
bool CUtlStreamBuffer::IsOpen() const
{
if ( m_hFileHandle != FILESYSTEM_INVALID_HANDLE )
return true;
// Delayed open case
return ( m_pFileName != 0 );
}
//-----------------------------------------------------------------------------
// Grow allocation size to fit requested size
//-----------------------------------------------------------------------------
void CUtlStreamBuffer::GrowAllocatedSize( int nSize )
{
int nNewSize = Size();
if ( nNewSize < nSize + 1 )
{
while ( nNewSize < nSize + 1 )
{
nNewSize += DEFAULT_STREAM_CHUNK_SIZE;
}
m_Memory.Grow( nNewSize - Size() );
}
}
//-----------------------------------------------------------------------------
// Commit some of the stream to disk when we overflow.
//-----------------------------------------------------------------------------
bool CUtlStreamBuffer::StreamPutOverflow( int nSize )
{
if ( !IsValid() || IsReadOnly() )
return false;
// Make sure the allocated size is at least as big as the requested size
if ( nSize > 0 )
{
GrowAllocatedSize( nSize + 2 );
}
// m_nOffset represents the location in the virtual buffer of m_Memory[0].
// Compute the number of bytes that we've buffered up in memory so that we know what to write to disk.
int nBytesToWrite = TellPut() - m_nOffset;
if ( ( nBytesToWrite > 0 ) || ( nSize < 0 ) )
{
if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE )
{
m_hFileHandle = OpenFile( m_pFileName, m_pPath, m_nOpenFileFlags );
}
}
// Write out the data that we have buffered if we have any.
if ( nBytesToWrite > 0 )
{
int nBytesWritten = g_pFullFileSystem->Write( Base(), nBytesToWrite, m_hFileHandle );
if ( nBytesWritten != nBytesToWrite )
return false;
// Set the offset to the current Put location to indicate that the buffer is now empty.
m_nOffset = TellPut();
}
if ( nSize < 0 )
{
m_nOffset = -nSize-1;
g_pFullFileSystem->Seek( m_hFileHandle, m_nOffset, FILESYSTEM_SEEK_HEAD );
}
return true;
}
//-----------------------------------------------------------------------------
// Commit some of the stream to disk upon requests
//-----------------------------------------------------------------------------
bool CUtlStreamBuffer::TryFlushToFile( int nFlushToFileBytes )
{
if ( !IsValid() || IsReadOnly() || ( m_Error & PUT_OVERFLOW ) )
return false;
// m_nOffset represents the location in the virtual buffer of m_Memory[0].
// Compute the number of bytes that we've buffered up in memory so that we know what to write to disk.
int nBytesToWrite = TellPut() - m_nOffset;
if ( nFlushToFileBytes < nBytesToWrite )
nBytesToWrite = nFlushToFileBytes; // cannot write more than what we have, but can flush beginning of buffer up to certain amount
if ( nBytesToWrite <= 0 )
return true; // nothing buffered to write
if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE )
{
m_hFileHandle = OpenFile( m_pFileName, m_pPath, m_nOpenFileFlags );
}
// Write out the data that we have buffered if we have any.
int nBytesWritten = g_pFullFileSystem->Write( Base(), nBytesToWrite, m_hFileHandle );
if ( nBytesWritten != nBytesToWrite )
{
m_Error |= PUT_OVERFLOW; // Flag the buffer the same way as if put overflow callback failed
return false; // Let caller know about file IO failure
}
// Move the uncommitted data over and advance the offset up by as many bytes
memmove( Base(), ( const char* ) Base() + nBytesWritten, ( TellPut() - m_nOffset ) - nBytesWritten + 1 ); // + 1 byte for null termination character
m_nOffset += nBytesWritten;
return true;
}
//-----------------------------------------------------------------------------
// Reads bytes from the file; fixes up maxput if necessary and null terminates
//-----------------------------------------------------------------------------
int CUtlStreamBuffer::ReadBytesFromFile( int nBytesToRead, int nReadOffset )
{
if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE )
{
if ( !m_pFileName )
{
Warning( "File has not been opened!\n" );
Assert(0);
return 0;
}
m_hFileHandle = OpenFile( m_pFileName, m_pPath, m_nOpenFileFlags );
if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE )
{
Error( "Unable to read file %s!\n", m_pFileName );
return 0;
}
if ( m_nOffset != 0 )
{
g_pFullFileSystem->Seek( m_hFileHandle, m_nOffset, FILESYSTEM_SEEK_HEAD );
}
}
char *pReadPoint = (char*)Base() + nReadOffset;
int nBytesRead = g_pFullFileSystem->Read( pReadPoint, nBytesToRead, m_hFileHandle );
if ( nBytesRead != nBytesToRead )
{
// Since max put is a guess at the start,
// we need to shrink it based on the actual # read
if ( m_nMaxPut > TellGet() + nReadOffset + nBytesRead )
{
m_nMaxPut = TellGet() + nReadOffset + nBytesRead;
}
}
if ( nReadOffset + nBytesRead < Size() )
{
// This is necessary to deal with auto-NULL terminiation
pReadPoint[nBytesRead] = 0;
}
return nBytesRead;
}
//-----------------------------------------------------------------------------
// Load up more of the stream when we overflow
//-----------------------------------------------------------------------------
bool CUtlStreamBuffer::StreamGetOverflow( int nSize )
{
if ( !IsValid() || !IsReadOnly() )
return false;
// Shift the unread bytes down
// NOTE: Can't use the partial overlap path if we're seeking. We'll
// get negative sizes passed in if we're seeking.
int nUnreadBytes;
bool bHasPartialOverlap = ( nSize >= 0 ) && ( TellGet() >= m_nOffset ) && ( TellGet() <= m_nOffset + Size() );
if ( bHasPartialOverlap )
{
nUnreadBytes = Size() - ( TellGet() - m_nOffset );
if ( ( TellGet() != m_nOffset ) && ( nUnreadBytes > 0 ) )
{
memmove( Base(), (const char*)Base() + TellGet() - m_nOffset, nUnreadBytes );
}
}
else
{
m_nOffset = TellGet();
g_pFullFileSystem->Seek( m_hFileHandle, m_nOffset, FILESYSTEM_SEEK_HEAD );
nUnreadBytes = 0;
}
// Make sure the allocated size is at least as big as the requested size
if ( nSize > 0 )
{
GrowAllocatedSize( nSize );
}
int nBytesToRead = Size() - nUnreadBytes;
int nBytesRead = ReadBytesFromFile( nBytesToRead, nUnreadBytes );
if ( nBytesRead == 0 )
return false;
m_nOffset = TellGet();
return ( nBytesRead + nUnreadBytes >= nSize );
}
//-----------------------------------------------------------------------------
// open file unless already failed to open
//-----------------------------------------------------------------------------
FileHandle_t CUtlStreamBuffer::OpenFile( const char *pFileName, const char *pPath, int nOpenFileFlags )
{
if ( m_Error & FILE_OPEN_ERROR )
return FILESYSTEM_INVALID_HANDLE;
char options[ 3 ] = "xx";
options[ 0 ] = IsReadOnly() ? 'r' : 'w';
options[ 1 ] = IsText() && !ContainsCRLF() ? 't' : 'b';
return g_pFullFileSystem->OpenEx( pFileName, options, nOpenFileFlags, pPath );
}