//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "tier1/strtools.h"
#include <sys/stat.h>
#include "bitmap/bitmap.h"
#include "bitmap/tgaloader.h"
#include "bitmap/psd.h"
#include "bitmap/float_bm.h"
#include "bitmap/imageformat.h"
#include "mathlib/mathlib.h"

#ifdef POSIX
#include <sys/stat.h>
#define _stat stat
#endif

#ifdef WIN32
#include "conio.h"
#include <direct.h>
#include <io.h>
#endif

#include "vtf/vtf.h"
#include "utlbuffer.h"
#include "tier0/dbg.h"
#include "cmdlib.h"
#include "tier0/icommandline.h"
#ifdef WIN32
#include "windows.h"
#endif
#include "ilaunchabledll.h"
#include "ivtex.h"
#include "appframework/IAppSystemGroup.h"

#include "tier2/tier2.h"
#include "tier1/checksum_crc.h"
#include "imageutils.h"

#define FF_TRYAGAIN 1
#define FF_DONTPROCESS 2

#define LOWRESIMAGE_DIM 16

#ifdef POSIX
#define LOWRES_IMAGE_FORMAT IMAGE_FORMAT_RGBA8888
#else
#define LOWRES_IMAGE_FORMAT IMAGE_FORMAT_DXT1
#endif

//#define DEBUG_NO_COMPRESSION
static bool g_Quiet = false;
static const char *g_ShaderName = NULL;
static bool g_CreateDir = true;
static bool g_UseGameDir = true;

static bool g_bWarningsAsErrors = false;
static bool g_bUsedAsLaunchableDLL = false;

static char g_ForcedOutputDir[MAX_PATH];


#define MAX_VMT_PARAMS	16

struct VTexVMTParam_t
{
	const char *m_szParam;
	const char *m_szValue;
};

class SmartIVtfTexture
{
public:
	explicit SmartIVtfTexture( IVTFTexture *pVtf ) : m_p( pVtf ) {}
	~SmartIVtfTexture() { if ( m_p ) DestroyVTFTexture( m_p ); }

private:
	SmartIVtfTexture( SmartIVtfTexture const &x );
	SmartIVtfTexture & operator = ( SmartIVtfTexture const &x );

private:
	SmartIVtfTexture & operator = ( IVTFTexture *pVtf ) { m_p = pVtf; return *this; }
	operator IVTFTexture * () const { return m_p; }

public:
	IVTFTexture * Assign( IVTFTexture *pVtfNew ) { IVTFTexture *pOld = m_p; m_p = pVtfNew; return pOld; }
	IVTFTexture * Get() const { return m_p; }
	IVTFTexture * operator->() const { return m_p; }

protected:
	IVTFTexture *m_p;
};

static VTexVMTParam_t g_VMTParams[MAX_VMT_PARAMS];

static int g_NumVMTParams = 0;
static enum Mode { eModePSD, eModeTGA, eModePFM, eModePNG } g_eMode = eModePSD;

// NOTE: these must stay in the same order as CubeMapFaceIndex_t.
static const char *g_CubemapFacingNames[7] = { "rt", "lf", "bk", "ft", "up", "dn", "sph" };

static bool VTexErrorAborts()
{
	if ( CommandLine()->FindParm( "-crcvalidate" ) )
		return false;

	return true;
}


static void VTexError( const char *pFormat, ... )
{
	char str[4096];
	va_list marker;
	va_start( marker, pFormat );
	Q_vsnprintf( str, sizeof( str ), pFormat, marker );
	va_end( marker );

	if ( !VTexErrorAborts() )
	{
		fprintf( stderr, "ERROR: %s", str );
		return;
	}


	fprintf( stderr, "ERROR: %s", str );
	exit( 1 );	
}


static void VTexWarning( const char *pFormat, ... )
{
	char str[4096];
	va_list marker;
	va_start( marker, pFormat );
	Q_vsnprintf( str, sizeof( str ), pFormat, marker );
	va_end( marker );

	if ( g_bWarningsAsErrors )
	{
		VTexError( "%s", str );
	}
	else
	{
		fprintf( stderr, "WARN: %s", str );
	}	
}



struct VTexConfigInfo_t
{
	int m_nStartFrame;
	int m_nEndFrame;
	unsigned int m_nFlags;
	float m_flBumpScale;
	LookDir_t m_LookDir;
	bool m_bNormalToDuDv;
	bool m_bAlphaToLuminance;
	bool m_bDuDv;
	float m_flAlphaThreshhold;
	float m_flAlphaHiFreqThreshhold;
	bool m_bSkyBox;
	int m_nVolumeTextureDepth;
	float m_pfmscale;
	bool m_bStripAlphaChannel;
	bool m_bStripColorChannel;
	bool m_bIsCubeMap;


	// scaling parameters
	int m_nReduceX;
	int m_nReduceY;

	int m_nMaxDimensionX, m_nMaxDimensionX_360;
	int m_nMaxDimensionY, m_nMaxDimensionY_360;

	// may restrict the texture to reading only 3 channels
	int m_numChannelsMax;

	bool m_bAlphaToDistance;
	float m_flDistanceSpread;					 // how far to stretch out distance range in pixels

	CRC32_t m_uiInputHash;	// Sources hash

	TextureSettingsEx_t m_exSettings0;
	VtfProcessingOptions m_vtfProcOptions;

	enum
	{
		// CRC of input files:
		//  txt + tga/pfm
		// or
		//  psd
		VTF_INPUTSRC_CRC = MK_VTF_RSRC_ID( 'C','R','C' )
	};

	char m_SrcName[MAX_PATH];

	VTexConfigInfo_t( void )
	{
		m_nStartFrame = -1;
		m_nEndFrame = -1;
		m_nFlags = 0;
		m_bNormalToDuDv = false;
		m_bAlphaToLuminance = false;
		m_flBumpScale = 1.0f;
		m_bDuDv = false;
		m_flAlphaThreshhold = -1.0f;
		m_flAlphaHiFreqThreshhold = -1.0f;
		m_bSkyBox = false;
		m_nVolumeTextureDepth = 1;
		m_pfmscale=1.0;
		m_bStripAlphaChannel = false;
		m_bStripColorChannel = false;
		m_bIsCubeMap = false;
		m_nReduceX = 1;
		m_nReduceY = 1;
		m_SrcName[0]=0;
		m_numChannelsMax = 4;
		m_bAlphaToDistance = 0;
		m_flDistanceSpread = 1.0;
		m_nMaxDimensionX = -1;
		m_nMaxDimensionX_360 = -1;
		m_nMaxDimensionY = -1;
		m_nMaxDimensionY_360 = -1;

		memset( &m_exSettings0, 0, sizeof( m_exSettings0 ) );

		memset( &m_vtfProcOptions, 0, sizeof( m_vtfProcOptions ) );
		m_vtfProcOptions.cbSize = sizeof( m_vtfProcOptions );
		
		m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_FILTER_NICE;

		CRC32_Init( &m_uiInputHash );
	}

	bool IsSettings0Valid( void ) const
	{
		TextureSettingsEx_t exSettingsEmpty;
		memset( &exSettingsEmpty, 0, sizeof( exSettingsEmpty ) );
		Assert( sizeof( m_exSettings0 ) == sizeof( exSettingsEmpty ) );
		return !!memcmp( &m_exSettings0, &exSettingsEmpty, sizeof( m_exSettings0 ) );
	}

	// returns false if unrecognized option
	void ParseOptionKey( const char *pKeyName,  const char *pKeyValue );

};

template < typename T >
static inline T& SetFlagValueT( T &field, T const &flag, int bSetFlag )
{
	if ( bSetFlag )
		field |= flag;
	else
		field &=~flag;

	return field;
}

static inline uint32& SetFlagValue( uint32 &field, uint32 const &flag, int bSetFlag )
{
	return SetFlagValueT<uint32>( field, flag, bSetFlag );
}

