//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <conio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <io.h>
#include <direct.h>
#include <stdarg.h>

#include "goldsrc_standin.h"

#include "wadlib.h"
#include "goldsrc_bspfile.h"


extern FILE *wadhandle;


#pragma pack( 1 )
	struct TGAHeader_t
	{
		unsigned char 	id_length;
		unsigned char	colormap_type;
		unsigned char	image_type;
		unsigned short	colormap_index;
		unsigned short	colormap_length;
		unsigned char	colormap_size;
		unsigned short	x_origin;
		unsigned short	y_origin;
		unsigned short	width;
		unsigned short	height;
		unsigned char	pixel_size;
		unsigned char	attributes;
	};
#pragma pack()


typedef enum {ST_SYNC=0, ST_RAND } synctype_t;
typedef enum { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t;

typedef struct {
	int			ident;
	int			version;
	int			type;
	int			texFormat;
	float		boundingradius;
	int			width;
	int			height;
	int			numframes;
	float		beamlength;
	synctype_t	synctype;
} dsprite_t;

typedef struct {
	int			origin[2];
	int			width;
	int			height;
} dspriteframe_t;


class RGBAColor
{
public:
	unsigned char r,g,b,a;
};


const char *g_pDefaultShader = "LightmappedGeneric";
const char *g_pShader = g_pDefaultShader;
bool g_bBMPAllowTranslucent = false;
bool g_bDecal = false;
bool g_bQuiet = false;

#define MAX_VMT_PARAMS	16

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

static VTexVMTParam_t g_VMTParams[MAX_VMT_PARAMS];

static int g_NumVMTParams = 0;

void PrintExitStuff()
{
	if ( !g_bQuiet )
	{
		printf( "Press a key to quit.\n" );
		_getch();
	}
}


RGBAColor* ConvertToRGBUpsideDown( byte *pBits, int width, int height, byte *pPalette, bool *bTranslucent )
{
	RGBAColor *pRet = new RGBAColor[width*height];

	byte newPalette[256][3];
	for ( int i=0; i < 256; i++ )
	{
		newPalette[i][0] = pPalette[i*3+2];
		newPalette[i][1] = pPalette[i*3+1];
		newPalette[i][2] = pPalette[i*3+0];
	}

	// Write the lines upside-down.
	for ( int y=0; y < height; y++ )
	{
		byte *pLine = &pBits[(height-y-1)*width];
		for ( int x=0; x < width; x++ )
		{
			pRet[y*width+x].r = newPalette[pLine[x]][0];
			pRet[y*width+x].g = newPalette[pLine[x]][1];
			pRet[y*width+x].b = newPalette[pLine[x]][2];
			if ( pLine[x] == 255 )
			{
				*bTranslucent = true;
				pRet[y*width+x].a = 0;
			}
			else
			{
				pRet[y*width+x].a = 255;
			}
		}
	}

	return pRet;
}


void FloodSolidPixels( RGBAColor *pTexels, int width, int height )
{
	byte *pAlphaMap = new byte[width*height];
	byte *pNewAlphaMap = new byte[width*height];

	for ( int y=0; y < height; y++ )
		for ( int x=0; x < width; x++ )
			pAlphaMap[y*width+x] = pTexels[y*width+x].a;
	
	bool bHappy = false;
	while ( !bHappy )
	{
		bHappy = true;
	
		memcpy( pNewAlphaMap, pAlphaMap, width * height );

		for ( int y=0; y < height; y++ )
		{
			RGBAColor *pLine = &pTexels[y*width];

			for ( int x=0; x < width; x++ )
			{
				if ( pAlphaMap[y*width+x] == 0 )
				{
					int nNeighbors = 0;
					int neighborTotal[3] = {0,0,0};

					// Blend all the neighboring solid pixels.
					for ( int offsetX=-1; offsetX <= 1; offsetX++ )
					{
						for ( int offsetY=-1; offsetY <= 1; offsetY++ )
						{
							int testX = x + offsetX;
							int testY = y + offsetY;
							if ( testX >= 0 && testY >= 0 && testX < width && testY < height )
							{
								if ( pAlphaMap[testY*width+testX] )
								{
									RGBAColor *pNeighbor = &pTexels[testY*width+testX];
									++nNeighbors;
									neighborTotal[0] += pNeighbor->r;
									neighborTotal[1] += pNeighbor->g;
									neighborTotal[2] += pNeighbor->b;
								}
							}
						}
					}

					if ( nNeighbors )
					{
						pNewAlphaMap[y*width+x] = 255;
						bHappy = false;
						pLine[x].r = (byte)( neighborTotal[0] / nNeighbors );
						pLine[x].g = (byte)( neighborTotal[1] / nNeighbors );
						pLine[x].b = (byte)( neighborTotal[2] / nNeighbors );
					}
				}
			}
		}

		memcpy( pAlphaMap, pNewAlphaMap, width * height );
	}

	delete [] pAlphaMap;
	delete [] pNewAlphaMap;
}


RGBAColor* ResampleImage( RGBAColor *pRGB, int width, int height, int newWidth, int newHeight )
{
	RGBAColor *pResampled = new RGBAColor[newWidth * newHeight];
	for ( int y=0; y < newHeight; y++ )
	{
		float yPercent = (float)y / (newHeight - 1);
		float flSrcY = yPercent * (height - 1.00001f);
		int iSrcY = (int)flSrcY;
		float flYFrac = flSrcY - iSrcY;

		for ( int x=0; x < newWidth; x++ )
		{
			float xPercent = (float)x / (newWidth - 1);
			float flSrcX = xPercent * (width - 1.00001f);
			int iSrcX = (int)flSrcX;
			float flXFrac = flSrcX - iSrcX;

			byte *pSrc0 = ((byte*)&pRGB[iSrcY*width+iSrcX]);
			byte *pSrc1 = ((byte*)&pRGB[iSrcY*width+iSrcX+1]);
			byte *pSrc2 = ((byte*)&pRGB[(iSrcY+1)*width+iSrcX]);
			byte *pSrc3 = ((byte*)&pRGB[(iSrcY+1)*width+iSrcX+1]);
			byte *pDest = (byte*)&pResampled[y*newWidth+x];

			// Now blend the nearest 4 source pixels.
			for ( int i=0; i < 4; i++ )
			{
				//pDest[i] = pSrc0[i];
				float topColor    = ( pSrc0[i]*(1-flXFrac) + pSrc1[i]*flXFrac );
				float bottomColor = ( pSrc2[i]*(1-flXFrac) + pSrc3[i]*flXFrac );
				pDest[i] = (byte)( topColor*(1-flYFrac) + bottomColor*flYFrac );
			}
		}
	}
	return pResampled;
}


bool WriteTGAFile( 
	const char *pFilename, 
	bool bAllowTranslucent,
	byte *pBits, 
	int width, 
	int height, 
	byte *pPalette,
	bool bPowerOf2,
	bool *bTranslucent, 
	bool *bResized
	)
{
	*bResized = *bTranslucent = false;

	RGBAColor *pRGB = ConvertToRGBUpsideDown( pBits, width, height, pPalette, bTranslucent );

	// Unless the filename starts with '{', we don't allow translucency.
	if ( !bAllowTranslucent )
		*bTranslucent = false;

	// Flood the solid texel colors into the transparent texels.
	// Since we turn on point sampling for these textures, this only matters if we're resizing the texture.
	if ( *bTranslucent )
	{
		FloodSolidPixels( pRGB, width, height );
	}

	if ( bPowerOf2 )
	{
		// Is it not a power of 2?
		if ( (width & (width - 1)) || (height & (height - 1)) )
		{
			// Ok, resize it to the next highest power of 2.
			int newWidth = width;
			while ( (newWidth & (newWidth - 1)) )
				++newWidth;

			int newHeight = height;
			while ( (newHeight & (newHeight - 1)) )
				++newHeight;

			printf( "\t(%dx%d) -> (%dx%d)", width, height, newWidth, newHeight );

			RGBAColor *pResampled = ResampleImage( pRGB, width, height, newWidth, newHeight );
			delete [] pRGB;
			pRGB = pResampled;
			
			width = newWidth;
			height = newHeight;

			*bResized = true;
		}
	}

	// Write it..	
	TGAHeader_t hdr;
	memset( &hdr, 0, sizeof( hdr ) );

	hdr.width = width;
	hdr.height = height;
	hdr.colormap_type = 0;	// no, no colormap please
	hdr.image_type = 2;		// uncompressed, true-color
	hdr.pixel_size = 32;	// 32 bits per pixel
	
	FILE *fp = fopen( pFilename, "wb" );
	if ( !fp )
		return false;

	SafeWrite( fp, &hdr, sizeof( hdr ) );
	SafeWrite( fp, pRGB, sizeof( RGBAColor ) * width * height );	
	fclose( fp );

	delete [] pRGB;
	return true;
}


int PrintUsage( const char *pExtra, ... )
{
	va_list marker;
	va_start( marker, pExtra );
	vprintf( pExtra, marker );
	va_end( marker );

	printf( 
		"%s \n"
		"\t[-AutoDir]\n"
			"\t\tAutomatically detects -basedir and -wadfile or -bmpfile based\n"
			"\t\ton the last parameter (it must be a WAD file or a BMP file.\n"
		"\t[-decal]\n"
			"\t\tCreates VMTs for decals and creates VMTs for model decals.\n"
		"\t[-onlytex <tex name>]\n"
		"\t[-shader <shader name>]\n"
			"\t\tSpecify the shader to use in the VMT file (default\n"
			"\t\tis LightmappedGeneric.\n"
		"\t[-vtex]\n"
			"\t\tIf -vtex is specified, then it calls vtex on each newly-created\n"
			"\t\t.TGA file.\n"
		"\t[-vmtparam <ParamName> <ParamValue>]\n"
			"\t\tif -vtex was specified, passes the parameters to that process.\n"
			"\t\tUsed to add parameters to the generated .vmt file\n"
		"\t-BaseDir <basedir>\n"
		"\t-game <basedir>\n"
			"\t\tSpecifies where the root mod directory is.\n"
		"\t-WadFile <wildcard>\n"
			"\t\t-wadfile will make (power-of-2) TGAs, VTFs, and VMTs for each\n"
			"\t\ttexture in the WAD. It will also place them under a directory\n"
			"\t\twith the name of the WAD. In addition, it will create\n"
			"\t\t.resizeinfo files in the materials directory if it has to\n"
			"\t\tresize the texture. Then Hammer's File->WAD To Material\n"
			"\t\tcommand will use them to rescale texture coordinates.\n"
		"\t-bmpfile <wildcard>\n"
			"\t\t-bmpfile acts like -WadFile but for BMP files, and it'll place\n"
			"\t\tthem in the root materials directory.\n"
		"\t-sprfile <wildcard>\n"
			"\t\tActs like -bmpfile, but ports a sprite.\n"
		"\t-Transparent (BMP files only)\n"
			"\t\tIf this is set, then it will treat palette index 255 as a\n"
			"\t\ttransparent pixel."
		"\t-SubDir <subdirectory>\n"
			"\t\t-SubDir tells it what directory under materials to place the\n"
			"\t\tfinal art. If using a WAD file, then it will automatically\n"
			"\t\tuse the WAD filename if no -SubDir is specified.\n"
		"\t-Quiet\n"
			"\t\tDon't print out anything or wait for a keypress on exit.\n"
		"\n"
		, __argv[0] );
	printf( "ex: %s -vtex -BaseDir c:\\hl2\\dod -WadFile c:\\hl1\\dod\\*.wad\n", __argv[0] );
	printf( "ex: %s -vtex -BaseDir c:\\hl2\\dod -bmpfile test.bmp -SubDir models\\props\n", __argv[0] );
	printf( "ex: %s -vtex -vmtparam $ignorez 1 -BaseDir c:\\hl2\\dod -sprfile test.spr -SubDir sprites\\props\n", __argv[0] );
	
	PrintExitStuff();
	return 1;
}


// Take something like "c:/a/b/c/filename.ext" and return "filename".
void GetBaseFilename( const char *pWadFilename, char wadBaseName[512] )
{
	const char *pBase = strrchr( pWadFilename, '\\' );
	if ( strrchr( pWadFilename, '/' ) > pBase )
		pBase = strrchr( pWadFilename, '/' );

	if ( pBase )
		strcpy( wadBaseName, pBase+1 );
	else
		strcpy( wadBaseName, pWadFilename );

	char *pDot = strchr( wadBaseName, '.' );
	if ( pDot )
		*pDot = 0;
}


const char *LastSlash( const char *pSrc )
{
	const char *pRet = strrchr( pSrc, '/' );
	const char *pRet2 = strrchr( pSrc, '\\' );
	return (pRet > pRet2) ? pRet : pRet2;
}


void EnsureDirExists( const char *pDir )
{
	if ( _access( pDir, 0 ) != 0 )
	{
		// We use the shell's "md" command here instead of the _mkdir() function because 
		// md will create all the subdirectories leading up to the bottom one and _mkdir() won't.
		char cmd[1024];
		_snprintf( cmd, sizeof( cmd ), "md \"%s\"", pDir );
		system( cmd );

		if ( _access( pDir, 0 ) != 0 )
			Error( "\tCan't create directory: %s.\n", pDir );
	}
}


void WriteVMTFile( const char *pBaseDir, const char *pSubDir, const char *pName, bool bTranslucent )
{
	char vmtFilename[512];
	sprintf( vmtFilename, "%s\\materials\\%s\\%s.vmt", pBaseDir, pSubDir, pName );

	FILE *fp = fopen( vmtFilename, "wt" );
	if ( !fp )
	{
		Error( "\tWriteVMTFile failed to open %s for writing.\n", vmtFilename );
		return;
	}
	
	fprintf( fp, "\"%s\"\n{\n", g_pShader );
	fprintf( fp, "\t\"$basetexture\"\t\"%s\\%s\"\n", pSubDir, pName );
	
	if ( bTranslucent || g_bDecal )
	{
		fprintf( fp, "\t\"$alphatest\"\t\"1\"\n" );
		fprintf( fp, "\t\"$ALPHATESTREFERENCE\"\t\"0.5\"\n" );
	}

	if ( g_bDecal )
	{
		fprintf( fp, "\t\"$decal\"\t\t\"1\"\n" );
		
	}
	
	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, "}" );

	fclose( fp );
}


