diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/vtex/vtex.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/vtex/vtex.cpp')
| -rw-r--r-- | utils/vtex/vtex.cpp | 2996 |
1 files changed, 2996 insertions, 0 deletions
diff --git a/utils/vtex/vtex.cpp b/utils/vtex/vtex.cpp new file mode 100644 index 0000000..7a43b3f --- /dev/null +++ b/utils/vtex/vtex.cpp @@ -0,0 +1,2996 @@ +//========= 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 "tier2/p4helpers.h" +#include "p4lib/ip4.h" + +#include "tier1/checksum_crc.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_NoPause = false; +static bool g_Quiet = false; +static const char *g_ShaderName = NULL; +static bool g_CreateDir = true; +static bool g_UseGameDir = true; + +static bool g_bUseStandardError = false; +static bool g_bWarningsAsErrors = false; + +static bool g_bUsedAsLaunchableDLL = false; + +static bool g_bNoTga = false; +static bool g_bNoPsd = 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 } 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 void Pause( void ) +{ + if( !g_NoPause ) + { + printf( "Hit a key to continue\n" ); +#ifdef WIN32 + getch(); +#endif + } +} + +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; + } + + if ( g_bUseStandardError ) + { + Error( "ERROR: %s", str ); + } + else + { + fprintf( stderr, "ERROR: %s", str ); + Pause(); + 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 ); + Pause(); + } +} + + + +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"; + 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 ); + + { CP4AutoAddFile autop4( pFileName ); /* add loaded file to P4 */ } + + // 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 ); + } +} + + +// 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 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 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 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 == true ) + 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 ); + } + + { + CP4AutoEditAddFile autop4( 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 && !g_bNoTga && ( 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 ); + if ( g_bNoTga && ( 00 == access( pFileName, 00 ) ) ) + { + printf( "Warning: -notga disables \"%s\"\n", pFileName ); + } + + // PSD file attempt + memcpy( pFileName + lenBaseName, ".psd", 4 ); + if ( !bOK && !g_bNoPsd ) // 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; + } + } + } + } + } + else if ( 00 == access( pFileName, 00 ) ) + { + if ( !bOK ) + printf( "Warning: -nopsd disables \"%s\"\n", pFileName ); + else + printf( "Warning: psd file \"%s\" exists, but not used, delete tga and txt files to use psd file directly\n", pFileName ); + } + + // 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 %s%sPFM+TXT files!\n", + pFileBaseName, + g_bNoPsd ? "" : "PSD or ", + g_bNoTga ? "" : "TGA or " + ); + 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 [-outdir dir] [-quiet] [-nopause] [-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" + "-nopause : don't pause for input\n" + "-nomkdir : don't create destination folder if it doesn't exist\n" + "-vmtparam : adds parameter and value to the .vmt file\n" + "-outdir <dir> : write output to the specified dir regardless of source filename and vproject\n" + "-deducepath : deduce path of sources by target file names\n" + "-quickconvert : use with \"-nop4 -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 -nop4 -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 ); + + CP4AutoAddFile autop4( buf ); + } + 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) + { + Pause(); + 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 = true; // make sure this is initialized to true. + const char *p4ChangelistLabel = "VTex Auto Checkout"; + bool bCreatedFilesystem = false; + + int i; + i = 1; + while( i < argc ) + { + if( stricmp( argv[i], "-quiet" ) == 0 ) + { + i++; + g_Quiet = true; + g_NoPause = true; // no point in pausing if we aren't going to print anything out. + } + else if( stricmp( argv[i], "-nopause" ) == 0 ) + { + i++; + g_NoPause = true; + } + else if ( stricmp( argv[i], "-WarningsAsErrors" ) == 0 ) + { + i++; + g_bWarningsAsErrors = true; + } + else if ( stricmp( argv[i], "-UseStandardError" ) == 0 ) + { + i++; + g_bUseStandardError = true; + } + else if ( stricmp( argv[i], "-nopsd" ) == 0 ) + { + i++; + g_bNoPsd = true; + } + else if ( stricmp( argv[i], "-notga" ) == 0 ) + { + i++; + g_bNoTga = true; + } + else if ( stricmp( argv[i], "-nomkdir" ) == 0 ) + { + i++; + g_CreateDir = false; + } + else if ( stricmp( argv[i], "-mkdir" ) == 0 ) + { + i++; + g_CreateDir = true; + } + else if ( stricmp( argv[i], "-game" ) == 0 ) + { + i += 2; + } + else if ( stricmp( argv[i], "-outdir" ) == 0 ) + { + V_strcpy_safe( g_ForcedOutputDir, argv[i+1] ); + i += 2; + } + else if ( stricmp( argv[i], "-p4changelistlabel" ) == 0 ) + { + p4ChangelistLabel = argv[i+1]; + i += 2; + } + else if ( stricmp( argv[i], "-p4skipchangelistlabel" ) == 0 ) + { + p4ChangelistLabel = NULL; + i++; + } + else if ( stricmp( argv[i], "-dontusegamedir" ) == 0) + { + ++i; + g_UseGameDir = false; + } + else if( stricmp( argv[i], "-shader" ) == 0 ) + { + i++; + if( i < argc ) + { + g_ShaderName = argv[i]; + i++; + } + } + else if( stricmp( argv[i], "-vproject" ) == 0 ) + { + // skip this one. . we dont' use it internally. + i += 2; + } + else if( stricmp( argv[i], "-allowdebug" ) == 0 ) + { + // skip this one. . we dont' use it internally. + 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 if( stricmp( argv[i], "-nop4" ) == 0 ) + { + // Just here to signify that -nop4 is a valid flag + ++ i; + } + else if( stricmp( argv[i], "-deducepath" ) == 0 ) + { + // Just here to signify that -deducepath is a valid flag + ++ i; + } + else if( stricmp( argv[i], "-quickconvert" ) == 0 ) + { + // Just here to signify that -quickconvert is a valid flag + ++ i; + } + else if( stricmp( argv[i], "-crcvalidate" ) == 0 ) + { + // Just here to signify that -crcvalidate is a valid flag + ++ i; + } + else if( stricmp( argv[i], "-crcforce" ) == 0 ) + { + // Just here to signify that -crcforce is a valid flag + ++ i; + } + else if( stricmp( argv[i], "-p4skip" ) == 0 ) + { + // Just here to signify that -p4skip is a valid flag + ++ i; + } + 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, '/' ); + } + + if ( !CommandLine()->FindParm( "-p4skip" ) ) + { + // Initialize P4 + bool bP4DLLExists = false; + if ( g_pFullFileSystem ) + { + bP4DLLExists = g_pFullFileSystem->FileExists( "p4lib.dll", "EXECUTABLE_PATH" ); + } + + if ( g_bUsedAsLaunchableDLL && !CommandLine()->FindParm( "-nop4" ) && bP4DLLExists ) + { + const char *pModuleName = "p4lib.dll"; + CSysModule *pModule = Sys_LoadModule( pModuleName ); + if ( !pModule ) + { + printf( "Can't load %s.\n", pModuleName ); + return -1; + } + CreateInterfaceFn fn = Sys_GetFactory( pModule ); + if ( !fn ) + { + printf( "Can't get factory from %s.\n", pModuleName ); + Sys_UnloadModule( pModule ); + return -1; + } + p4 = (IP4 *)fn( P4_INTERFACE_VERSION, NULL ); + if ( !p4 ) + { + printf( "Can't get IP4 interface from %s, proceeding with -nop4.\n", pModuleName ); + g_p4factory->SetDummyMode( true ); + } + else + { + p4->Connect( FileSystem_GetFactory() ); + p4->Init(); + } + } + else + { + g_p4factory->SetDummyMode( true ); + } + + // Setup p4 factory + if ( p4ChangelistLabel && p4ChangelistLabel[0] != '\000' ) + { + // Set the named changelist + g_p4factory->SetOpenFileChangeList( p4ChangelistLabel ); + } + } + + // 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; + } + +#ifdef WIN32 + char search[ 128 ]; + char basedir[MAX_PATH]; + char ext[_MAX_EXT]; + char filename[_MAX_FNAME]; + + _splitpath( pInputBaseName, NULL, NULL, NULL, ext ); //find extension wanted + + if ( !Q_ExtractFilePath ( pInputBaseName, basedir, sizeof( basedir ) ) ) + strcpy( basedir, ".\\" ); + + sprintf( search, "%s\\*.*", basedir ); + + WIN32_FIND_DATA wfd; + HANDLE hResult; + memset(&wfd, 0, sizeof(WIN32_FIND_DATA)); + + hResult = FindFirstFile( search, &wfd ); + + if ( hResult != INVALID_HANDLE_VALUE ) + { + sprintf( filename, "%s%s", basedir, wfd.cFileName ); + + if ( wfd.cFileName[0] != '.' ) + Process_File( filename, sizeof( filename ) ); + + int iFFType = Find_Files( wfd, hResult, basedir, ext ); + + while ( iFFType ) + { + sprintf( filename, "%s%s", basedir, wfd.cFileName ); + + if ( wfd.cFileName[0] != '.' && iFFType != FF_DONTPROCESS ) + Process_File( filename, sizeof( filename ) ); + + iFFType = Find_Files( wfd, hResult, basedir, ext ); + } + + if ( iFFType == 0 ) + { + FindClose( hResult ); + } + } +#endif + + } + + // Shutdown P4 + if ( g_bUsedAsLaunchableDLL && p4 && !CommandLine()->FindParm( "-p4skip" ) ) + { + p4->Shutdown(); + p4->Disconnect(); + } + + 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 ); + } + + Pause(); + 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 ); |