void VTexConfigInfo_t::ParseOptionKey( const char *pKeyName,  const char *pKeyValue )
{
	int iValue = atoi( pKeyValue ); // To properly have "clamps 0" and not enable the clamping

	if ( !stricmp( pKeyName, "skybox" ) )
	{
		// We're going to treat it like a cubemap until the very end, so it'll load the other skybox faces and
		// match their edges with the texture compression and mipmapping.
		m_bSkyBox = iValue ? true : false;
		m_bIsCubeMap = iValue ? true : false;
		if ( !g_Quiet && iValue )
			Msg( "'skybox' detected. Treating skybox like a cubemap for edge-matching purposes.\n" );
	}
	else if( !stricmp( pKeyName, "startframe" ) )
	{
		m_nStartFrame = atoi( pKeyValue );
	}
	else if( !stricmp( pKeyName, "endframe" ) )
	{
		m_nEndFrame = atoi( pKeyValue );
	}
	else if( !stricmp( pKeyName, "volumetexture" ) )
	{
		m_nVolumeTextureDepth = atoi( pKeyValue );

		// FIXME: Volume textures don't currently support DXT compression
		m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_NOCOMPRESS;

		// FIXME: Volume textures don't currently support NICE filtering
		m_vtfProcOptions.flags0 &= ~VtfProcessingOptions::OPT_FILTER_NICE;
	}
	else if( !stricmp( pKeyName, "spheremap_x" ) )
	{
		if ( iValue )
			m_LookDir = LOOK_DOWN_X;
	}
	else if( !stricmp( pKeyName, "spheremap_negx" ) )
	{
		if ( iValue )
			m_LookDir = LOOK_DOWN_NEGX;
	}
	else if( !stricmp( pKeyName, "spheremap_y" ) )
	{
		if ( iValue )
			m_LookDir = LOOK_DOWN_Y;
	}
	else if( !stricmp( pKeyName, "spheremap_negy" ) )
	{
		if ( iValue )
			m_LookDir = LOOK_DOWN_NEGY;
	}
	else if( !stricmp( pKeyName, "spheremap_z" ) )
	{
		if ( iValue )
			m_LookDir = LOOK_DOWN_Z;
	}
	else if( !stricmp( pKeyName, "spheremap_negz" ) )
	{
		if ( iValue )
			m_LookDir = LOOK_DOWN_NEGZ;
	}
	else if( !stricmp( pKeyName, "bumpscale" ) )
	{
		m_flBumpScale = atof( pKeyValue );
	}
	else if( !stricmp( pKeyName, "pointsample" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_POINTSAMPLE, iValue );
	}
	else if( !stricmp( pKeyName, "trilinear" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_TRILINEAR, iValue );
	}
	else if( !stricmp( pKeyName, "clamps" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_CLAMPS, iValue );
	}
	else if( !stricmp( pKeyName, "clampt" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_CLAMPT, iValue );
	}
	else if( !stricmp( pKeyName, "clampu" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_CLAMPU, iValue );
	}
	else if( !stricmp( pKeyName, "border" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_BORDER, iValue );
		// Gets applied to s, t and u   We currently assume black border color
	}
	else if( !stricmp( pKeyName, "anisotropic" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_ANISOTROPIC, iValue );
	}
	else if( !stricmp( pKeyName, "dxt5" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_HINT_DXT5, iValue );
	}
	else if( !stricmp( pKeyName, "nocompress" ) )
	{
		SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_NOCOMPRESS, iValue );
	}
	else if( !stricmp( pKeyName, "normal" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_NORMAL, iValue );
	}
	else if( !stricmp( pKeyName, "ssbump" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_SSBUMP, iValue );
	}
	else if( !stricmp( pKeyName, "nomip" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_NOMIP, iValue );
	}
	else if( !stricmp( pKeyName, "allmips" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_ALL_MIPS, iValue );
	}
	else if( !stricmp( pKeyName, "nonice" ) )
	{
		SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_FILTER_NICE, !iValue );
	}
	else if( !stricmp( pKeyName, "nolod" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_NOLOD, iValue );
	}
	else if( !stricmp( pKeyName, "procedural" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_PROCEDURAL, iValue );
	}
	else if( !stricmp( pKeyName, "alphatest" ) )
	{
		SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_MIP_ALPHATEST, iValue );
	}
	else if( !stricmp( pKeyName, "alphatest_threshhold" ) )
	{
		m_flAlphaThreshhold = atof( pKeyValue );
	}
	else if( !stricmp( pKeyName, "alphatest_hifreq_threshhold" ) )
	{
		m_flAlphaHiFreqThreshhold = atof( pKeyValue );
	}
	else if( !stricmp( pKeyName, "rendertarget" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_RENDERTARGET, iValue );
	}
	else if ( !stricmp( pKeyName, "numchannels" ) )
	{
		m_numChannelsMax = atoi( pKeyValue );
	}
	else if ( !stricmp( pKeyName, "nodebug" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_NODEBUGOVERRIDE, iValue );
	}
	else if ( !stricmp( pKeyName, "singlecopy" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_SINGLECOPY, iValue );
	}
	else if( !stricmp( pKeyName, "oneovermiplevelinalpha" ) )
	{
		SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_SET_ALPHA_ONEOVERMIP, iValue );
	}
	else if( !stricmp( pKeyName, "premultcolorbyoneovermiplevel" ) )
	{
		SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP, iValue );
	}
	else if ( !stricmp( pKeyName, "normaltodudv" ) )
	{
		m_bNormalToDuDv = iValue ? true : false;
		SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_NORMAL_DUDV, iValue );
	}
	else if ( !stricmp( pKeyName, "stripalphachannel" ) )
	{
		m_bStripAlphaChannel = iValue ? true : false;
	}
	else if ( !stricmp( pKeyName, "stripcolorchannel" ) )
	{
		m_bStripColorChannel = iValue ? true : false;
	}
	else if ( !stricmp( pKeyName, "normalalphatodudvluminance" ) )
	{
		m_bAlphaToLuminance = iValue ? true : false;
	}
	else if ( !stricmp( pKeyName, "dudv" ) )
	{
		m_bDuDv = iValue ? true : false;
	}
	else if( !stricmp( pKeyName, "reduce" ) )
	{
		m_nReduceX = atoi(pKeyValue);
		m_nReduceY = m_nReduceX;
	}
	else if( !stricmp( pKeyName, "reducex" ) )
	{
		m_nReduceX = atoi(pKeyValue);
	}
	else if( !stricmp( pKeyName, "reducey" ) )
	{
		m_nReduceY = atoi(pKeyValue);
	}
	else if( !stricmp( pKeyName, "maxwidth" ) )
	{
		m_nMaxDimensionX = atoi(pKeyValue);
	}
	else if( !stricmp( pKeyName, "maxwidth_360" ) )
	{
		m_nMaxDimensionX_360 = atoi(pKeyValue);
	}
	else if( !stricmp( pKeyName, "maxheight" ) )
	{
		m_nMaxDimensionY = atoi(pKeyValue);
	}
	else if( !stricmp( pKeyName, "maxheight_360" ) )
	{
		m_nMaxDimensionY_360 = atoi(pKeyValue);
	}
	else if( !stricmp( pKeyName, "alphatodistance" ) )
	{
		m_bAlphaToDistance = iValue ? true : false;
	}
	else if( !stricmp( pKeyName, "distancespread" ) )
	{
		m_flDistanceSpread = atof(pKeyValue);
	}
	else if( !stricmp( pKeyName, "pfmscale" ) )
	{
		m_pfmscale=atof(pKeyValue);
		printf("******pfm scale=%f\n",m_pfmscale);
	}
	else if ( !stricmp( pKeyName, "pfm" ) )
	{
		if ( iValue )
			g_eMode = eModePFM;
	}
	else if ( !stricmp( pKeyName, "specvar" ) )
	{
		int iDecayChannel = -1;

		if ( !stricmp( pKeyValue, "red" ) || !stricmp( pKeyValue, "r" ) )
			iDecayChannel = 0;
		if ( !stricmp( pKeyValue, "green" ) || !stricmp( pKeyValue, "g" ) )
			iDecayChannel = 1;
		if ( !stricmp( pKeyValue, "blue" ) || !stricmp( pKeyValue, "b" ) )
			iDecayChannel = 2;
		if ( !stricmp( pKeyValue, "alpha" ) || !stricmp( pKeyValue, "a" ) )
			iDecayChannel = 3;

		if ( iDecayChannel >= 0 && iDecayChannel < 4 )
		{
			m_vtfProcOptions.flags0 |= ( VtfProcessingOptions::OPT_DECAY_R | VtfProcessingOptions::OPT_DECAY_EXP_R ) << iDecayChannel;
			m_vtfProcOptions.numNotDecayMips[iDecayChannel] = 0;
			m_vtfProcOptions.clrDecayGoal[iDecayChannel] = 0;
			m_vtfProcOptions.fDecayExponentBase[iDecayChannel] = 0.75;
			SetFlagValue( m_nFlags, TEXTUREFLAGS_ALL_MIPS, 1 );
		}
	}
	else if ( !stricmp( pKeyName, "mipblend" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_ALL_MIPS, 1 );

		// Possible values
		if ( !stricmp( pKeyValue, "detail" ) ) // Skip 2 mips and fade to gray -> (128, 128, 128, -)
		{
			for( int ch = 0; ch < 3; ++ ch )
			{
				m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch;
				// m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch);
				m_vtfProcOptions.numNotDecayMips[ch] = 2;
				m_vtfProcOptions.clrDecayGoal[ch] = 128;
			}
		}
		/*
		else if ( !stricmp( pKeyValue, "additive" ) ) // Skip 2 mips and fade to black -> (0, 0, 0, -)
		{
			for( int ch = 0; ch < 3; ++ ch )
			{
				m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch;
				m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch);
				m_vtfProcOptions.numDecayMips[ch] = 2;
				m_vtfProcOptions.clrDecayGoal[ch] = 0;
			}
		}
		else if ( !stricmp( pKeyValue, "alphablended" ) ) // Skip 2 mips and fade out alpha to 0
		{
			for( int ch = 3; ch < 4; ++ ch )
			{
				m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch;
				m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch);
				m_vtfProcOptions.numDecayMips[ch] = 2;
				m_vtfProcOptions.clrDecayGoal[ch] = 0;
			}
		}
		*/
		else
		{
			// Parse the given value:
			// skip=3:r=255:g=255:b=255:a=255  - linear decay
			// r=0e.75 - exponential decay targeting 0 with exponent base 0.75

			int nSteps = 0; // default
			
			for ( char const *szParse = pKeyValue; szParse; szParse = strchr( szParse, ':' ), szParse ? ++ szParse : 0 )
			{
				if ( char const *sz = StringAfterPrefix( szParse, "skip=" ) )
				{
					szParse = sz;
					nSteps = atoi(sz);
				}
				else if ( StringHasPrefix( szParse, "r=" ) ||
					      StringHasPrefix( szParse, "g=" ) ||
						  StringHasPrefix( szParse, "b=" ) ||
						  StringHasPrefix( szParse, "a=" ) )
				{
					int ch = 0;
					switch ( *szParse )
					{
					case 'g': case 'G': ch = 1; break;
					case 'b': case 'B': ch = 2; break;
					case 'a': case 'A': ch = 3; break;
					}
					
					szParse += 2;
					m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch;
					m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch);
					m_vtfProcOptions.numNotDecayMips[ch] = nSteps;
					m_vtfProcOptions.clrDecayGoal[ch] = atoi( szParse );
					
					while ( isdigit( *szParse ) )
						++ szParse;

					// Exponential decay
					if ( ( *szParse == 'e' || *szParse == 'E' ) && ( szParse[1] == '.' ) )
					{
						m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_EXP_R << ch;
						m_vtfProcOptions.fDecayExponentBase[ch] = ( float ) atof( szParse + 1 );
					}
				}
				else
				{
					printf( "Warning: invalid mipblend setting \"%s\"\n", pKeyValue );
				}
			}
		}
	}
	else if( !stricmp( pKeyName, "srgb" ) )
	{
		SetFlagValue( m_nFlags, TEXTUREFLAGS_SRGB, iValue );
	}
	else
	{
		VTexError("unrecognized option in text file - %s\n", pKeyName );
	}
}




static const char *GetSourceExtension( void )
{
	switch ( g_eMode )
	{
	case eModePSD:
		return ".psd";
	case eModeTGA:
		return ".tga";
	case eModePFM:
		return ".pfm";
	case eModePNG:
		return ".png";
	default:
		return ".tga";
	}
}


//-----------------------------------------------------------------------------
// Computes the desired texture format based on flags
//-----------------------------------------------------------------------------
static ImageFormat ComputeDesiredImageFormat( IVTFTexture *pTexture, VTexConfigInfo_t &info )
{
	bool bDUDVTarget = info.m_bNormalToDuDv || info.m_bDuDv;
	bool bCopyAlphaToLuminance = info.m_bNormalToDuDv && info.m_bAlphaToLuminance;

	ImageFormat targetFormat;

	int nFlags = pTexture->Flags();
	if ( info.m_bStripAlphaChannel )
	{
		nFlags &= ~( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA );
	}

	// HDRFIXME: Need to figure out what format to use here.
	if ( pTexture->Format() == IMAGE_FORMAT_RGB323232F )
	{
#ifndef DEBUG_NO_COMPRESSION
		if ( g_bUsedAsLaunchableDLL && !( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NOCOMPRESS ) )
		{
			return IMAGE_FORMAT_BGRA8888;
		}
		else
#endif // #ifndef DEBUG_NO_COMPRESSION
		{
			return IMAGE_FORMAT_RGBA16161616F;
		}
	}

	if ( bDUDVTarget )
	{
		if ( bCopyAlphaToLuminance && ( nFlags & ( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ) ) )
			return IMAGE_FORMAT_UVLX8888;
		return IMAGE_FORMAT_UV88;
	}

	if ( info.m_bStripColorChannel )
	{
		return IMAGE_FORMAT_A8;
	}

	// can't compress textures that are smaller than 4x4
	if( (nFlags & TEXTUREFLAGS_PROCEDURAL) ||
		(info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NOCOMPRESS) ||
		(pTexture->Width() < 4) || (pTexture->Height() < 4) )
	{
		if ( nFlags & ( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ) )
		{
			targetFormat = IMAGE_FORMAT_BGRA8888;
		}
		else
		{
			targetFormat = IMAGE_FORMAT_BGR888;
		}
	}
	else if( nFlags & TEXTUREFLAGS_HINT_DXT5 )
	{
#ifdef DEBUG_NO_COMPRESSION
		targetFormat = IMAGE_FORMAT_BGRA8888;
#else
		targetFormat = IsPosix() ? IMAGE_FORMAT_BGRA8888 : IMAGE_FORMAT_DXT5; // No DXT compressor on Posix
#endif
	}
	else if( nFlags & TEXTUREFLAGS_EIGHTBITALPHA )
	{
		// compressed with alpha blending
#ifdef DEBUG_NO_COMPRESSION
		targetFormat = IMAGE_FORMAT_BGRA8888;
#else
		targetFormat = IsPosix() ? IMAGE_FORMAT_BGRA8888 : IMAGE_FORMAT_DXT5; // No DXT compressor on Posix
#endif
	}
	else if ( nFlags & TEXTUREFLAGS_ONEBITALPHA )
	{
		// garymcthack - fixme IMAGE_FORMAT_DXT1_ONEBITALPHA doesn't work yet.
#ifdef DEBUG_NO_COMPRESSION
		targetFormat = IMAGE_FORMAT_BGRA8888;
#else
		//		targetFormat = IMAGE_FORMAT_DXT1_ONEBITALPHA;
		targetFormat = IsPosix() ? IMAGE_FORMAT_BGRA8888 : IMAGE_FORMAT_DXT5; // No DXT compressor on Posix
#endif
	}
	else
	{
#ifdef DEBUG_NO_COMPRESSION
		targetFormat = IMAGE_FORMAT_BGR888;
#else
		targetFormat = IsPosix() ? IMAGE_FORMAT_BGR888 : IMAGE_FORMAT_DXT1; // No DXT compressor on Posix
#endif
	}
	return targetFormat;
} 