void WriteTXTFile( const char *pBaseDir, const char *pSubDir, const char *pName )
{
	char filename[512];
	sprintf( filename, "%s\\materialsrc\\%s\\%s.txt", pBaseDir, pSubDir, pName );

	FILE *fp = fopen( filename, "wt" );
	if ( !fp )
		Error( "\tWriteTXTFile: can't open %s for writing.\n", filename );

	fprintf( fp, "\"pointsample\" \"1\"\n" );
	fclose( fp );
}


void WriteResizeInfoFile( const char *pBaseDir, const char *pSubDir, const char *pName, int width, int height )
{
	char filename[512];
	sprintf( filename, "%s\\materials\\%s\\%s.resizeinfo", pBaseDir, pSubDir, pName );

	FILE *fp = fopen( filename, "wt" );
	if ( !fp )
	{
		Error( "\tWriteResizeInfoFile failed to open %s for writing.\n", filename );
		return;
	}
	
	fprintf( fp, "%d %d", width, height );
	fclose( fp );
}


void RunVTexOnFile( const char *pBaseDir, const char *pFilename )
{
	char executableDir[MAX_PATH];
	GetModuleFileName( NULL, executableDir, sizeof( executableDir ) );
	
	char *pLastSlash = max( strrchr( executableDir, '/' ), strrchr( executableDir, '\\' ) );
	if ( !pLastSlash )
		Error( "Can't find filename in '%s'.\n", executableDir );
	
	*pLastSlash = 0;

	// Set the vproject environment variable (vtex doesn't allow game yet).
	char envStr[MAX_PATH];
	_snprintf( envStr, sizeof( envStr ), "vproject=%s", pBaseDir );
	_putenv( envStr );

	// Call vtex on this texture now.
	char vtexCommand[1024];
	sprintf( vtexCommand, "%s\\vtex.exe -quiet -nopause \"%s\"", executableDir, pFilename );
	if ( system( vtexCommand ) != 0 )
	{
		Error( "\tCommand '%s' failed!\n", vtexCommand );
	}
}


