text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: The 360 VTF file format I/O class to help simplify access to 360 VTF files.
  // 360 Formatted VTF's are stored ascending 1x1 up to NxN. Disk format and unserialized
  // formats are expected to be the same.
  //
  //=====================================================================================//
  
  #include "bitmap/imageformat.h"
  #include "cvtf.h"
  #include "utlbuffer.h"
  #include "tier0/dbg.h"
  #include "tier0/mem.h"
  #include "tier2/fileutils.h"
  #include "byteswap.h"
  #include "filesystem.h"
  #include "mathlib/mathlib.h"
  #include "tier1/lzmaDecoder.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  //-----------------------------------------------------------------------------
  // Callback for UpdateOrCreate utility function - swaps a vtf file.
  //-----------------------------------------------------------------------------
  static bool VTFCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pExtraData )
  {
  	// Generate the file
  	CUtlBuffer sourceBuf;
  	CUtlBuffer targetBuf;
  	bool bOk = g_pFullFileSystem->ReadFile( pSourceName, pPathID, sourceBuf );
  	if ( bOk )
  	{
  		bOk = ConvertVTFTo360Format( pSourceName, sourceBuf, targetBuf, NULL );
  		if ( bOk )
  		{
  			bOk = g_pFullFileSystem->WriteFile( pTargetName, pPathID, targetBuf );
  		}
  	}
  
  	if ( !bOk )
  	{
  		Warning( "Failed to create %s\n", pTargetName );
  	}
  	return bOk;
  }
  
  //-----------------------------------------------------------------------------
  // Calls utility function to create .360 version of a vtf file.
  //-----------------------------------------------------------------------------
  int CVTFTexture::UpdateOrCreate( const char *pFilename, const char *pPathID, bool bForce )
  {
  	return ::UpdateOrCreate( pFilename, NULL, 0, pPathID, VTFCreateCallback, bForce, NULL );
  }
  
  //-----------------------------------------------------------------------------
  // Determine size of file, possibly smaller if skipping top mip levels.
  //-----------------------------------------------------------------------------
  int CVTFTexture::FileSize( bool bPreloadOnly, int nMipSkipCount ) const
  {
  	if ( bPreloadOnly )
  	{
  		// caller wants size of preload
  		return m_iPreloadDataSize;
  	}
  
  	const ResourceEntryInfo *pEntryInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
  	if ( !pEntryInfo )
  	{
  		// has to exist
  		Assert( 0 );
  		return 0;
  	}
  	int iImageDataOffset = pEntryInfo->resData;
  
  	if ( m_iCompressedSize )
  	{
  		// file is compressed, mip skipping is non-applicable at this stage
  		return iImageDataOffset + m_iCompressedSize;
  	}
  
  	// caller gets file size, possibly truncated due to mip skipping
  	int nFaceSize = ComputeFaceSize( nMipSkipCount );
  	return iImageDataOffset + m_nFrameCount * m_nFaceCount * nFaceSize;
  }
  
  //-----------------------------------------------------------------------------
  // Unserialization of image data from buffer
  //-----------------------------------------------------------------------------
  bool CVTFTexture::LoadImageData( CUtlBuffer &buf, bool bBufferIsVolatile, int nMipSkipCount )
  {
  	ResourceEntryInfo *pEntryInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
  	if ( !pEntryInfo )
  	{
  		// has to exist
  		Assert( 0 );
  		return false;
  	}
  	int iImageDataOffset = pEntryInfo->resData;
  
  	// Fix up the mip count + size based on how many mip levels we skip...
  	if ( nMipSkipCount > 0 )
  	{
  		if ( nMipSkipCount >= m_nMipCount )
  		{
  			nMipSkipCount = 0;
  		}
  		ComputeMipLevelDimensions( nMipSkipCount, &m_nWidth, &m_nHeight, &m_nDepth );
  		m_nMipCount -= nMipSkipCount;
  		m_nMipSkipCount += nMipSkipCount;
  	}
  
  	int iImageSize = ComputeFaceSize();
  	iImageSize = m_nFrameCount * m_nFaceCount * iImageSize;
  
  	// seek to start of image data
  	// The mip levels are stored on disk ascending from smallest (1x1) to largest (NxN) to allow for picmip truncated reads
  	buf.SeekGet( CUtlBuffer::SEEK_HEAD, iImageDataOffset ); 
  
  	CLZMA lzma;
  	if ( m_iCompressedSize )
  	{
  		unsigned char *pCompressedData = (unsigned char *)buf.PeekGet();
  		if ( !lzma.IsCompressed( pCompressedData ) )
  		{
  			// huh? header says it was compressed
  			Assert( 0 );
  			return false;
  		}
  	
  		// have to decode entire image
  		unsigned int originalSize = lzma.GetActualSize( pCompressedData );
  		AllocateImageData( originalSize );
  		unsigned int outputLength = lzma.Uncompress( pCompressedData, m_pImageData );
  		return ( outputLength == originalSize );		
  	}
  
  	bool bOK;
  	if ( bBufferIsVolatile )
  	{
  		AllocateImageData( iImageSize );
  		buf.Get( m_pImageData, iImageSize );
  		bOK = buf.IsValid();
  	}
  	else
  	{
  		// safe to alias
  		m_pImageData = (unsigned char *)buf.PeekGet( iImageSize, 0 );
  		bOK = ( m_pImageData != NULL );
  	}
  
  	return bOK;
  }
  
  //-----------------------------------------------------------------------------
  // Unserialization
  //-----------------------------------------------------------------------------
  bool CVTFTexture::ReadHeader( CUtlBuffer &buf, VTFFileHeaderX360_t &header )
  {
  	memset( &header, 0, sizeof( VTFFileHeaderX360_t ) );
  	buf.GetObjects( &header );
  	if ( !buf.IsValid() )
  	{
  		Warning( "*** Error getting header from a X360 VTF file.\n" );
  		return false;
  	}
  
  	// Validity check
  	if ( Q_strncmp( header.fileTypeString, "VTFX", 4 ) )
  	{
  		Warning( "*** Tried to load a PC VTF file as a X360 VTF file!\n" );
  		return false;
  	}
  
  	if ( header.version[0] != VTF_X360_MAJOR_VERSION && header.version[1] != VTF_X360_MINOR_VERSION )
  	{
  		Warning( "*** Encountered X360 VTF file with an invalid version!\n" );
  		return false;
  	}
  
  	if ( ( header.flags & TEXTUREFLAGS_ENVMAP ) && ( header.width != header.height ) )
  	{
  		Warning( "*** Encountered X360 VTF non-square cubemap!\n" );
  		return false;
  	}
  
  	if ( ( header.flags & TEXTUREFLAGS_ENVMAP ) && ( header.depth != 1 ) )
  	{
  		Warning( "*** Encountered X360 VTF volume texture cubemap!\n" );
  		return false;
  	}
  
  	if ( header.width <= 0 || header.height <= 0 || header.depth <= 0 )
  	{
  		Warning( "*** Encountered X360 VTF invalid texture size!\n" );
  		return false;
  	}
  
  	return true;
  }
  
  //-----------------------------------------------------------------------------
  // Unserialization. Can optionally alias image components to a non-volatile buffer,
  // which prevents unecessary copies.  Disk format and memory format of the image
  // components are explicitly the same.
  //-----------------------------------------------------------------------------
  bool CVTFTexture::UnserializeFromBuffer( CUtlBuffer &buf, bool bBufferIsVolatile, bool bHeaderOnly, bool bPreloadOnly, int nMipSkipCount )
  {
  	VTFFileHeaderX360_t header;
  	ResourceEntryInfo	*pEntryInfo;
  
  	if ( !ReadHeader( buf, header ) )
  	{
  		return false;
  	}
  
  	// must first release any prior owned memory or reset aliases, otherwise corruption if types intermingled
  	ReleaseImageMemory();
  	ReleaseResources();
  
  	m_nVersion[0] = header.version[0];
  	m_nVersion[1] = header.version[1];
  
  	m_nWidth = header.width;
  	m_nHeight = header.height;
  	m_nDepth = header.depth;
  	m_Format = header.imageFormat;
  	m_nFlags = header.flags;
  	m_nFrameCount = header.numFrames;
  	m_nFaceCount = ( m_nFlags & TEXTUREFLAGS_ENVMAP ) ? CUBEMAP_FACE_COUNT-1 : 1;
  	m_nMipCount = ComputeMipCount();
  	m_nMipSkipCount = header.mipSkipCount;
  	m_vecReflectivity = header.reflectivity;
  	m_flBumpScale = header.bumpScale;
  	m_iPreloadDataSize = header.preloadDataSize;
  	m_iCompressedSize = header.compressedSize;
  
  	m_LowResImageFormat = IMAGE_FORMAT_RGB888;
  	if ( header.lowResImageSample[3] )
  	{
  		// nonzero denotes validity of color value
  		m_nLowResImageWidth = 1;
  		m_nLowResImageHeight = 1;
  		*(unsigned int *)m_LowResImageSample = *(unsigned int *)header.lowResImageSample;
  	}
  	else
  	{
  		m_nLowResImageWidth = 0;
  		m_nLowResImageHeight = 0;
  		*(unsigned int *)m_LowResImageSample = 0;
  	}
  
  	// 360 always has the image resource
  	Assert( header.numResources >= 1 );
  	m_arrResourcesInfo.SetCount( header.numResources );
  	m_arrResourcesData.SetCount( header.numResources );
  
  	// Read the dictionary of resources info
  	buf.Get( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) );
  	if ( !buf.IsValid() )
  	{
  		return false;
  	}
  
  	pEntryInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
  	if ( !pEntryInfo )
  	{
  		// not optional, has to be present
  		Assert( 0 );
  		return false;
  	}
  
  	if ( bHeaderOnly )
  	{
  		// caller wants header components only
  		// resource data chunks are NOT unserialized!
  		return true;
  	}
  
  	if ( !LoadNewResources( buf ) )
  	{
  		return false;
  	}
  
  	if ( bPreloadOnly )
  	{
  		// caller wants preload portion only, everything up to the image
  		return true;
  	}
  
  	if ( !LoadImageData( buf, bBufferIsVolatile, nMipSkipCount ) )
  	{
  		return false;
  	}
  
  	return true;
  }
  
  //-----------------------------------------------------------------------------
  // Discard image data to free up memory.
  //-----------------------------------------------------------------------------
  void CVTFTexture::ReleaseImageMemory()
  {
  	// valid sizes identify locally owned memory
  	if ( m_nImageAllocSize )
  	{
  		delete [] m_pImageData;
  		m_nImageAllocSize = 0;
  	}
  
  	// block pointers could be owned or aliased, always clear
  	// ensures other caller's don't free an aliased pointer
  	m_pImageData = NULL;
  }
  
  //-----------------------------------------------------------------------------
  // Attributes...
  //-----------------------------------------------------------------------------
  bool CVTFTexture::IsPreTiled() const
  {
  	return false; 
  }
  
  int CVTFTexture::MappingWidth() const
  {
  	return m_nWidth << m_nMipSkipCount;
  }
  
  int CVTFTexture::MappingHeight() const
  {
  	return m_nHeight << m_nMipSkipCount;
  }
  
  int CVTFTexture::MappingDepth() const
  {
  	return m_nDepth << m_nMipSkipCount;
  }
  
  int CVTFTexture::MipSkipCount() const
  {
  	return m_nMipSkipCount;
  }
  
  unsigned char *CVTFTexture::LowResImageSample()
  {
  	return &m_LowResImageSample[0];
  }
  