//-----------------------------------------------------------------------------
// Computes the low res image size
//-----------------------------------------------------------------------------
void VTFGetLowResImageInfo( int cacheWidth, int cacheHeight, int *lowResImageWidth, int *lowResImageHeight,
						   ImageFormat *imageFormat )
{
	if (cacheWidth > cacheHeight)
	{
		int factor = cacheWidth / LOWRESIMAGE_DIM;
		if (factor > 0)
		{
			*lowResImageWidth = LOWRESIMAGE_DIM;
			*lowResImageHeight = cacheHeight / factor;
		}
		else
		{
			*lowResImageWidth = cacheWidth;
			*lowResImageHeight = cacheHeight;
		}
	}
	else
	{
		int factor = cacheHeight / LOWRESIMAGE_DIM;
		if (factor > 0)
		{
			*lowResImageHeight = LOWRESIMAGE_DIM;
			*lowResImageWidth = cacheWidth / factor;
		}
		else
		{
			*lowResImageWidth = cacheWidth;
			*lowResImageHeight = cacheHeight;
		}
	}

	// Can end up with a dimension of zero for high aspect ration images.
	if( *lowResImageWidth < 1 )
	{
		*lowResImageWidth = 1;
	}
	if( *lowResImageHeight < 1 )
	{
		*lowResImageHeight = 1;
	}
	*imageFormat = LOWRES_IMAGE_FORMAT;
}


//-----------------------------------------------------------------------------
// This method creates the low-res image and hooks it into the VTF Texture
//-----------------------------------------------------------------------------
static void CreateLowResImage( IVTFTexture *pVTFTexture )
{
	int iWidth, iHeight;
	ImageFormat imageFormat;
	VTFGetLowResImageInfo( pVTFTexture->Width(), pVTFTexture->Height(), &iWidth, &iHeight, &imageFormat );

	// Allocate the low-res image data
	pVTFTexture->InitLowResImage( iWidth, iHeight, imageFormat );

	// Generate the low-res image bits
	if (!pVTFTexture->ConstructLowResImage())
	{
		VTexError( "Can't convert image from %s to %s in CalcLowResImage\n",
			ImageLoader::GetName(IMAGE_FORMAT_RGBA8888), ImageLoader::GetName(imageFormat) );
	}
}


//-----------------------------------------------------------------------------
// Computes the source file name
//-----------------------------------------------------------------------------
void MakeSrcFileName( char *pSrcName, unsigned int flags, const char *pFullNameWithoutExtension, int frameID, 
					 int faceID, int z, bool isCubeMap, int startFrame, int endFrame, bool bNormalToDUDV )
{
	bool bAnimated = !( startFrame == -1 || endFrame == -1 );

	char tempBuf[512];
	if( bNormalToDUDV )
	{
		char *pNormalString = Q_stristr( ( char * )pFullNameWithoutExtension, "_dudv" );
		if( pNormalString )
		{
			Q_strncpy( tempBuf, pFullNameWithoutExtension, sizeof(tempBuf) );
			char *pNormalString = Q_stristr( tempBuf, "_dudv" );
			Q_strcpy( pNormalString, "_normal" );
			pFullNameWithoutExtension = tempBuf;
		}
		else
		{
			Assert( Q_stristr( ( char * )pFullNameWithoutExtension, "_dudv" ) );
		}
	}

	if( bAnimated )
	{
		if( isCubeMap )
		{
			Assert( z == -1 );
			sprintf( pSrcName, "%s%s%03d%s", pFullNameWithoutExtension, g_CubemapFacingNames[faceID], frameID + startFrame, GetSourceExtension() );
		}
		else
		{
			if ( z == -1 )
			{
				sprintf( pSrcName, "%s%03d%s", pFullNameWithoutExtension, frameID + startFrame, GetSourceExtension() );
			}
			else
			{
				sprintf( pSrcName, "%s%03d_z%03d%s", pFullNameWithoutExtension, z, frameID + startFrame, GetSourceExtension() );
			}
		}
	}
	else
	{
		if( isCubeMap )
		{
			Assert( z == -1 );
			sprintf( pSrcName, "%s%s%s", pFullNameWithoutExtension, g_CubemapFacingNames[faceID], GetSourceExtension() );
		}
		else
		{
			if ( z == -1 )
			{
				sprintf( pSrcName, "%s%s", pFullNameWithoutExtension, GetSourceExtension() );
			}
			else
			{
				sprintf( pSrcName, "%s_z%03d%s", pFullNameWithoutExtension, z, GetSourceExtension() );
			}
		}
	}
}

static void ComputeBufferHash( void const *pvBuffer, size_t numBytes, CRC32_t &uiHashUpdate )
{
	CRC32_ProcessBuffer( &uiHashUpdate, pvBuffer, numBytes );
}

//-----------------------------------------------------------------------------
// Loads a file into a UTLBuffer,
// also computes the hash of the buffer.
//-----------------------------------------------------------------------------
static bool LoadFile( const char *pFileName, CUtlBuffer &buf, bool bFailOnError, CRC32_t *puiHash )
{
	FILE *fp = fopen( pFileName, "rb" );
	if (!fp)
	{
		if ( bFailOnError )
			VTexError( "Can't open: \"%s\"\n", pFileName );

		return false;
	}

	fseek( fp, 0, SEEK_END );
	int nFileLength = ftell( fp );
	fseek( fp, 0, SEEK_SET );

	buf.EnsureCapacity( nFileLength );
	int nBytesRead = fread( buf.Base(), 1, nFileLength, fp );
	fclose( fp );

	buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );

	// Auto-compute buffer hash if necessary
	if ( puiHash )
		ComputeBufferHash( buf.Base(), nBytesRead, *puiHash );

	return true;
}


//-----------------------------------------------------------------------------
// Creates a texture the size of the PSD image stored in the buffer
//-----------------------------------------------------------------------------
static void InitializeSrcTexture_PSD( IVTFTexture *pTexture, const char *pInputFileName,
									  CUtlBuffer &psdBuffer, int nDepth, int nFrameCount,
									  const VTexConfigInfo_t &info )
{
	int nWidth, nHeight;
	ImageFormat imageFormat;
	float flSrcGamma; 
	bool ok = PSDGetInfo( psdBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma );
	if (!ok)
	{
		Error( "PSD %s is bogus!\n", pInputFileName );
	}
	nWidth /= info.m_nReduceX;
	nHeight /= info.m_nReduceY;


	if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_DEFAULT, info.m_nFlags, nFrameCount ))
	{
		Error( "Error initializing texture %s\n", pInputFileName );
	}
}

//-----------------------------------------------------------------------------
// Creates a texture the size of the TGA image stored in the buffer
//-----------------------------------------------------------------------------
static void InitializeSrcTexture_TGA( IVTFTexture *pTexture, const char *pInputFileName,
									  CUtlBuffer &tgaBuffer, int nDepth, int nFrameCount,
									  const VTexConfigInfo_t &info )
{
	int nWidth, nHeight;
	ImageFormat imageFormat;
	float flSrcGamma; 
	bool ok = TGALoader::GetInfo( tgaBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma );
	if (!ok)
	{
		Error( "TGA %s is bogus!\n", pInputFileName );
	}
	nWidth /= info.m_nReduceX;
	nHeight /= info.m_nReduceY;

	if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_DEFAULT, info.m_nFlags, nFrameCount ))
	{
		Error( "Error initializing texture %s\n", pInputFileName );
	}
}

static void InitializeSrcTexture_PNG( IVTFTexture *pTexture, const char *pInputFileName,
									  CUtlBuffer &pngBuffer, int nDepth, int nFrameCount,
									  const VTexConfigInfo_t &info )
{
	int nWidth, nHeight;
	ImageFormat imageFormat;
	float flSrcGamma;

	ConversionErrorType error = CE_SUCCESS;
	unsigned char *data = ImgUtl_ReadPNGAsRGBAFromBuffer( pngBuffer, nWidth, nHeight, error );
	if (error != CE_SUCCESS)
		Error( "PNG %s is bogus!\n", pInputFileName );

	pngBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
	pngBuffer.Put( data, nWidth*nHeight*4 );

	nWidth /= info.m_nReduceX;
	nHeight /= info.m_nReduceY;

	FILE *f = fopen("shit", "wb");
	fwrite(pngBuffer.Base(), 1, 256*256*4, f);
	fclose(f);

	if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_DEFAULT, info.m_nFlags, nFrameCount ))
	{
		Error( "Error initializing texture %s\n", pInputFileName );
	}
}

// HDRFIXME: Put this somewhere better than this.
// This reads an integer from a binary CUtlBuffer.
static int ReadIntFromUtlBuffer( CUtlBuffer &buf )
{
	int val = 0;
	int c;
	while( buf.IsValid() )
	{
		c = buf.GetChar();
		if( c >= '0' && c <= '9' )
		{
			val = val * 10 + ( c - '0' );
		}
		else
		{
			buf.SeekGet( CUtlBuffer::SEEK_CURRENT, -1 );
			break;
		}
	}
	return val;
}

static inline bool IsWhitespace( char c )
{
	return c == ' ' || c == '\t' || c == 10;
}

static void EatWhiteSpace( CUtlBuffer &buf )
{
	while( buf.IsValid() )
	{
		int	c = buf.GetChar();
		if( !IsWhitespace( c ) )
		{
			buf.SeekGet( CUtlBuffer::SEEK_CURRENT, -1 );
			return;
		}
	}
	return;
}

//-----------------------------------------------------------------------------
// Creates a texture the size of the PFM image stored in the buffer
//-----------------------------------------------------------------------------
static void InitializeSrcTexture_PFM( IVTFTexture *pTexture, const char *pInputFileName,
									  CUtlBuffer &fileBuffer, int nDepth, int nFrameCount, 
									  const VTexConfigInfo_t &info )
{
	fileBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
	if( fileBuffer.GetChar() != 'P' )
	{
		Assert( 0 );
		return;
	}
	if( fileBuffer.GetChar() != 'F' )
	{
		Assert( 0 );
		return;
	}
	if( fileBuffer.GetChar() != 0xa )
	{
		Assert( 0 );
		return;
	}
	int nWidth, nHeight;
	nWidth = ReadIntFromUtlBuffer( fileBuffer );
	EatWhiteSpace( fileBuffer );
	nHeight = ReadIntFromUtlBuffer( fileBuffer );

	//	// eat crap until the next newline
	//	while( fileBuffer.GetChar() != 0xa )
	//	{
	//	}

	nWidth /= info.m_nReduceX;

	nHeight /= info.m_nReduceY;


	if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_RGB323232F, info.m_nFlags, nFrameCount ))
	{
		Error( "Error initializing texture %s\n", pInputFileName );
	}
}

static void InitializeSrcTexture( IVTFTexture *pTexture, const char *pInputFileName,
								  CUtlBuffer &tgaBuffer, int nDepth, int nFrameCount,
								  const VTexConfigInfo_t &info )
{
	switch ( g_eMode )
	{
	case eModePSD:
		InitializeSrcTexture_PSD( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info );
		break;
	case eModeTGA:
		InitializeSrcTexture_TGA( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info );
		break;
	case eModePNG:
		InitializeSrcTexture_PNG( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info );
		break;
	case eModePFM:
		InitializeSrcTexture_PFM( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info );
		break;
	}
}

#define DISTANCE_CODE_ALPHA_INOUT_THRESHOLD 10