void WriteOutputFiles(
	const char *pBaseDir,
	const char *pSubDir,
	const char *pName,
	bool bAllowTranslucent,
	byte *buffer,
	int width,
	int height,
	byte *pPalette,
	bool bVTex
)
{
	bool bTranslucent, bResized;
	bool bPowerOf2 = true;

	char tgaFilename[1024];
	sprintf( tgaFilename, "%s\\materialsrc\\%s\\%s.tga", pBaseDir, pSubDir, pName );
	if ( !WriteTGAFile( 
		tgaFilename, 
		bAllowTranslucent, 
		buffer, 
		width, 
		height, 
		pPalette, 
		bPowerOf2, 
		&bTranslucent, 
		&bResized ) )
	{
		Error( "\tError writing %s.\n", tgaFilename );
	}

	// Write its .VMT file.
	WriteVMTFile( pBaseDir, pSubDir, pName, bTranslucent );

	// Write a text file for it if it's translucent so we can enable pointsample for vtex.
//	if ( bTranslucent )
//		WriteTXTFile( pBaseDir, pSubDir, pName );

	// Write its .resizeinfo file.
	if ( bResized )
	{
		WriteResizeInfoFile( pBaseDir, pSubDir, pName, width, height );
	}

	if ( bVTex )
	{
		RunVTexOnFile( pBaseDir, tgaFilename );
	}
}