//-----------------------------------------------------------------------------
// Loads a face from a PSD image
//-----------------------------------------------------------------------------
static bool LoadFaceFromPSD( IVTFTexture *pTexture, CUtlBuffer &psdBuffer, int z, int nFrame, int nFace, float flGamma, const VTexConfigInfo_t &info )
{
	// NOTE: This only works because all mip levels are stored sequentially
	// in memory, starting with the highest mip level. It also only works
	// because the VTF Texture store *all* mip levels down to 1x1

	// Get the information from the file...
	int nWidth, nHeight;
	ImageFormat imageFormat;
	float flSrcGamma;
	bool ok = PSDGetInfo( psdBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma );
	if (!ok)
		return false;

	// Seek back so PSDLoader can see the psd header...
	psdBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );

	// Load the psd and create all mipmap levels
	unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z );

	if ( ( info.m_bAlphaToDistance ) || 
		( nWidth != pTexture->Width() ) ||
		( nHeight != pTexture->Height() ) )
	{
		// Load into temp
		Bitmap_t bmPsdData;
		ok = PSDReadFileRGBA8888( psdBuffer, bmPsdData );
		if ( !ok )
			return false;

		CUtlMemory<uint8> tmpDest( 0, pTexture->Width() * pTexture->Height() * 4 );

		ImageLoader::ResampleInfo_t resInfo;
		resInfo.m_pSrc = bmPsdData.GetBits();
		resInfo.m_pDest = tmpDest.Base();
		resInfo.m_nSrcWidth = nWidth;
		resInfo.m_nSrcHeight = nHeight;
		resInfo.m_nDestWidth = pTexture->Width();
		resInfo.m_nDestHeight = pTexture->Height();
		resInfo.m_flSrcGamma = flGamma;
		resInfo.m_flDestGamma = flGamma;
		if (info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_FILTER_NICE )
		{
			resInfo.m_nFlags |= ImageLoader::RESAMPLE_NICE_FILTER;
		}

		ResampleRGBA8888( resInfo );

		if ( info.m_bAlphaToDistance )
		{
			float flMaxRad=info.m_flDistanceSpread*2.0*max(info.m_nReduceX,info.m_nReduceY);
			int nSearchRad=ceil(flMaxRad);
			bool bWarnEdges = false;
			// now, do alpha to distance coded stuff
			ImageFormatInfo_t fmtInfo=ImageLoader::ImageFormatInfo( pTexture->Format() );
			if ( fmtInfo.m_NumAlphaBits == 0 )
			{
				VTexWarning( "%s: alpha to distance asked for but no alpha channel.\n", info.m_SrcName );
			}
			else
			{
				for(int x=0; x < pTexture->Width(); x++ )
				{
					for(int y=0; y < pTexture->Height(); y++ )
					{
						// map to original image coords
						int nOrig_x=FLerp(0,nWidth-1,0,pTexture->Width()-1,x);
						int nOrig_y=FLerp(0,nHeight-1,0,pTexture->Height()-1,y);

						uint8 nOrigAlpha = bmPsdData.GetColor(nOrig_x, nOrig_y).a();
						bool bInOrOut=nOrigAlpha > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD;

						float flClosest_Dist=1.0e23;
						for(int iy=-nSearchRad; iy <= nSearchRad; iy++ )
						{
							for(int ix=-nSearchRad; ix <= nSearchRad; ix++ )
							{
								int cx=max( 0, min( nWidth-1, ix + nOrig_x ) );
								int cy=max( 0, min( nHeight-1, iy + nOrig_y ) );

								uint8 alphaValue = bmPsdData.GetColor(cx, cy).a();
								bool bIn =( alphaValue > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD );
								if ( bInOrOut != bIn )		// transition?
								{
									float flTryDist = sqrt( (float) (ix*ix+iy*iy) );
									flClosest_Dist = min( flClosest_Dist, flTryDist );
								}
							}
						}

						// now, map signed distance to alpha value
						float flOutDist = min( 0.5f, FLerp( 0, .5, 0, flMaxRad, flClosest_Dist ) );
						if ( ! bInOrOut )
						{
							// negative distance
							flOutDist = -flOutDist;
						}
						uint8 &nOutAlpha= tmpDest[3+4*(x+pTexture->Width()*y )];
						nOutAlpha = min( 255.0, 255.0*( 0.5+flOutDist ) );
						if ( ( nOutAlpha != 0 ) && 
							(
							( x == 0 ) ||
							( y == 0 ) ||
							( x == pTexture->Width()-1 ) ||
							( y == pTexture->Height()-1 ) ) )
						{
							bWarnEdges = true;
							nOutAlpha = 0;					// force it.
						}
					}
				}
			}

			if ( bWarnEdges )
			{
				VTexWarning( "%s: There are non-zero distance pixels along the image edge. You may need"
					" to reduce your distance spread or reduce the image less"
					" or add a border to the image.\n",
					info.m_SrcName );
			}
		}


		// now, store in dest
		ImageLoader::ConvertImageFormat( tmpDest.Base(), IMAGE_FORMAT_RGBA8888, pDestBits,
			pTexture->Format(), pTexture->Width(), pTexture->Height(), 
			0, 0 );
		return true;

	}
	else
	{
		// Read the PSD file into a bitmap
		Bitmap_t bmPsdData;
		ok = PSDReadFileRGBA8888( psdBuffer, bmPsdData );
		if ( ok )
		{
			memcpy( pDestBits, bmPsdData.GetBits(), bmPsdData.Height() * bmPsdData.Stride() );
		}
		return ok;
	}
}


//-----------------------------------------------------------------------------
// Loads a face from a TGA image
//-----------------------------------------------------------------------------
static bool LoadFaceFromTGA( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, float flGamma, const VTexConfigInfo_t &info )
{
	// NOTE: This only works because all mip levels are stored sequentially
	// in memory, starting with the highest mip level. It also only works
	// because the VTF Texture store *all* mip levels down to 1x1

	// Get the information from the file...
	int nWidth, nHeight;
	ImageFormat imageFormat;
	float flSrcGamma;
	bool ok = TGALoader::GetInfo( tgaBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma );
	if (!ok)
		return false;

	// Seek back so TGALoader::Load can see the tga header...
	tgaBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
	
	// Load the tga and create all mipmap levels
	unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z );

	if ( ( info.m_bAlphaToDistance ) || 
		 ( nWidth != pTexture->Width() ) ||
		 ( nHeight != pTexture->Height() ) )
	{
		// load into temp and resample
		CUtlMemory<uint8> tmpImage( 0, nWidth*nHeight*4 );
		if ( ! TGALoader::Load( tmpImage.Base(), tgaBuffer, nWidth, 
								nHeight, IMAGE_FORMAT_RGBA8888, flGamma, false ) )
		{
			return false;
		}

		CUtlMemory<uint8> tmpDest( 0, pTexture->Width() * pTexture->Height() *4 );

		ImageLoader::ResampleInfo_t resInfo;
		resInfo.m_pSrc = tmpImage.Base();
		resInfo.m_pDest = tmpDest.Base();
		resInfo.m_nSrcWidth = nWidth;
		resInfo.m_nSrcHeight = nHeight;
		resInfo.m_nDestWidth = pTexture->Width();
		resInfo.m_nDestHeight = pTexture->Height();
		resInfo.m_flSrcGamma = flGamma;
		resInfo.m_flDestGamma = flGamma;
		if (info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_FILTER_NICE )
		{
			resInfo.m_nFlags |= ImageLoader::RESAMPLE_NICE_FILTER;
		}

		ResampleRGBA8888( resInfo );

		if ( info.m_bAlphaToDistance )
		{
			float flMaxRad=info.m_flDistanceSpread*2.0*max(info.m_nReduceX,info.m_nReduceY);
			int nSearchRad=ceil(flMaxRad);
			bool bWarnEdges = false;
			// now, do alpha to distance coded stuff
			ImageFormatInfo_t fmtInfo=ImageLoader::ImageFormatInfo( pTexture->Format() );
			if ( fmtInfo.m_NumAlphaBits == 0 )
			{
				VTexWarning( "%s: alpha to distance asked for but no alpha channel.\n", info.m_SrcName );
			}
			else
			{
				for(int x=0; x < pTexture->Width(); x++ )
				{
					for(int y=0; y < pTexture->Height(); y++ )
					{
						// map to original image coords
						int nOrig_x=FLerp(0,nWidth-1,0,pTexture->Width()-1,x);
						int nOrig_y=FLerp(0,nHeight-1,0,pTexture->Height()-1,y);
						
						uint8 nOrigAlpha=tmpImage[3+4*(nOrig_x+nWidth*nOrig_y)];
						bool bInOrOut=nOrigAlpha > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD;

						float flClosest_Dist=1.0e23;
						for(int iy=-nSearchRad; iy <= nSearchRad; iy++ )
						{
							for(int ix=-nSearchRad; ix <= nSearchRad; ix++ )
							{
								int cx=max( 0, min( nWidth-1, ix + nOrig_x ) );
								int cy=max( 0, min( nHeight-1, iy + nOrig_y ) );

								int nOffset = 3+ 4 * ( cx + cy * nWidth );
								uint8 alphaValue = tmpImage[nOffset];
								bool bIn =( alphaValue > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD );
								if ( bInOrOut != bIn )		// transition?
								{
									float flTryDist = sqrt( (float) (ix*ix+iy*iy) );
									flClosest_Dist = min( flClosest_Dist, flTryDist );
								}
							}
						}

						// now, map signed distance to alpha value
						float flOutDist = min( 0.5f, FLerp( 0, .5, 0, flMaxRad, flClosest_Dist ) );
						if ( ! bInOrOut )
						{
							// negative distance
							flOutDist = -flOutDist;
						}
						uint8 &nOutAlpha= tmpDest[3+4*(x+pTexture->Width()*y )];
						nOutAlpha = min( 255.0, 255.0*( 0.5+flOutDist ) );
						if ( ( nOutAlpha != 0 ) && 
							 (
								 ( x == 0 ) ||
								 ( y == 0 ) ||
								 ( x == pTexture->Width()-1 ) ||
								 ( y == pTexture->Height()-1 ) ) )
						{
							bWarnEdges = true;
							nOutAlpha = 0;					// force it.
						}
					}
				}
			}

			if ( bWarnEdges )
			{
				VTexWarning( "%s: There are non-zero distance pixels along the image edge. You may need"
							 " to reduce your distance spread or reduce the image less"
							 " or add a border to the image.\n",
							 info.m_SrcName );
			}
		}
		
		
		// now, store in dest
		ImageLoader::ConvertImageFormat( tmpDest.Base(), IMAGE_FORMAT_RGBA8888, pDestBits,
										 pTexture->Format(), pTexture->Width(), pTexture->Height(), 
										 0, 0 );
		return true;
		
	}
	else
	{
		return TGALoader::Load( pDestBits, tgaBuffer, pTexture->Width(), 
			pTexture->Height(), pTexture->Format(), flGamma, false );
	}
}


//-----------------------------------------------------------------------------
// Loads a face from a PNG image
//-----------------------------------------------------------------------------
static bool LoadFaceFromPNG( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, float flGamma, const VTexConfigInfo_t &info )
{
	// NOTE: This only works because all mip levels are stored sequentially
	// in memory, starting with the highest mip level. It also only works
	// because the VTF Texture store *all* mip levels down to 1x1

	// Get the information from the file...
	int nWidth, nHeight;
	ImageFormat imageFormat;
	float flSrcGamma;

	ConversionErrorType error = CE_SUCCESS;

	// Load the tga and create all mipmap levels
	unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z );

	memcpy( pDestBits, tgaBuffer.Base(), tgaBuffer.TellPut() );

	return true;
}

//-----------------------------------------------------------------------------
// Loads a face from a PFM image
//-----------------------------------------------------------------------------
// HDRFIXME: How is this different from InitializeSrcTexture_PFM?
static bool LoadFaceFromPFM( IVTFTexture *pTexture, CUtlBuffer &fileBuffer, int z, int nFrame,
							int nFace, float flGamma, const VTexConfigInfo_t &info )
{
	fileBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
	if( fileBuffer.GetChar() != 'P' )
	{
		Assert( 0 );
		return false;
	}
	if( fileBuffer.GetChar() != 'F' )
	{
		Assert( 0 );
		return false;
	}
	if( fileBuffer.GetChar() != 0xa )
	{
		Assert( 0 );
		return false;
	}
	int nWidth, nHeight;
	nWidth = ReadIntFromUtlBuffer( fileBuffer );
	EatWhiteSpace( fileBuffer );
	nHeight = ReadIntFromUtlBuffer( fileBuffer );

	// eat crap until the next newline
	while( fileBuffer.IsValid() && fileBuffer.GetChar() != 0xa )
	{
	}
	// eat crap until the next newline
	while( fileBuffer.IsValid() && fileBuffer.GetChar() != 0xa )
	{
	}
	Assert( ImageLoader::SizeInBytes( pTexture->Format() ) == 3 * sizeof( float ) );

	// Load the pfm and create all mipmap levels
	float *pDestBits = ( float * )pTexture->ImageData( nFrame, nFace, 0, 0, 0, z );

	int y;
	for( y = nHeight-1; y >= 0; y-- )
	{
		Assert( fileBuffer.IsValid() );
		fileBuffer.Get( pDestBits + y * nWidth * 3, nWidth * 3 * sizeof( float ) );
		for(int x=0;x<nWidth*3;x++)
			pDestBits[x+y*nWidth*3]*=info.m_pfmscale;
	}
	return true;
}

static bool LoadFaceFromX( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, 
						   float flGamma, const VTexConfigInfo_t &info )
{
	switch ( g_eMode )
	{
	case eModePSD:
		return LoadFaceFromPSD( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info );
		break;
	case eModeTGA:
		return LoadFaceFromTGA( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info );
		break;
	case eModePNG:
		return LoadFaceFromPNG( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info );
		break;
	case eModePFM:
		return LoadFaceFromPFM( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info );
		break;
	default:
		return false;
	}
}

static bool LoadFace( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, 
					  float flGamma, const VTexConfigInfo_t &info )
{
	if ( !LoadFaceFromX( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info ) )
		return false;

	// Restricting number of channels by painting white into the rest
	if ( info.m_numChannelsMax < 1 || info.m_numChannelsMax > 4 )
	{
		VTexWarning( "%s: Invalid setting restricting number of channels to %d, discarded!\n", info.m_SrcName, info.m_numChannelsMax );
	}
	else if ( info.m_numChannelsMax < 4 )
	{
		if ( 4 != ImageLoader::SizeInBytes( pTexture->Format() ) )
		{
			VTexWarning( "%s: Channels restricted to %d, but cannot fill white"
				" because pixel format is %d (size in bytes %d)!"
				" Proceeding with unmodified channels.\n",
				info.m_SrcName,
				info.m_numChannelsMax, pTexture->Format(), ImageLoader::SizeInBytes( pTexture->Format() ) );
			Assert( 0 );
		}
		else
		{
			// Fill other channels with white

			unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z );
			int nWidth = pTexture->Width();
			int nHeight = pTexture->Height();
			
			int nPaintOff = info.m_numChannelsMax;
			int nPaintBytes = 4 - nPaintOff;
			
			pDestBits += nPaintOff;

			for( int j = 0; j < nHeight; ++ j )
			{
				for ( int k = 0; k < nWidth; ++ k, pDestBits += 4 )
				{
					memset( pDestBits, 0xFF, nPaintBytes );
				}
			}
		}
	}

	return true;
}

//-----------------------------------------------------------------------------
// Loads source image data 
//-----------------------------------------------------------------------------
static bool LoadSourceImages( IVTFTexture *pTexture, const char *pFullNameWithoutExtension,
							  bool *pbGenerateSphereMaps,
							  VTexConfigInfo_t &info )
{
	static char pSrcName[1024];

	bool bGenerateSpheremaps = false;

	// The input file name here is simply for error reporting
	char *pInputFileName = ( char * )stackalloc( strlen( pFullNameWithoutExtension ) + strlen( GetSourceExtension() ) + 1 );
	strcpy( pInputFileName, pFullNameWithoutExtension );
	strcat( pInputFileName, GetSourceExtension() );

	int nFrameCount;
	bool bAnimated = !( info.m_nStartFrame == -1 || info.m_nEndFrame == -1 );
	if( !bAnimated )
	{
		nFrameCount = 1;
	}
	else
	{
		nFrameCount = info.m_nEndFrame - info.m_nStartFrame + 1;
	}

	bool bIsCubeMap = (info.m_nFlags & TEXTUREFLAGS_ENVMAP) != 0;
	bool bIsVolumeTexture = ( info.m_nVolumeTextureDepth > 1 );

	// Iterate over all faces of all frames
	int nFaceCount = bIsCubeMap ? CUBEMAP_FACE_COUNT : 1;
	for( int iFrame = 0; iFrame < nFrameCount; ++iFrame )
	{
		for( int iFace = 0; iFace < nFaceCount; ++iFace )
		{
			for ( int z = 0; z < info.m_nVolumeTextureDepth; ++z )
			{
				// Generate the filename to load....
				MakeSrcFileName( pSrcName, info.m_nFlags, pFullNameWithoutExtension, 
					iFrame, iFace, bIsVolumeTexture ? z : -1, bIsCubeMap, info.m_nStartFrame, info.m_nEndFrame, info.m_bNormalToDuDv );

				// Don't fail if the 7th iFace of a cubemap isn't loaded...
				// that just means that we're gonna have to build the spheremap ourself.
				bool bFailOnError = !bIsCubeMap || (iFace != CUBEMAP_FACE_SPHEREMAP);

				// Load the TGA from disk...
				CUtlBuffer tgaBuffer;
				if ( !LoadFile( pSrcName, tgaBuffer, bFailOnError,
					( g_eMode != eModePSD ) ? &info.m_uiInputHash : NULL ) )
				{
					// If we want to fail on error and VTexError didn't abort then
					// simply notify the caller that we failed
					if ( bFailOnError )
						return false;

					// The only other way we can get here is if LoadFile tried to load a spheremap and failed
					bGenerateSpheremaps = true;
					continue;
				}

				// Initialize the VTF Texture here if we haven't already....
				// Note that we have to do it here because we have to get the width + height from the file
				if (!pTexture->ImageData())
				{
					InitializeSrcTexture( pTexture, pSrcName, tgaBuffer, info.m_nVolumeTextureDepth, nFrameCount, info );

					// Re-seek back to the front of the buffer so LoadFaceFromTGA works
					tgaBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
				}

				strcpy( info.m_SrcName, pSrcName );
				// NOTE: This here will generate all mip levels of the source image
				if (!LoadFace( pTexture, tgaBuffer, z, iFrame, iFace, 2.2, info ))
				{
					Error( "Error loading texture %s\n", pInputFileName );
				}
			}
		}
	}

	if ( pbGenerateSphereMaps )
	{
		*pbGenerateSphereMaps = bGenerateSpheremaps;
	}

	return true;
}


void PreprocessSkyBox( char *pFullNameWithoutExtension, int *iSkyboxFace )
{
	// When we get here, it means that we're processing one face of a skybox, but we're going to 
	// load all the faces and treat it as a cubemap so we can do the edge matching.

	// Since they passed in only one face of the skybox, there's a 2 letter extension we want to get rid of.
	int len = strlen( pFullNameWithoutExtension );
	if ( len >= 3 )
	{
		// Make sure there really is a 2 letter extension.
		char *pEnd = &pFullNameWithoutExtension[ len - 2 ];
		*iSkyboxFace = -1;
		for ( int i=0; i < ARRAYSIZE( g_CubemapFacingNames ); i++ )
		{
			if ( stricmp( pEnd, g_CubemapFacingNames[i] ) == 0 )
			{
				*iSkyboxFace = i;
				break;
			}
		}

		// Cut off the 2 letter extension.
		if ( *iSkyboxFace != -1 )
		{
			pEnd[0] = 0;
			return;
		}
	}

	Error( "PreprocessSkyBox: filename %s doesn't have a proper extension (bk, dn, rt, etc..)\n", pFullNameWithoutExtension );
}


// Right now, we've got a full cubemap, and we want to return the one face of the
// skybox that we're supposed to be processing.
IVTFTexture* PostProcessSkyBox( IVTFTexture *pTexture, int iSkyboxFace )
{
	int nFlags = pTexture->Flags();
	Assert( nFlags & TEXTUREFLAGS_ENVMAP );	// Should have been treated as an envmap till now.
	nFlags &= ~TEXTUREFLAGS_ENVMAP;			// But it ends now!

	IVTFTexture *pRet = CreateVTFTexture();
	if ( !pRet->Init( pTexture->Width(), pTexture->Height(), 1, pTexture->Format(), nFlags, pTexture->FrameCount() ) )
		Error( "PostProcessSkyBox: IVTFTexture::Init() failed.\n" );

	// Now just dump the data for the face we want to keep.
	int nMips = min( pTexture->MipCount(), pRet->MipCount() );
	for ( int iMip=0; iMip < nMips; iMip++ )
	{
		int mipSize = pTexture->ComputeMipSize( iMip );
		if ( pRet->ComputeMipSize( iMip ) != mipSize )
		{
			Error( "PostProcessSkyBox: ComputeMipSize differs (src=%d, dest=%d)\n", mipSize, pRet->ComputeMipSize( iMip ) );
		}

		for ( int iFrame=0; iFrame < pTexture->FrameCount(); iFrame++ )
		{
			unsigned char *pDest = pRet->ImageData( iFrame, 0, iMip );
			const unsigned char *pSrc = pTexture->ImageData( iFrame, iSkyboxFace, iMip );
			memcpy( pDest, pSrc, mipSize );
		}
	}

	// Note: there are a few things that don't get copied here, like alpha test threshold
	// and bumpscale, but we shouldn't need those for skyboxes anyway.

	// Get rid of the full cubemap one and return the single-face one.
	DestroyVTFTexture( pTexture );
	return pRet;
}


void MakeDirHier( const char *pPath )
{
#ifdef POSIX
#define mkdir(s) mkdir(s, S_IRWXU | S_IRWXG | S_IRWXO )
#endif
	char temp[1024];
	Q_strncpy( temp, pPath, 1024 );
	int i;
	for( i = 0; i < strlen( temp ); i++ )
	{
		if( temp[i] == '/' || temp[i] == '\\' )
		{
			temp[i] = '\0';
			//			DebugOut( "mkdir( %s )\n", temp );
			mkdir( temp );
			temp[i] = CORRECT_PATH_SEPARATOR;
		}
	}
	//	DebugOut( "mkdir( %s )\n", temp );
	mkdir( temp );
}


static uint8 GetClampingValue( int nClampSize )
{
	if ( nClampSize <= 0 )
		return 30;											// ~1 billion
	int nRet = 0;
	while ( nClampSize > 1 )
	{
		nClampSize >>= 1;
		nRet++;
	}
	return nRet;
}

static void SetTextureLodData( IVTFTexture *pTexture, VTexConfigInfo_t const &info )
{
	if (
		( info.m_nMaxDimensionX > 0 && info.m_nMaxDimensionX < pTexture->Width() ) ||
		( info.m_nMaxDimensionY > 0 && info.m_nMaxDimensionY < pTexture->Height() ) ||
		( info.m_nMaxDimensionX_360 > 0 && info.m_nMaxDimensionX_360 < pTexture->Width() ) ||
		( info.m_nMaxDimensionY_360 > 0 && info.m_nMaxDimensionY_360 < pTexture->Height() )
		)
	{
		TextureLODControlSettings_t lodChunk;
		memset( &lodChunk, 0, sizeof( lodChunk ) );
		lodChunk.m_ResolutionClampX = GetClampingValue( info.m_nMaxDimensionX );
		lodChunk.m_ResolutionClampY = GetClampingValue( info.m_nMaxDimensionY );
		lodChunk.m_ResolutionClampX_360 = GetClampingValue( info.m_nMaxDimensionX_360 );
		lodChunk.m_ResolutionClampY_360 = GetClampingValue( info.m_nMaxDimensionY_360 );
		pTexture->SetResourceData( VTF_RSRC_TEXTURE_LOD_SETTINGS, &lodChunk, sizeof( lodChunk ) );
	}
}