void EnsureDirectoriesExist( const char *pBaseDir, const char *pSubDir )
{
	char materialsrcDir[512], materialsDir[512];
	sprintf( materialsrcDir, "%s\\materialsrc\\%s", pBaseDir, pSubDir );
	sprintf( materialsDir, "%s\\materials\\%s", pBaseDir, pSubDir );
	EnsureDirExists( materialsrcDir );
	EnsureDirExists( materialsDir );
}


void ProcessWadFile( const char *pWadFilename, const char *pBaseDir, const char *pSubDir, const char *pOnlyTex, bool bVTex )
{
	if ( !g_bQuiet )
		printf( "\n\n[WADFILE %s]\n\n", pWadFilename );

	// If no -subdir was specified, then figure it out from the wad filename.
	char wadBaseName[512];
	if ( !pSubDir )
	{
		// Get the base wad filename.
		GetBaseFilename( pWadFilename, wadBaseName );
		pSubDir = wadBaseName;
	}

	EnsureDirectoriesExist( pBaseDir, pSubDir );
		

	// Now process all the images in the wad.
	W_OpenWad( pWadFilename );

	#define MAXLUMP (640*480*85/64)
	byte inbuffer[MAXLUMP];

	for (int i = 0; i < numlumps; i++) 
	{
		if ( pOnlyTex && _stricmp( pOnlyTex, lumpinfo[i].name ) != 0 )
			continue;

		fseek( wadhandle, lumpinfo[i].filepos, SEEK_SET );
		SafeRead ( wadhandle, inbuffer, lumpinfo[i].size );

		miptex_t *qtex = (miptex_t *)inbuffer;
		int width = LittleLong(qtex->width);
		int height = LittleLong(qtex->height);

		if ( width <= 0 || height <= 0 || width > 5000 || height > 5000 )
		{
			if ( !g_bQuiet )
				printf("\tskipping %s @ %d  size %d (not an image?)\n", lumpinfo[i].name, lumpinfo[i].filepos, lumpinfo[i].size );
			continue;
		}
		else
		{
			if ( !g_bQuiet )
				printf( "\t%s", lumpinfo[i].name );
		}

		byte *pPalette = inbuffer + LittleLong( qtex->offsets[3] ) + width * height / 64 + 2;
		byte *psrc, *pdest;

		byte outbuffer[(640+320)*480];
	
		// The old xwad	put the mipmaps in there too, but we don't want that now (usually).
		// copy in 0 image
		psrc = inbuffer + LittleLong( qtex->offsets[0] );
		pdest = outbuffer;
		for (int t = 0; t < height; t++) 
		{
			memcpy( pdest + t * width, psrc + t * width, width );
		}

		
		WriteOutputFiles( 
			pBaseDir,				// base directory
			pSubDir,				// subdir under materials
			qtex->name,				// filename (w/o extension)
			qtex->name[0] == '{',	// allow transparency?
			outbuffer, 
			width, 
			height, 
			pPalette,
			bVTex
			);

		if ( !g_bQuiet )
			printf( "\n" );
	}
}