static void AttachShtFile( const char *pFullNameWithoutExtension, IVTFTexture *pTexture, CRC32_t *puiHash )
{
	char shtName[MAX_PATH];
	Q_strncpy( shtName, pFullNameWithoutExtension, sizeof(shtName) );
	Q_SetExtension( shtName, ".sht", sizeof(shtName) );

	struct	_stat statBuf;
	if( _stat( shtName, &statBuf ) == -1 )
		return;

	printf( "Attaching .sht file %s.\n", shtName );

	// Ok, the file exists. Read it.
	CUtlBuffer buf;
	if ( !LoadFile( shtName, buf, false, puiHash ) )
		return;

	pTexture->SetResourceData( VTF_RSRC_SHEET, buf.Base(), buf.TellPut() );
}


//-----------------------------------------------------------------------------
// Does the dirty deed and generates a VTF file
//-----------------------------------------------------------------------------
bool ProcessFiles( const char *pFullNameWithoutExtension, 
				  const char *pOutputDir, const char *pBaseName, 
				  bool isCubeMap, VTexConfigInfo_t &info )
{
	// force clamps/clampt for cube maps
	if( isCubeMap )
	{
		info.m_nFlags |= TEXTUREFLAGS_ENVMAP;
		info.m_nFlags |= TEXTUREFLAGS_CLAMPS;
		info.m_nFlags |= TEXTUREFLAGS_CLAMPT;
	}

	// Create the texture we're gonna store out
	SmartIVtfTexture pVTFTexture( CreateVTFTexture() );

	int iSkyboxFace = 0;
	char fullNameTemp[512];
	if ( info.m_bSkyBox )
	{
		Q_strncpy( fullNameTemp, pFullNameWithoutExtension, sizeof( fullNameTemp ) );
		pFullNameWithoutExtension = fullNameTemp;
		PreprocessSkyBox( fullNameTemp, &iSkyboxFace );
	}

	// Load the source images into the texture
	bool bGenerateSpheremaps = false;
	bool bLoadedSourceImages = LoadSourceImages( pVTFTexture.Get(), 
		pFullNameWithoutExtension, &bGenerateSpheremaps, info );
	if ( !bLoadedSourceImages )
	{
		VTexError( "Can't load source images for \"%s\"\n", pFullNameWithoutExtension );
		return false;
	}

	// Attach a sheet file if present
	AttachShtFile( pFullNameWithoutExtension, pVTFTexture.Get(), &info.m_uiInputHash );

	// No more file loads, finalize the sources hash
	CRC32_Final( &info.m_uiInputHash );
	pVTFTexture->SetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, &info.m_uiInputHash, sizeof( info.m_uiInputHash ) );
	CRC32_t crcWritten = info.m_uiInputHash;

	// Name of the destination file
	char dstFileName[1024];
	sprintf( dstFileName, "%s/%s%s.vtf", pOutputDir, pBaseName, ( ( eModePFM == g_eMode ) && isCubeMap ) ? ".hdr" : "" );

	// Now if we are only validating the CRC
	if( CommandLine()->FindParm( "-crcvalidate" ) )
	{
		CUtlBuffer bufFile;
		bool bLoad = LoadFile( dstFileName, bufFile, false, NULL );
		if ( !bLoad )
		{
			fprintf( stderr, "LOAD ERROR: %s\n", dstFileName );
			return false;
		}

		SmartIVtfTexture spExistingVtf( CreateVTFTexture() );
		bLoad = spExistingVtf->Unserialize( bufFile );
		if ( !bLoad )
		{
			fprintf( stderr, "UNSERIALIZE ERROR: %s\n", dstFileName );
			return false;
		}

		size_t numDataBytes;
		void *pCrcData = spExistingVtf->GetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, &numDataBytes );
		if ( !pCrcData || numDataBytes != sizeof( CRC32_t ) )
		{
			fprintf( stderr, "OLD TEXTURE FORMAT: %s\n", dstFileName );
			return false;
		}

		CRC32_t crcFile = * reinterpret_cast< CRC32_t const * >( pCrcData );
		if ( crcFile != crcWritten )
		{
			fprintf( stderr, "CRC MISMATCH: %s\n", dstFileName );
			return false;
		}

		fprintf( stderr, "OK: %s\n", dstFileName );
		return true;
	}

	// Now if we are not forcing the CRC
	if( !CommandLine()->FindParm( "-crcforce" ) )
	{
		CUtlBuffer bufFile;
		if ( LoadFile( dstFileName, bufFile, false, NULL ) )
		{
			SmartIVtfTexture spExistingVtf( CreateVTFTexture() );
			if ( spExistingVtf->Unserialize( bufFile ) )
			{
				size_t numDataBytes;
				void *pCrcData = spExistingVtf->GetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, &numDataBytes );
				if ( pCrcData && numDataBytes == sizeof( CRC32_t ) )
				{
					CRC32_t crcFile = * reinterpret_cast< CRC32_t const * >( pCrcData );
					if ( crcFile == crcWritten )
					{
						if( !g_Quiet )
							printf( "SUCCESS: %s is up-to-date\n", dstFileName );

						if( !CommandLine()->FindParm( "-crcforce" ) )
							return true;
					}
				}
			}
		}
	}

	// Bumpmap scale..
	pVTFTexture->SetBumpScale( info.m_flBumpScale );

	// Alphatest threshhold
	pVTFTexture->SetAlphaTestThreshholds( info.m_flAlphaThreshhold, info.m_flAlphaHiFreqThreshhold );

	// Set texture lod data
	SetTextureLodData( pVTFTexture.Get(), info );

	// Get the texture all internally consistent and happy
	bool bAllowFixCubemapOrientation = !info.m_bSkyBox;	// Don't let it rotate our pseudo-cubemap faces around if it's a skybox.
	pVTFTexture->SetPostProcessingSettings( &info.m_vtfProcOptions );
	pVTFTexture->PostProcess( bGenerateSpheremaps, info.m_LookDir, bAllowFixCubemapOrientation );

	// Compute the preferred image format
	ImageFormat vtfImageFormat = ComputeDesiredImageFormat( pVTFTexture.Get(), info );

	// Set up the low-res image
	if (pVTFTexture->IsCubeMap())
	{
		// "Stage 1" of matching cubemap borders. Sometimes, it has to store off the original image.
		pVTFTexture->MatchCubeMapBorders( 1, vtfImageFormat, info.m_bSkyBox );
	}
	else
	{
		CreateLowResImage( pVTFTexture.Get() );
	}

	// Convert to the final format
	pVTFTexture->ConvertImageFormat( vtfImageFormat, info.m_bNormalToDuDv );

	// Stage 2 of matching cubemap borders.
	pVTFTexture->MatchCubeMapBorders( 2, vtfImageFormat, info.m_bSkyBox );

	if ( info.m_bSkyBox )
	{
		pVTFTexture.Assign( PostProcessSkyBox( pVTFTexture.Get(), iSkyboxFace ) );
	}

	if ( info.IsSettings0Valid() )
	{
		pVTFTexture->SetResourceData( VTF_RSRC_TEXTURE_SETTINGS_EX, &info.m_exSettings0, sizeof( info.m_exSettings0 ) );
	}

	// Write it!
	if ( g_CreateDir )
		MakeDirHier( pOutputDir ); //It'll create it if it doesn't exist.

	// Make sure the CRC hasn't been modified since finalized
	Assert( crcWritten == info.m_uiInputHash );

	CUtlBuffer outputBuf;
	if (!pVTFTexture->Serialize( outputBuf ))
	{
		VTexError( "ERROR: \"%s\": Unable to serialize the VTF file!\n", dstFileName );
	}

	{
		FILE *fp = fopen( dstFileName, "wb" );
		if( !fp )
		{
			VTexError( "Can't open: %s\n", dstFileName );
		}
		fwrite( outputBuf.Base(), 1, outputBuf.TellPut(), fp );
		fclose( fp );
	}

	printf("SUCCESS: Vtf file created\n");
	return true;
}

const char *GetPossiblyQuotedWord( const char *pInBuf, char *pOutbuf )
{
	pInBuf += strspn( pInBuf, " \t" );						// skip whitespace

	const char *pWordEnd;
	bool bQuote = false;
	if (pInBuf[0]=='"')
	{
		pInBuf++;
		pWordEnd=strchr(pInBuf,'"');
		bQuote = true;
	}
	else
	{
		pWordEnd=strchr(pInBuf,' ');
		if (! pWordEnd )
			pWordEnd = strchr(pInBuf,'\t' );
		if (! pWordEnd )
			pWordEnd = pInBuf+strlen(pInBuf);
	}
	if ((! pWordEnd ) || (pWordEnd == pInBuf ) )
		return NULL;										// no word found
	memcpy( pOutbuf, pInBuf, pWordEnd-pInBuf );
	pOutbuf[pWordEnd-pInBuf]=0;

	pInBuf = pWordEnd;
	if ( bQuote )
		pInBuf++;
	return pInBuf;
}

// GetKeyValueFromBuffer:
//		fills in "key" and "val" respectively and returns "true" if succeeds.
//		returns false if:
//			a) end-of-buffer is reached (then "val" is empty)
//			b) error occurs (then "val" is the error message)
//
static bool GetKeyValueFromBuffer( CUtlBuffer &buffer, char *key, char *val )
{
	char buf[2048];

	while( buffer.GetBytesRemaining() )
	{
		buffer.GetLine( buf, sizeof( buf ) );

		// Scanning algorithm
		char *pComment = strpbrk( buf, "#\n\r" );
		if ( pComment )
			*pComment = 0;

		pComment = strstr( buf, "//" );
		if ( pComment)
			*pComment = 0;

		const char *scan = buf;
		scan=GetPossiblyQuotedWord( scan, key );
		if ( scan )
		{
			scan=GetPossiblyQuotedWord( scan, val );
			if ( scan )
				return true;
			else
			{
				sprintf( val, "parameter %s has no value", key );
				return false;
			}
		}
	}

	val[0] = 0;
	return false;
}