void ProcessBMPFile( const char *pBaseDir, const char *pSubDir, const char *pFilename, bool bVTex )
{
	if ( !g_bQuiet )
		printf( "[%s]\n", pFilename );

	if ( !pSubDir )
		pSubDir = ".";

	// First make directories under materialsrc and materials if they don't exist.
	EnsureDirectoriesExist( pBaseDir, pSubDir );

	// Read in the 8-bit BMP file.
	FILE *fp = fopen( pFilename, "rb" );
	if ( !fp )
		Error( "ProcessBMPFile( %s ) can't open the file for reading.\n", pFilename );

	BITMAPFILEHEADER bfh;
	BITMAPINFOHEADER bih;
	unsigned char pixelData[512*512];

	SafeRead( fp, &bfh, sizeof( bfh ) );
	SafeRead( fp, &bih, sizeof( bih ) );

	// Make sure it's an 8-bit one like we want.
	if ( bih.biSize != sizeof( bih ) || 
		bih.biPlanes != 1 || 
		bih.biBitCount != 8 || 
		bih.biCompression != BI_RGB ||
		bih.biHeight < 0 ||
		bih.biWidth * bih.biHeight > sizeof( pixelData ) )
	{
		Error( "ProcessBMPFile( %s ) - invalid format.\n", pFilename );
	}

	int nColorsUsed = 256;
	if ( bih.biClrUsed != 0 )
	{
		nColorsUsed = bih.biClrUsed;
		if ( nColorsUsed > 256 )
			Error( "ProcessBMPFile( %s ) - bih.biClrUsed = %d.\n", pFilename, bih.biClrUsed );
	}

	RGBQUAD quadPalette[256];
	SafeRead( fp, quadPalette, sizeof( quadPalette[0] ) * nColorsUsed );

	// Usually, bfOffBits is the same place we are at now, but sometimes it's a little different.
	fseek( fp, bfh.bfOffBits, SEEK_SET );

	// Now read the bitmap data.
	SafeRead( fp, pixelData, bih.biWidth * bih.biHeight );
	
	fclose( fp );


	// Convert the palette.
	byte palette[256][3];
	for ( int i=0; i < 256; i++ )
	{
		palette[i][0] = quadPalette[i].rgbRed;
		palette[i][1] = quadPalette[i].rgbGreen;
		palette[i][2] = quadPalette[i].rgbBlue;
	}

	// Unflip the pixel data.
	for ( int y=0; y < bih.biHeight / 2; y++ )
	{
		byte tempLine[1024];
		memcpy( tempLine, &pixelData[y*bih.biWidth], bih.biWidth );
		memcpy( &pixelData[y*bih.biWidth], &pixelData[(bih.biHeight-y-1)*bih.biWidth], bih.biWidth );
		memcpy( &pixelData[(bih.biHeight-y-1)*bih.biWidth], tempLine, bih.biWidth );
	}


	char baseFilename[512];
	GetBaseFilename( pFilename, baseFilename );

	// Save it out.
	WriteOutputFiles( 
		pBaseDir,		// base directory
		pSubDir,		// subdir under materials
		baseFilename,	// filename (w/o extension)
		g_bBMPAllowTranslucent,	// allow transparency
		pixelData, 
		bih.biWidth, 
		bih.biHeight, 
		(byte*)palette,
		bVTex
		);
}