//-----------------------------------------------------------------------------
// Loads the .psd file or .txt file associated with the .tga and gets out various data
//-----------------------------------------------------------------------------
static bool LoadConfigFile( const char *pFileBaseName, VTexConfigInfo_t &info, bool *isCubeMap )
{
	// Tries to load .txt, then .psd

	int lenBaseName = strlen( pFileBaseName );
	char *pFileName = ( char * )stackalloc( lenBaseName + strlen( ".tga" ) + 1 );
	strcpy( pFileName, pFileBaseName );
	strcat( pFileName, ".tga" );

	bool bOK = false;

	info.m_LookDir = LOOK_DOWN_Z;

	// Try TGA file with config
	memcpy( pFileName + lenBaseName, ".tga", 4 );
	if ( !bOK && ( 00 == access( pFileName, 00 ) ) ) // TGA file exists
	{
		g_eMode = eModeTGA;

		memcpy( pFileName + lenBaseName, ".txt", 4 );
		CUtlBuffer bufFile( 0, 0, CUtlBuffer::TEXT_BUFFER );
		bOK = LoadFile( pFileName, bufFile, false, &info.m_uiInputHash );
		if ( bOK )
		{
			printf("config file %s\n",pFileName);

			{
				char key[2048];
				char val[2048];
				while( GetKeyValueFromBuffer( bufFile, key, val ) )
				{
					info.ParseOptionKey( key, val );
				}

				if ( val[0] )
				{
					VTexError( "%s: %s\n", pFileName, val );
					return false;
				}
			}
		}
		else
		{
			memcpy( pFileName + lenBaseName, ".tga", 4 );
			printf("no config file for %s\n",pFileName);
			bOK = true;
		}
	}
	memcpy( pFileName + lenBaseName, ".tga", 4 );

	// PSD file attempt
	memcpy( pFileName + lenBaseName, ".psd", 4 );
	if ( !bOK && ( 00 == access( pFileName, 00 ) ) ) // If PSD mode was not disabled
	{
		g_eMode = eModePSD;

		CUtlBuffer bufFile;
		bOK = LoadFile( pFileName, bufFile, false, &info.m_uiInputHash );
		if ( bOK )
		{
			printf("config file %s\n", pFileName);
			bOK = IsPSDFile( bufFile );
			if ( !bOK )
			{
				VTexError( "%s is not a valid PSD file!\n", pFileName );
				return false;
			}

			PSDImageResources imgres = PSDGetImageResources( bufFile );
			PSDResFileInfo resFileInfo( imgres.FindElement( PSDImageResources::eResFileInfo ) );
			PSDResFileInfo::ResFileInfoElement descr = resFileInfo.FindElement( PSDResFileInfo::eDescription );
			if ( descr.m_pvData )
			{
				CUtlBuffer bufDescr( 0, 0, CUtlBuffer::TEXT_BUFFER );
				bufDescr.EnsureCapacity( descr.m_numBytes );
				bufDescr.Put( descr.m_pvData, descr.m_numBytes );

				{
					char key[2048];
					char val[2048];
					while( GetKeyValueFromBuffer( bufDescr, key, val ) )
					{
						info.ParseOptionKey( key, val );
					}

					if ( val[0] )
					{
						VTexError( "%s: %s\n", pFileName, val );
						return false;
					}
				}
			}
		}
	}

	// PNG file attempt
	memcpy( pFileName + lenBaseName, ".png", 4 );
	if ( !bOK ) // If PNG mode was not disabled
	{
		g_eMode = eModePNG;
		info.m_nFlags |= TEXTUREFLAGS_NOMIP;
		bOK = true;
	}

	// Try TXT file as config again for TGA cubemap / PFM
	memcpy( pFileName + lenBaseName, ".txt", 4 );
	if ( !bOK )
	{
		g_eMode = eModeTGA;

		CUtlBuffer bufFile( 0, 0, CUtlBuffer::TEXT_BUFFER );
		bOK = LoadFile( pFileName, bufFile, false, &info.m_uiInputHash );
		if ( bOK )
		{
			printf("config file %s\n",pFileName);

			{
				char key[2048];
				char val[2048];
				while( GetKeyValueFromBuffer( bufFile, key, val ) )
				{
					info.ParseOptionKey( key, val );
				}

				if ( val[0] )
				{
					VTexError( "%s: %s\n", pFileName, val );
					return false;
				}
			}

			if ( g_eMode == eModePFM )
			{
				if ( g_bUsedAsLaunchableDLL && !( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NOCOMPRESS ) )
				{
					info.m_nFlags |= TEXTUREFLAGS_NOMIP;
				}
			}
		}
	}

	if ( !bOK )
	{
		VTexError( "\"%s\" does not specify valid PSD or TGA or PFM+TXT files!\n");
		return false;
	}

	if ( info.m_bIsCubeMap )
		*isCubeMap = true;

	if( ( info.m_bNormalToDuDv || ( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NORMAL_DUDV ) ) &&
		!( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP ) )
	{
		printf( "Implicitly setting premultcolorbyoneovermiplevel since you are generating a dudv map\n" );
		info.m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP;
	}

	if( ( info.m_bNormalToDuDv || ( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NORMAL_DUDV ) ) )
	{
		printf( "Implicitly setting trilinear since you are generating a dudv map\n" );
		info.m_nFlags |= TEXTUREFLAGS_TRILINEAR;
	}

	if( Q_stristr( pFileBaseName, "_normal" ) )
	{
		if( !( info.m_nFlags & TEXTUREFLAGS_NORMAL ) )
		{
			if( !g_Quiet )
			{
				fprintf( stderr, "implicitly setting:\n" );
				fprintf( stderr, "\t\"normal\" \"1\"\n" );
				fprintf( stderr, "since filename ends in \"_normal\"\n" );
			}
			info.m_nFlags |= TEXTUREFLAGS_NORMAL;
		}
	}

	if( Q_stristr( pFileBaseName, "ssbump" ) )
	{
		if( !( info.m_nFlags & TEXTUREFLAGS_SSBUMP ) )
		{
			if( !g_Quiet )
			{
				fprintf( stderr, "implicitly setting:\n" );
				fprintf( stderr, "\t\"ssbump\" \"1\"\n" );
				fprintf( stderr, "since filename includes \"ssbump\"\n" );
			}
			info.m_nFlags |= TEXTUREFLAGS_SSBUMP;
		}
	}

	if( Q_stristr( pFileBaseName, "_dudv" ) )
	{
		if( !info.m_bNormalToDuDv && !info.m_bDuDv )
		{
			if( !g_Quiet )
			{
				fprintf( stderr, "Implicitly setting:\n" );
				fprintf( stderr, "\t\"dudv\" \"1\"\n" );
				fprintf( stderr, "since filename ends in \"_dudv\"\n" );
				fprintf( stderr, "If you are trying to convert from a normal map to a dudv map, put \"normaltodudv\" \"1\" in description.\n" );
			}
			info.m_bDuDv = true;
		}
	}

	// turn off nice filtering if we are a cube map (takes too long with buildcubemaps) or
	// if we are a normal map (looks like terd.)
	if( ( info.m_nFlags & TEXTUREFLAGS_NORMAL ) || *isCubeMap )
	{
		if (info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_FILTER_NICE )
		{
			if ( !g_Quiet )
			{
				fprintf( stderr, "implicity disabling nice filtering\n" );
			}
		}
		info.m_vtfProcOptions.flags0 &= ~VtfProcessingOptions::OPT_FILTER_NICE;
	}

	return true;
}

void Usage( void )
{
	VTexError( 
		"Usage: vtex [-quiet] [-mkdir] [-shader ShaderName] [-vmtparam Param Value] tex1.txt tex2.txt . . .\n"
		"-quiet            : don't print anything out, don't pause for input\n"
		"-warningsaserrors : treat warnings as errors\n"
		"-nomkdir          : don't create destination folder if it doesn't exist\n"
		"-vmtparam         : adds parameter and value to the .vmt file\n"
		"-deducepath       : deduce path of sources by target file names\n"
		"-quickconvert     : use with \"-dontusegamedir -quickconvert\" to upgrade old .vmt files\n"
		"-crcvalidate      : validate .vmt against the sources\n"
		"-crcforce         : generate a new .vmt even if sources crc matches\n"
		"\teg: -vmtparam $ignorez 1 -vmtparam $translucent 1\n"
		"Note that you can use wildcards and that you can also chain them\n"
		"e.g. materialsrc/monster1/*.tga materialsrc/monster2/*.tga\n" );
}

bool GetOutputDir( const char *inputName, char *outputDir )
{
	if ( g_ForcedOutputDir[0] )
	{
		strcpy( outputDir, g_ForcedOutputDir );
	}
	else
	{
		// Is inputName a relative path?
		char buf[MAX_PATH];
		Q_MakeAbsolutePath( buf, sizeof( buf ), inputName, NULL );
		Q_FixSlashes( buf );

		char szSearch[MAX_PATH] = { 0 };
		V_snprintf( szSearch, sizeof( szSearch ), "materialsrc%c", CORRECT_PATH_SEPARATOR );
		const char *pTmp = Q_stristr( buf, szSearch );
		if( !pTmp )
		{
			return false;
		}
		pTmp += strlen( "materialsrc/" );
		strcpy( outputDir, gamedir );
		strcat( outputDir, "materials/" );
		strcat( outputDir, pTmp );
		Q_StripFilename( outputDir );
	}
	if( !g_Quiet )
	{
		printf( "output directory: %s\n", outputDir );
	}
	return true;
}

bool IsCube( const char *inputName )
{
	char tgaName[MAX_PATH];
	// Do Strcmp for ".hdr" to make sure we aren't ripping too much stuff off.
	Q_StripExtension( inputName, tgaName, MAX_PATH );
	const char *pInputExtension = inputName + Q_strlen( tgaName );
	Q_strncat( tgaName, "rt", MAX_PATH, COPY_ALL_CHARACTERS );
	Q_strncat( tgaName, pInputExtension, MAX_PATH, COPY_ALL_CHARACTERS );
	Q_strncat( tgaName, GetSourceExtension(), MAX_PATH, COPY_ALL_CHARACTERS );
	struct	_stat buf;
	if( _stat( tgaName, &buf ) != -1 )
	{
		return true;
	}
	else
	{
		return false;
	}
}

#ifdef WIN32
int Find_Files( WIN32_FIND_DATA &wfd, HANDLE &hResult, const char *basedir, const char *extension )
{
	char	filename[MAX_PATH] = {0};

	BOOL bMoreFiles = FindNextFile( hResult, &wfd);

	if ( bMoreFiles )
	{
		// Skip . and ..
		if ( wfd.cFileName[0] == '.' )
		{
			return FF_TRYAGAIN;
		}

		// If it's a subdirectory, just recurse down it
		if ( (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) )
		{
			char	subdir[MAX_PATH];
			sprintf( subdir, "%s\\%s", basedir, wfd.cFileName );

			// Recurse
			Find_Files( wfd, hResult, basedir, extension );
			return FF_TRYAGAIN;
		}

		// Check that it's a tga
		//

		char fname[_MAX_FNAME] = {0};
		char ext[_MAX_EXT] = {0};

		_splitpath( wfd.cFileName, NULL, NULL, fname, ext );

		// Not the type we want.
		if ( stricmp( ext, extension ) )
			return FF_DONTPROCESS;

		// Check for .vmt
		sprintf( filename, "%s\\%s.vmt", basedir, fname );
		// Exists, so don't overwrite it
		if ( access( filename, 0 ) != -1 )
			return FF_TRYAGAIN;

		char texturename[ _MAX_PATH ] = {0};
		char *p = ( char * )basedir;

		// Skip over the base path to get a material system relative path
		// p += strlen( wfd.cFileName ) + 1;

		// Construct texture name
		sprintf( texturename, "%s\\%s", p, fname );

		// Convert all to lower case
		strlwr( texturename );
		strlwr( filename );
	}

	return bMoreFiles;
}
#endif