void ProcessSPRFile( const char *pBaseDir, const char *pSubDir, const char *pFilename, bool bVTex )
{
	if ( !g_bQuiet )
		printf( "[%s]\n", pFilename );

	if ( !pSubDir )
		pSubDir = ".";

	char baseFilename[512];
	GetBaseFilename( pFilename, baseFilename );

	// First make directories under materialsrc and materials if they don't exist.
	EnsureDirectoriesExist( pBaseDir, pSubDir );

	// Read in the SPR file.
	FILE *fp = fopen( pFilename, "rb" );
	if ( !fp )
		Error( "ProcessSPRFile( %s ) can't open the file for reading.\n", pFilename );
	
	dsprite_t header;
	SafeRead( fp, &header, sizeof( header ) );

	// Make sure it's a sprite file.
	if ( ((header.ident>>0) & 0xFF) != 'I' || 
		((header.ident>>8) & 0xFF) != 'D' ||
		((header.ident>>16) & 0xFF) != 'S' ||
		((header.ident>>24) & 0xFF) != 'P' )
	{
		Warning( "WARNING: sprite %s is not a sprite file. Skipping.\n", pFilename );
		fclose( fp );
		return;
	}

	if ( header.version != 2 )
	{
		Warning( "WARNING: sprite %s is not a version 2 sprite file. Skipping.\n", pFilename );
		fclose( fp );
		return;
	}

	// Read the palette.
	short cnt;
	byte palette[768];
	SafeRead( fp, &cnt, sizeof( cnt ) );
	SafeRead( fp, palette, sizeof( palette ) );

	// Read the frames.
	int i;
	for ( i=0; i < header.numframes; i++ )
	{
		spriteframetype_t type;
		SafeRead( fp, &type, sizeof( type ) );
		if ( type == SPR_SINGLE )
		{
			dspriteframe_t frame;
			SafeRead( fp, &frame, sizeof( frame ) );
			if ( frame.width > 5000 || frame.height > 5000 || frame.width < 1 || frame.height < 1 )
			{
				Warning( "WARNING: sprite %s has an invalid frame size (%d x %d) for frame %d.\n", pFilename, frame.width, frame.height, i );
				fclose( fp );
				return;
			}
				
			byte *frameData = new byte[frame.width * frame.height];
			SafeRead( fp, frameData, frame.width * frame.height );

			Msg( "\tFrame %d   ", i );

			// Write the TGA file for this frame.
			bool bTranslucent, bResized;
			char frameFilename[512];
			_snprintf( frameFilename, sizeof( frameFilename ), "%s\\materialsrc\\%s\\%s%03d.tga", pBaseDir, pSubDir, baseFilename, i );
			if ( !WriteTGAFile( 
				frameFilename, 
				g_bBMPAllowTranslucent, 
				frameData, 
				frame.width, 
				frame.height, 
				palette, 
				true,			// allow power-of-2
				&bTranslucent, 
				&bResized ) )
			{
				Error( "\tError writing %s.\n", frameFilename );
			}

			if ( !g_bQuiet )
				printf( "\n" );

			delete [] frameData;
		}
		else if ( type == SPR_GROUP )
		{
			Error( "Sprite %s uses type SPR_GROUP. Get a programmer to add support for it.\n", pFilename );
		}
		else
		{
			Warning( "WARNING: sprite %s has an invalid frame type (%d) for frame %d.\n", pFilename, type, i );
			fclose( fp );
			return;
		}
	}
		
	fclose( fp );

	//
	// Generate a .txt file for the sprite.
	//
	char txtFilename[512];
	sprintf( txtFilename, "%s\\materialsrc\\%s\\%s.txt", pBaseDir, pSubDir, baseFilename );

	fp = fopen( txtFilename, "wt" );
	if ( !fp )
		Error( "\tProcessSPRFile: can't open %s for writing.\n", txtFilename );

	fprintf( fp, "\"startframe\" \"0\"\n" );
	fprintf( fp, "\"endframe\" \"%d\"\n", header.numframes-1 );
	fprintf( fp, "\"nomip\" \"1\"\n" );
	fprintf( fp, "\"nolod\" \"1\"\n" );
	fclose( fp );

	//
	// Run VTEX on the .txt file?
	//
	if ( bVTex )
	{
		RunVTexOnFile( pBaseDir, txtFilename );
	}

	//
	// Generate a .vmt file.
	//
	char vmtFilename[512];
	_snprintf( vmtFilename, sizeof( vmtFilename ), "%s\\materials\\%s\\%s.vmt", pBaseDir, pSubDir, baseFilename );
	fp = fopen( vmtFilename, "wt" );
	if ( !fp )
		Error( "\tProcessSPRFile: can't open %s for writing.\n", vmtFilename );

	if ( g_pShader == g_pDefaultShader )
		fprintf( fp, "\"UnlitGeneric\"\n" );
	else
		fprintf( fp, "\"%s\"\n", g_pShader );

	fprintf( fp, "{\n" );
	fprintf( fp, "\t\"$spriteorientation\" \"vp_parallel\"\n" );
	fprintf( fp, "\t\"$spriteorigin\" \"[ 0.50 0.50 ]\"\n" );
	fprintf( fp, "\t\"$basetexture\" \"%s/%s\"\n", pSubDir, baseFilename );

	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, "}" );

	fclose( fp );
}


void ExtractDirectory( const char *pFilename, char *prefix )
{
	const char *pSlash = strrchr( pFilename, '/' );
	if ( strrchr( pFilename, '\\' ) > pSlash )
		pSlash = strrchr( pFilename, '\\' );

	if ( pSlash )
	{
		memcpy( prefix, pFilename, pSlash - pFilename );
		prefix[ pSlash - pFilename ] = 0;
	}
	else
	{
		strcpy( prefix, ".\\" );
	}
}


// This allows them to have a WAD or BMP under their materialsrc directory and it'll try to figure out
// all the other parameters for them.
bool DragAndDropCheck( 
	const char **pBaseDir, 
	const char **pSubDir, 
	const char **pWadFilenames, 
	const char **pBMPFilenames,
	const char **pSPRFilenames,
	bool *bVTex )
{
	const char *pLastParam = __argv[__argc-1];

	// Get the first argument in upper case.
	char arg1[512];
	strncpy( arg1, pLastParam, sizeof( arg1 ) );
	_strupr( arg1 );

	// Only handle it if there's a full path (with a colon).
	if ( !strchr( arg1, ':' ) )
		return false;

	if ( strstr( arg1, ".WAD" ) )
		*pWadFilenames = pLastParam;
	else if ( strstr( arg1, ".BMP" ) )
		*pBMPFilenames = pLastParam;
	else if ( strstr( arg1, ".SPR" ) )
		*pSPRFilenames = pLastParam;
	else
		return false;
	
	// Ok, we know that argv[1] has a valid filename. Is it under materialsrc?
	char *pMatSrc = strstr( arg1, "MATERIALSRC" );
	if ( !pMatSrc || pMatSrc == arg1 )
		return false;

	// The base directory is everything before materialsrc.
	static char baseDir[512];
	*pBaseDir = baseDir;
	memcpy( baseDir, arg1, pMatSrc-arg1 );
	baseDir[pMatSrc-arg1-1] = 0;

	// The subdirectory is everything after materialsrc, minus the filename.
	char *pSubDirSrc = pMatSrc + strlen( "MATERIALSRC" ) + 1;
	char *pEnd = strrchr( pSubDirSrc, '\\' );
	if ( strrchr( pSubDirSrc, '/' ) > pEnd )
		pEnd = strrchr( pSubDirSrc, '/' );

	if ( !pEnd )
		return false;

	static char subDir[512];
	*pSubDir = subDir;
	memcpy( subDir, pSubDirSrc, pEnd - pSubDirSrc );
	subDir[pEnd-pSubDirSrc] = 0;

	// Always use vtex in drag-and-drop mode.	
	*bVTex = true;
	return true;
}