bool Process_File( char *pInputBaseName, int maxlen )
{
	char outputDir[1024];
	Q_FixSlashes( pInputBaseName, '/' );
	Q_StripExtension( pInputBaseName, pInputBaseName, maxlen );

	if ( CommandLine()->FindParm( "-deducepath" ) )
	{
		strcpy( outputDir, pInputBaseName );

		// If it is not a full path, try making it a full path
		if ( pInputBaseName[0] != '/' &&
			 pInputBaseName[1] != ':' )
		{
			// Convert to full path
			getcwd( outputDir, sizeof( outputDir ) );
			Q_FixSlashes( outputDir, '/' );
			Q_strncat( outputDir, "/", sizeof( outputDir ) );
			Q_strncat( outputDir, pInputBaseName, sizeof( outputDir ) );
		}

		// If it is pointing inside "/materials/" make it go for "/materialsrc/"
		char *pGame = strstr( outputDir, "/game/" );
		char *pMaterials = strstr( outputDir, "/materials/" );
		if ( pGame && pMaterials && ( pGame < pMaterials ) )
		{
			// "u:/data/game/tf/materials/"  ->  "u:/data/content/tf/materialsrc/"
			int numExtraBytes = strlen( "/content/.../materialsrc/" ) - strlen( "/game/.../materials/" );
			int numConvertBytes = pMaterials + strlen( "/materials/" ) - outputDir;
			memmove( outputDir + numConvertBytes + numExtraBytes, outputDir + numConvertBytes, strlen( outputDir ) - numConvertBytes + 1 );
			
			int numMidBytes = pMaterials - pGame - strlen( "/game" );
			memmove( pGame + strlen( "/content" ), pGame + strlen( "/game" ), numMidBytes );

			memmove( pGame, "/content", strlen( "/content" ) );
			memmove( pGame + strlen( "/content" ) + numMidBytes, "/materialsrc/", strlen( "/materialsrc/" ) );
		}

		Q_strncpy( pInputBaseName, outputDir, maxlen );
	}

	if( !g_Quiet )
	{
		printf( "input file: %s\n", pInputBaseName );
	}

	if(g_UseGameDir && !GetOutputDir( pInputBaseName, outputDir ) )
	{
		VTexError( "Problem figuring out outputdir for %s\n", pInputBaseName );
		return FALSE;
	}
	else if (!g_UseGameDir)
	{
		strcpy(outputDir, pInputBaseName);
		Q_StripFilename(outputDir);
	}

	// Usage:
	//			vtex -dontusegamedir -quickconvert u:\data\game\tf\texture.vtf
	// Will read the old texture format and write the new texture format
	//
	if ( CommandLine()->FindParm( "-quickconvert" ) )
	{
		printf( "Quick convert of '%s'...\n", pInputBaseName );

		char chFileNameConvert[ 512 ];
		sprintf( chFileNameConvert, "%s.vtf", pInputBaseName );

		IVTFTexture *pVtf = CreateVTFTexture();
		CUtlBuffer bufFile;
		LoadFile( chFileNameConvert, bufFile, true, NULL );
		bool bRes = pVtf->Unserialize( bufFile );
		if ( !bRes )
			VTexError( "Failed to read '%s'!\n", chFileNameConvert );

		// Determine the CRC if it was there
		// CRC32_t uiDataHash = 0;
		// CRC32_t *puiDataHash = &uiDataHash;
		// Assert( sizeof( uiDataHash ) == sizeof( int ) );
		// if ( !pVtf->GetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, ... ) )

		AttachShtFile( pInputBaseName, pVtf, NULL );

		// Update the CRC
		// if ( puiDataHash )
		// {
		//	pVtf->InitResourceDataSection( VTexConfigInfo_t::VTF_INPUTSRC_CRC, *puiDataHash );
		// }
		// Remove the CRC when quick-converting
		pVtf->SetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, NULL, 0 );

		bufFile.Clear();
		bRes = pVtf->Serialize( bufFile );
		if ( !bRes )
			VTexError( "Failed to write '%s'!\n", chFileNameConvert );

		DestroyVTFTexture( pVtf );

		if ( FILE *fw = fopen( chFileNameConvert, "wb" ) )
		{
			fwrite( bufFile.Base(), 1, bufFile.TellPut(), fw );
			fclose( fw );
		}
		else
			VTexError( "Failed to open '%s' for writing!\n", chFileNameConvert );

		printf( "... succeeded.\n" );

		return TRUE;
	}

	VTexConfigInfo_t info;
	bool isCubeMap = false;
	if ( !LoadConfigFile( pInputBaseName, info, &isCubeMap ) )
		return FALSE;

	if( !isCubeMap )
	{
		isCubeMap = IsCube( pInputBaseName );
	}

	if( ( info.m_nStartFrame == -1 && info.m_nEndFrame != -1 ) ||
		( info.m_nStartFrame != -1 && info.m_nEndFrame == -1 ) )
	{
		VTexError( "%s: If you use startframe, you must use endframe, and vice versa.\n", pInputBaseName );
		return FALSE;
	}

	const char *pBaseName = &pInputBaseName[strlen( pInputBaseName ) - 1];
	while( (pBaseName >= pInputBaseName) && *pBaseName != '\\' && *pBaseName != '/' )
	{
		pBaseName--;
	}
	pBaseName++;

	bool bProcessedFilesOK = ProcessFiles( pInputBaseName, outputDir, pBaseName, isCubeMap, info );
	if ( !bProcessedFilesOK )
		return FALSE;

	// create vmts if necessary
	if( g_ShaderName )
	{
		char buf[1024];
		sprintf( buf, "%s/%s.vmt", outputDir, pBaseName );
		const char *tmp = Q_stristr( outputDir, "materials" );
		FILE *fp;
		if( tmp )
		{
			// check if the file already exists.
			fp = fopen( buf, "r" );
			if( fp )
			{
				if ( !g_Quiet )
					fprintf( stderr, "vmt file \"%s\" already exists\n", buf );

				fclose( fp );
			}
			else
			{
				fp = fopen( buf, "w" );
				if( fp )
				{
					if ( !g_Quiet )
						fprintf( stderr, "Creating vmt file: %s/%s\n", tmp, pBaseName );
					tmp += strlen( "materials/" );
					fprintf( fp, "\"%s\"\n", g_ShaderName );
					fprintf( fp, "{\n" );
					fprintf( fp, "\t\"$baseTexture\" \"%s/%s\"\n", tmp, pBaseName );

					int i;
					for( i=0;i<g_NumVMTParams;i++ )
					{
						fprintf( fp, "\t\"%s\" \"%s\"\n", g_VMTParams[i].m_szParam, g_VMTParams[i].m_szValue );
					}

					fprintf( fp, "}\n" );
					fclose( fp );
				}
				else
				{
					VTexWarning( "Couldn't open \"%s\" for writing\n", buf );
				}
			}

		}
		else
		{
			VTexWarning( "Couldn't find \"materials/\" in output path\n", buf );
		}
	}

	return TRUE;
}

static SpewRetval_t VTexOutputFunc( SpewType_t spewType, char const *pMsg )
{
	printf( "%s", pMsg );
	if (spewType == SPEW_ERROR)
	{
		return SPEW_ABORT;
	}
	return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE; 
}


class CVTex : public CTier2AppSystem< IVTex >, public ILaunchableDLL
{
public:
	int VTex( int argc, char **argv );

	// ILaunchableDLL, used by vtex.exe.
	virtual int main( int argc, char **argv )
	{
		g_bUsedAsLaunchableDLL = true;

		// Being used as a launchable DLL, we don't want to blow away the host app's command line
		CUtlString strOrigCmdLine( CommandLine()->GetCmdLine() );

		// Run the vtex logic
		int iResult = VTex( argc, argv );

		// Restore command line
		CommandLine()->CreateCmdLine( strOrigCmdLine.Get() );

		return iResult;
	}

	virtual int VTex( CreateInterfaceFn fsFactory, const char *pGameDir, int argc, char **argv )
	{
		g_pFileSystem = g_pFullFileSystem = (IFileSystem*)fsFactory( FILESYSTEM_INTERFACE_VERSION, NULL );
		if ( !g_pFileSystem )
		{
			Error( "IVTex3::VTex - fsFactory can't get '%s' interface.", FILESYSTEM_INTERFACE_VERSION );
			return 0;
		}

		Q_strncpy( gamedir, pGameDir, sizeof( gamedir ) );
		Q_AppendSlash( gamedir, sizeof( gamedir ) );

		// When being used embedded in a host app, we don't want to blow away the host app's command line
		CUtlString strOrigCmdLine( CommandLine()->GetCmdLine() );

		int iResult = VTex( argc, argv );

		// Restore command line
		CommandLine()->CreateCmdLine( strOrigCmdLine.Get() );

		return iResult;
	}
};

static class CSuggestGameDirHelper
{
public:
	static bool SuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories );
	bool MySuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories );

public:
	CSuggestGameDirHelper() : m_pszInputFiles( NULL ), m_numInputFiles( 0 ) {}

public:
	char const * const *m_pszInputFiles;
	size_t m_numInputFiles;
} g_suggestGameDirHelper;

bool CSuggestGameDirHelper::SuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories )
{
	return g_suggestGameDirHelper.MySuggestFn( pFsSteamSetupInfo, pchPathBuffer, nBufferLength, pbBubbleDirectories );
}

bool CSuggestGameDirHelper::MySuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories )
{
	if ( !m_numInputFiles || !m_pszInputFiles )
		return false;

	if ( pbBubbleDirectories )
		*pbBubbleDirectories = true;

	for ( int k = 0; k < m_numInputFiles; ++ k )
	{
		Q_MakeAbsolutePath( pchPathBuffer, nBufferLength, m_pszInputFiles[ k ] );
		return true;
	}

	return false;
}

int CVTex::VTex( int argc, char **argv )
{
//	CommandLine()->CreateCmdLine( argc, argv );

	if ( g_bUsedAsLaunchableDLL )
	{
		SpewOutputFunc( VTexOutputFunc );
	}

	MathLib_Init(  2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false );
	if( argc < 2 )
	{
		Usage();
		return -1;
	}

	g_UseGameDir = false; // make sure this is initialized to true.
	bool bCreatedFilesystem = false;

	int i;
	i = 1;
	while( i < argc )
	{
		if( stricmp( argv[i], "-quiet" ) == 0 )
		{
			i++;
			g_Quiet = true;
		}
		else if ( stricmp( argv[i], "-warningsaserrors" ) == 0 )
		{
			i++;
			g_bWarningsAsErrors = true;
		}
		else if ( stricmp( argv[i], "-nomkdir" ) == 0 ) 
		{
			i++;
			g_CreateDir = false;
		}
		else if ( stricmp( argv[i], "-outdir" ) == 0 )
		{
			V_strcpy_safe( g_ForcedOutputDir, argv[i+1] );
			i += 2;
		}
		else if( stricmp( argv[i], "-shader" ) == 0 )
		{
			i++;
			if( i < argc )
			{
				g_ShaderName = argv[i];
				i++;
			}
		}
		else if( stricmp(argv[i], "-crcvalidate") == 0 )
		{
			i++;
		}
		else if( stricmp(argv[i], "-crcforce") == 0 )
		{
			i++;
		}
		else if( stricmp( argv[i], "-vmtparam" ) == 0 )
		{
			if( g_NumVMTParams < MAX_VMT_PARAMS )
			{
				i++;
				if( i < argc - 1 )
				{
					g_VMTParams[g_NumVMTParams].m_szParam = argv[i];
					i++;

					if( i < argc - 1 )
					{
						g_VMTParams[g_NumVMTParams].m_szValue = argv[i];
						i++;
					}
					else
					{
						g_VMTParams[g_NumVMTParams].m_szValue = "";
					}

					if( !g_Quiet )
					{
						fprintf( stderr, "Adding .vmt parameter: \"%s\"\t\"%s\"\n", 
							g_VMTParams[g_NumVMTParams].m_szParam,
							g_VMTParams[g_NumVMTParams].m_szValue );
					}

					g_NumVMTParams++;
				}
			}
			else
			{
				fprintf( stderr, "Exceeded max number of vmt parameters, extra ignored ( max %d )\n", MAX_VMT_PARAMS );
			}
		}
		else
		{
			break;
		}
	}

	// Set the suggest game info directory helper
	g_suggestGameDirHelper.m_pszInputFiles = argv + i;
	g_suggestGameDirHelper.m_numInputFiles = argc - i;
	SetSuggestGameInfoDirFn( CSuggestGameDirHelper::SuggestFn );

	// g_pFileSystem may have been inherited with -inherit_filesystem.

	if (g_UseGameDir && !g_pFileSystem)
	{
		FileSystem_Init( argv[i] );
		bCreatedFilesystem = true;

		Q_FixSlashes( gamedir, '/' );
	}

	// Parse args
	for( ; i < argc; i++ )
	{
		if ( argv[i][0] == '-' )
			continue; // Assuming flags

		char pInputBaseName[MAX_PATH];
		Q_strncpy( pInputBaseName, argv[i], sizeof(pInputBaseName) );
		// int maxlen = Q_strlen( pInputBaseName ) + 1;

		if ( !Q_strstr( pInputBaseName, "*." ) )
		{
			Process_File( pInputBaseName, sizeof(pInputBaseName) );
			continue;
		}
	}

	if ( bCreatedFilesystem )
	{
		FileSystem_Term();
	}

	if ( g_bUsedAsLaunchableDLL )
	{
		// Make sure any further spew doesn't call the function in this module (which will be unloaded shortly)
		SpewOutputFunc( NULL );
	}

	return 0;
}

CVTex g_VTex;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVTex, IVTex, IVTEX_VERSION_STRING, g_VTex );
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVTex, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION, g_VTex );