int main (int argc, char **argv)
{
	if (argc < 2) 
	{
		return PrintUsage( "" );
	}

	bool bWriteTGA = true;
	bool bWriteBMP = false;
	bool bPowerOf2 = true;
	bool bAutoDir = false;

	bool bVTex = false;
	const char *pBaseDir = NULL;
	const char *pWadFilenames = NULL;
	const char *pBMPFilenames = NULL;
	const char *pSPRFilenames = NULL;
	const char *pSubDir = NULL;
	const char *pOnlyTex = NULL;

	// Scan for options.
	for ( int i=1; i < argc; i++ )
	{
		if ( (i+2) < argc )
		{
			if ( _stricmp( argv[i], "-vmtparam" ) == 0 && g_NumVMTParams < MAX_VMT_PARAMS )
			{
				g_VMTParams[g_NumVMTParams].m_szParam = argv[i+1];
				g_VMTParams[g_NumVMTParams].m_szValue = argv[i+2];

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

				g_NumVMTParams++;

				i += 2;
			}
		}

		if ( (i+1) < argc )
		{
			if ( _stricmp( argv[i], "-basedir" ) == 0 )
			{
				pBaseDir = argv[i+1];
				++i;
			}
			else if ( _stricmp( argv[i], "-wadfile" ) == 0 )
			{
				pWadFilenames = argv[i+1];
				++i;
			}
			else if ( _stricmp( argv[i], "-bmpfile" ) == 0 )
			{
				pBMPFilenames = argv[i+1];
				++i;
			}
			else if ( _stricmp( argv[i], "-sprfile" ) == 0 )
			{
				pSPRFilenames = argv[i+1];
				++i;
			}
			else if ( _stricmp( argv[i], "-OnlyTex" ) == 0 )
			{
				pOnlyTex = argv[i+1];
				++i;
			}
			else if ( _stricmp( argv[i], "-SubDir" ) == 0 )
			{
				pSubDir = argv[i+1];
				++i;
			}
			else if ( _stricmp( argv[i], "-shader" ) == 0 )
			{
				g_pShader = argv[i+1];
				++i;
			}
		}
		
		if ( _stricmp( argv[i], "-AutoDir" ) == 0 )
		{
			bAutoDir = true;
		}
		else if ( _stricmp( argv[i], "-Transparent" ) == 0 )
		{
			g_bBMPAllowTranslucent = true;
		}
		else if ( _stricmp( argv[i], "-Decal" ) == 0 )
		{	
			g_bDecal = true;
			if ( g_pShader == g_pDefaultShader )
				g_pShader = "DecalModulate";
		}
		else if ( _stricmp( argv[i], "-quiet" ) == 0 )
		{
			g_bQuiet = true;
		}
		else if ( _stricmp( argv[i], "-vtex" ) == 0 )
		{
			bVTex = true;
		}
	}
	
	if ( bAutoDir )
	{
		if ( !DragAndDropCheck( &pBaseDir, &pSubDir, &pWadFilenames, &pBMPFilenames, &pSPRFilenames, &bVTex ) )
			return PrintUsage( "-AutoDir failed to setup directories." );
	}

	if ( !pBaseDir || (!pWadFilenames && !pBMPFilenames && !pSPRFilenames) )
		return PrintUsage( "Missing a parameter.\n" );

	char prefix[512];

	// Scan through each wadfile.
	if ( pWadFilenames )
	{
		ExtractDirectory( pWadFilenames, prefix );

		_finddata_t findData;
		long handle = _findfirst( pWadFilenames, &findData );
		if ( handle != -1 )
		{
			do
			{
				if ( !(findData.attrib & _A_SUBDIR) )
				{
					char fullFilename[512];
					sprintf( fullFilename, "%s\\%s", prefix, findData.name );
					ProcessWadFile( fullFilename, pBaseDir, pSubDir, pOnlyTex, bVTex );
				}
			} while( _findnext( handle, &findData ) == 0 );

			_findclose( handle );
		}
	}

	// Process BMP files.
	if ( pBMPFilenames )
	{
		ExtractDirectory( pBMPFilenames, prefix );

		_finddata_t findData;
		long handle = _findfirst( pBMPFilenames, &findData );
		if ( handle != -1 )
		{
			do
			{
				if ( !(findData.attrib & _A_SUBDIR) )
				{
					char fullFilename[512];
					sprintf( fullFilename, "%s\\%s", prefix, findData.name );
					ProcessBMPFile( pBaseDir, pSubDir, fullFilename, bVTex );
				}
			} while( _findnext( handle, &findData ) == 0 );

			_findclose( handle );
		}
	}

	if ( pSPRFilenames )
	{
		ExtractDirectory( pSPRFilenames, prefix );

		_finddata_t findData;
		long handle = _findfirst( pSPRFilenames, &findData );
		if ( handle != -1 )
		{
			do
			{
				if ( !(findData.attrib & _A_SUBDIR) )
				{
					char fullFilename[512];
					sprintf( fullFilename, "%s\\%s", prefix, findData.name );
					ProcessSPRFile( pBaseDir, pSubDir, fullFilename, bVTex );
				}
			} while( _findnext( handle, &findData ) == 0 );

			_findclose( handle );
		}
	}

	PrintExitStuff();
	return 0;
}	

