//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Generates a file list based on command line wildcard spec and drives
// conversion routines based on file extension.  The conversion routines should be
// !!!elsewhere!!! in libraries that the game also uses at runtime to convert data.
// This tool as spec'd should just be file iteration.
//
//=====================================================================================//

#include "MakeGameData.h"

// MAKESCENESIMAGE is defined for the external tool. In general, it only
//  supports the -pcscenes option. This gets built into MakeScenesImage.exe.

//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
class MakeGameDataApp : public CDefaultAppSystemGroup< CSteamAppSystemGroup >
{
public:
	// Methods of IApplication
	virtual bool Create();
	virtual bool PreInit( );
	virtual int Main();
	virtual void PostShutdown();
};

DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( MakeGameDataApp );

char			g_szSourcePath[MAX_PATH];
char			g_targetPath[MAX_PATH];
char			g_zipPath[MAX_PATH];
bool			g_bForce;
bool			g_bTest;
bool			g_bMakeZip;
CXZipTool		g_MasterXZip;
DiskWriteMode_t	g_WriteModeForConversions;
char			g_szGamePath[MAX_PATH];
char			g_szModPath[MAX_PATH];
bool			g_bModPathIsValid;
bool			g_bQuiet;
bool			g_bMakeScenes;
bool			g_bMakeScenesPC;
bool			g_bIsPlatformZip;
bool			g_bUseMapList;

IPhysicsCollision	*g_pPhysicsCollision;
CSysModule			*g_pPhysicsModule;

CUtlVector< CUtlString > g_ValidMapList;
CUtlVector< errorList_t > g_errorList;

const char *g_GameNames[] = 
{
	"ep2",
	"episodic",
	"hl2",
	"portal",
	"platform",
	"tf",
	NULL
};

// all known languages
const char *g_pLanguageSuffixes[] = 
{
	"_dannish",
	"_dutch",
	"_english",
	"_finnish",
	"_french",
	"_german",
	"_italian",
	"_japanese",
	"_korean",
	"_koreana",
	"_norwegian",
	"_polish",
	"_portuguese",
	"_russian",
	"_russion_buka",
	"_schinese",
	"_spanish",
	"_swedish",
	"_tchinese",
	"_thai",
};

// 360 is shipping with support for only these languages
const char *g_pTargetLanguageSuffixes[] = 
{
	"_english.",
	"_french.",
	"_german.",
};

// Master list of files that can go into the zip
static char *s_AllowedExtensionsInZip[] = 
{
	// Explicitly lacking from this list, thus excluded from the zip
	// .ain - AINs are external to the zip
	// .bsp - BSPs are external to the zip
	// .mp3 - MP3s aren't supported

	// Extensions with conversions
	// Purposely using .360 encoding to ensure pc versions don't leak in
	".360.wav",
	".360.vtf",
	".360.mdl",
	".360.ani",
	".dx90.360.vtx",
	".360.vvd",
	".360.phy",
	".360.dat",
	".360.lst",
	".360.vcs",
	".360.image",
	".360.pcf",

	// Extensions without conversions (taken as is)
	".rc",
	".txt",
	".cfg",
	".res",
	".vfe",
	".vbf",
	".vmt",
	".raw",
	".lst",
	".bns",
};



//-----------------------------------------------------------------------------
// Determine game path
//-----------------------------------------------------------------------------
void GetGamePath()
{
	GetCurrentDirectory( sizeof( g_szGamePath ), g_szGamePath );

	char szFullPath[MAX_PATH];
	if ( _fullpath( szFullPath, g_szGamePath, sizeof( szFullPath ) ) )
	{
		strcpy( g_szGamePath, szFullPath );
	}
	V_AppendSlash( g_szGamePath, sizeof( g_szGamePath ) );

	char *pGameDir = V_stristr( g_szGamePath, "game\\" );
	if ( !pGameDir )
	{
		Msg( "ERROR: Failed to determine game directory from current path. Expecting 'game' in current path." );
		exit( 1 );
	}

	// kill any trailing dirs
	pGameDir[4] = '\0';
}

//-----------------------------------------------------------------------------
// Determine mod path
//-----------------------------------------------------------------------------
bool GetModPath()
{
	char	szDirectory[MAX_PATH];
	char	szLastDirectory[MAX_PATH];

	// non destructively determine the mod directory
	bool bFound = false;
	szLastDirectory[0] = '\0';
	GetCurrentDirectory( sizeof( szDirectory ), szDirectory );
	while ( 1 )
	{
		V_ComposeFileName( szDirectory, "gameinfo.txt", g_szModPath, sizeof( g_szModPath ) );
		struct _stat statBuf;
		if ( _stat( g_szModPath, &statBuf ) != -1 )
		{
			bFound = true;
			V_strncpy( g_szModPath, szDirectory, sizeof( g_szModPath ) );
			break;
		}

		// previous dir
		V_ComposeFileName( szDirectory, "..", g_szModPath, sizeof( g_szModPath ) );

		char fullPath[MAX_PATH];
		if ( _fullpath( fullPath, g_szModPath, sizeof( fullPath ) ) )
		{
			strcpy( szDirectory, fullPath );
		}

		if ( !V_stricmp( szDirectory, szLastDirectory ) )
		{
			// can back up no further
			break;
		}
		strcpy( szLastDirectory, szDirectory );
	}

	if ( !bFound )
	{
		// use current directory instead
		GetCurrentDirectory( sizeof( g_szModPath ), g_szModPath );
	}

	return bFound;
}

//-----------------------------------------------------------------------------
// Setup File system and search paths
//-----------------------------------------------------------------------------
bool SetupFileSystem()
{
	if ( g_bModPathIsValid )
	{
		CFSSteamSetupInfo steamInfo;
		steamInfo.m_pDirectoryName = g_szModPath;
		steamInfo.m_bOnlyUseDirectoryName = true;
		steamInfo.m_bToolsMode = true;
		steamInfo.m_bSetSteamDLLPath = true;
		steamInfo.m_bSteam = false;
		if ( FileSystem_SetupSteamEnvironment( steamInfo ) != FS_OK )
			return false;

		CFSMountContentInfo fsInfo;
		fsInfo.m_pFileSystem = g_pFullFileSystem;
		fsInfo.m_bToolsMode = true;
		fsInfo.m_pDirectoryName = steamInfo.m_GameInfoPath;
		if ( FileSystem_MountContent( fsInfo ) != FS_OK )
			return false;

		// Finally, load the search paths for the "GAME" path.
		CFSSearchPathsInit searchPathsInit;
		searchPathsInit.m_pDirectoryName = steamInfo.m_GameInfoPath;
		searchPathsInit.m_pFileSystem = fsInfo.m_pFileSystem;
		if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK )
			return false;

		char platform[MAX_PATH];
		Q_strncpy( platform, steamInfo.m_GameInfoPath, MAX_PATH );
		Q_StripTrailingSlash( platform );
		Q_strncat( platform, "/../platform", MAX_PATH, MAX_PATH );
		fsInfo.m_pFileSystem->AddSearchPath( platform, "PLATFORM" );
	}

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Helper utility, read file into buffer
//-----------------------------------------------------------------------------
bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText, bool bNoOpenFailureWarning )
{
	return scriptlib->ReadFileToBuffer( pSourceName, buffer, bText, bNoOpenFailureWarning );
}

//-----------------------------------------------------------------------------
// Purpose: Helper utility, Write buffer to file
//-----------------------------------------------------------------------------
bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, bool bWriteToZip, DiskWriteMode_t writeMode )
{
	if ( g_bTest )
		return true;

	bool bSuccess = scriptlib->WriteBufferToFile( pTargetName, buffer, writeMode );
	bool bZipSuccess = true;

	if ( bSuccess && g_bMakeZip && !g_bTest && bWriteToZip )
	{
		if ( !g_MasterXZip.AddBuffer( pTargetName, buffer, true ) )
		{
			Msg( "WriteBufferToFile(): Error adding file %s\n", pTargetName );
			bZipSuccess = false;
		}
	}

	return bSuccess && bZipSuccess;
}

//-----------------------------------------------------------------------------
// Compress data
//-----------------------------------------------------------------------------
bool CompressCallback( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer )
{
	if ( !inputBuffer.TellPut() )
	{
		// nothing to do
		return false;
	}

	unsigned int compressedSize;
	unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)inputBuffer.Base() + inputBuffer.TellGet(), inputBuffer.TellPut() - inputBuffer.TellGet(), &compressedSize );
	if ( pCompressedOutput )
	{
		outputBuffer.EnsureCapacity( compressedSize );
		outputBuffer.Put( pCompressedOutput, compressedSize );
		free( pCompressedOutput );
		return true;
	}

	return false;
}



//-----------------------------------------------------------------------------
// Some converters need to run a final pass.
//-----------------------------------------------------------------------------
void DoPostProcessingFunctions( bool bWriteToZip )
{
	if ( g_bMakeScenes || g_bMakeScenesPC )
	{
		// scenes are converted and aggregated into one image
		CreateSceneImageFile( g_szModPath, bWriteToZip, g_bMakeScenesPC, g_bQuiet, g_WriteModeForConversions );
	}

	ProcessDXSupportConfig( bWriteToZip );
}

//-----------------------------------------------------------------------------
// Startup any conversion modules.
//-----------------------------------------------------------------------------
bool InitConversionModules()
{
	return true;
}

//-----------------------------------------------------------------------------
// Shutdown and cleanup any conversion modules.
//-----------------------------------------------------------------------------
void ShutdownConversionModules()
{
}

//-----------------------------------------------------------------------------
// Purpose: Distribute to worker function
//-----------------------------------------------------------------------------
bool CreateTargetFile( const char *pSourceName, const char *pTargetName, fileType_e fileType, bool bWriteToZip )
{
	// resolve relative source to absolute path
	// same workers use subdir name to determine conversion parameters
	char fullSourcePath[MAX_PATH];
	if ( _fullpath( fullSourcePath, pSourceName, sizeof( fullSourcePath ) ) )
	{
		pSourceName = fullSourcePath;
	}

	// distribute to actual worker
	// workers can expect exact final decorated filenames
	bool bSuccess = false;
	switch ( fileType )
	{
		case FILETYPE_UNKNOWN:
			break;

		case FILETYPE_VTF:
			bSuccess = CreateTargetFile_VTF( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_WAV:
			bSuccess = CreateTargetFile_WAV( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_MDL:
		case FILETYPE_ANI:
		case FILETYPE_VTX:
		case FILETYPE_VVD:
		case FILETYPE_PHY:
			bSuccess = CreateTargetFile_Model( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_BSP:
			bSuccess = CreateTargetFile_BSP( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_AIN:
			bSuccess = CreateTargetFile_AIN( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_CCDAT:
			bSuccess = CreateTargetFile_CCDAT( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_MP3:
			bSuccess = CreateTargetFile_MP3( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_RESLST:
			bSuccess = CreateTargetFile_RESLST( pSourceName, pTargetName, bWriteToZip );
			break;

		case FILETYPE_PCF:
			bSuccess = CreateTargetFile_PCF( pSourceName, pTargetName, bWriteToZip );
			break;

			// others...
	}

	return bSuccess;
}

//-----------------------------------------------------------------------------
// Purpose: Determine file type based on source extension.  Generate .360.xxx
// target name.
//-----------------------------------------------------------------------------
fileType_e ResolveFileType( const char *pSourceName, char *pTargetName, int targetNameSize )
{
	char szFullSourcePath[MAX_PATH];
	_fullpath( szFullSourcePath, pSourceName, sizeof( szFullSourcePath ) );

	char sourceExtension[MAX_PATH];
	V_ExtractFileExtension( pSourceName, sourceExtension, sizeof( sourceExtension ) );

	// default unrecognized
	fileType_e fileType = FILETYPE_UNKNOWN;

	if ( !V_stricmp( sourceExtension, "wav" ) )
	{
		 fileType = FILETYPE_WAV;
	}
	else if ( !V_stricmp( sourceExtension, "vtf" ) )
	{
		 fileType = FILETYPE_VTF;
	}
	else if ( !V_stricmp( sourceExtension, "mdl" ) )
	{
		fileType = FILETYPE_MDL;
	}
	else if ( !V_stricmp( sourceExtension, "ani" ) )
	{
		fileType = FILETYPE_ANI;
	}
	else if ( !V_stricmp( sourceExtension, "vvd" ) )
	{
		fileType = FILETYPE_VVD;
	}
	else if ( !V_stricmp( sourceExtension, "phy" ) )
	{
		fileType = FILETYPE_PHY;
	}
	else if ( !V_stricmp( sourceExtension, "bsp" ) )
	{
		fileType = FILETYPE_BSP;
	}
	else if ( !V_stricmp( sourceExtension, "ain" ) )
	{
		fileType = FILETYPE_AIN;
	}
	else if ( !V_stricmp( sourceExtension, "dat" ) )
	{
		if ( V_stristr( pSourceName, "closecaption_" ) )
		{
			// only want closecaption dat files, ignore all others
			fileType = FILETYPE_CCDAT;
		}
	}
	else if ( !V_stricmp( sourceExtension, "vtx" ) )
	{
		if ( V_stristr( pSourceName, ".dx90" ) )
		{
			// only want dx90 version, ignore all others
			fileType = FILETYPE_VTX;
		}
	}
	else if ( !V_stricmp( sourceExtension, "mp3" ) )
	{
		// mp3's are already pre-converted into .360.wav
		// slam the expected name here to simplify the external logic which will do the right thing
		V_StripExtension( pSourceName, pTargetName, targetNameSize );
		V_strcat( pTargetName, ".360.wav", targetNameSize );
		return FILETYPE_MP3;
	}
	else if ( !V_stricmp( sourceExtension, "lst" ) )
	{
		if ( V_stristr( szFullSourcePath, "reslists_xbox\\" ) )
		{
			// only want reslists map versions, due to special processing, ignore all others
			fileType = FILETYPE_RESLST;
		}
	}
	else if ( !V_stricmp( sourceExtension, "pcf" ) )
	{
		fileType = FILETYPE_PCF;
	}
	else if ( !V_stricmp( sourceExtension, "image" ) )
	{
		if ( V_stristr( szFullSourcePath, "scenes\\" ) )
		{
			// only want scene image, ignore all others
			fileType = FILETYPE_SCENEIMAGE;
		}
	}

	if ( fileType != FILETYPE_UNKNOWN && !V_stristr( pSourceName, ".360." ) )
	{
		char targetExtension[MAX_PATH];
		sprintf( targetExtension, ".360.%s", sourceExtension );

		V_StripExtension( pSourceName, pTargetName, targetNameSize );
		V_strcat( pTargetName, targetExtension, targetNameSize );
	}
	else
	{
		// unknown or already converted, target is same as input
		V_strncpy( pTargetName, pSourceName, targetNameSize );
	}

	return fileType;
}

//-----------------------------------------------------------------------------
// Purpose: Returns TRUE if file is a known localized file.
//-----------------------------------------------------------------------------
bool IsLocalizedFile( const char *pFileName )
{
	for ( int i = 0; i<ARRAYSIZE( g_pLanguageSuffixes ); i++ )
	{
		if ( V_stristr( pFileName, g_pLanguageSuffixes[i] ) )
		{
			// a localized file
			return true;
		}
	}

	// not a known localized file
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Returns TRUE if file is a supported localization.
//-----------------------------------------------------------------------------
bool IsLocalizedFileValid( const char *pFileName, const char *pLanguageSuffix )
{
	// file is a localized version
	if ( pLanguageSuffix )
	{
		if ( V_stristr( pFileName, pLanguageSuffix ) )
		{
			// allow it
			return true;
		}

		return false;
	}

	// must match the target supported languages
	for ( int i = 0; i < ARRAYSIZE( g_pTargetLanguageSuffixes ); i++  )
	{
		if ( V_stristr( pFileName, g_pTargetLanguageSuffixes[i] ) )
		{
			// allow it
			return true;
		}
	}
	
	// does not match a target language, not allowed
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Check against a list of allowed filetypes for inclusion in the zip
//-----------------------------------------------------------------------------
bool IncludeInZip( const char *pSourceName )
{
	if ( g_bIsPlatformZip )
	{
		// only allow known valid platform directories
		if ( !V_stristr( pSourceName, "materials\\" ) &&
			 !V_stristr( pSourceName, "resource\\" ) &&
			 !V_stristr( pSourceName, "vgui\\" ) )
		{
			// exclude it
			return false;
		}
	}

	for ( int i = 0; i < ARRAYSIZE( s_AllowedExtensionsInZip ); ++i )
	{
		const char *pAllowedExtension = s_AllowedExtensionsInZip[i];
		if ( !V_stristr( pSourceName, pAllowedExtension ) )
		{
			continue;
		}
	
		if ( !V_stricmp( pAllowedExtension, ".lst" ) )
		{
			// only want ???_exclude.lst files
			if  ( V_stristr( pSourceName, "_exclude.lst" ) )
			{
				return true;
			}
			return false;
		}

		if ( !V_stricmp( pAllowedExtension, ".txt" ) || !V_stricmp( pAllowedExtension, ".360.dat" ) )
		{
			if ( IsLocalizedFile( pSourceName ) && !IsLocalizedFileValid( pSourceName ) )
			{
				// exclude unsupported languages
				return false;
			}

			if ( !V_stricmp( pAllowedExtension, ".txt" ) && V_stristr( pSourceName, "closecaption_" ) )
			{
				// exclude all the closecaption_<language>.txt files
				return false;
			}
		}

		return true;
	}

	// exclude it
	return false;
}

//-----------------------------------------------------------------------------
// Returns true if map is in list, otherwise false
//-----------------------------------------------------------------------------
bool IsMapNameInList( const char *pMapName, CUtlVector< CUtlString > &mapList )
{
	char szBaseName[MAX_PATH];

	V_FileBase( pMapName, szBaseName, sizeof( szBaseName ) );
	V_strlower( szBaseName );

	if ( mapList.Find( szBaseName ) != mapList.InvalidIndex() )
	{
		// found
		return true;
	}

	// not found
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Get the list of valid BSPs
//-----------------------------------------------------------------------------
void BuildValidMapList( CUtlVector< CUtlString > &mapList )
{
	char szFilename[MAX_PATH];
	V_ComposeFileName( g_szModPath, "maplist.txt", szFilename, sizeof( szFilename ) );

	CUtlBuffer buffer;
	if ( !ReadFileToBuffer( szFilename, buffer, true, true ) )
	{
		return;
	}

	characterset_t breakSet;
	CharacterSetBuild( &breakSet, "" );

	char szToken[MAX_PATH];
	char szMapName[MAX_PATH];
	for ( ;; )
	{
		int nTokenSize = buffer.ParseToken( &breakSet, szToken, sizeof( szToken ) );
		if ( nTokenSize <= 0 )
		{
			break;
		}

		// reslists are pc built, filenames can be sloppy
		V_FileBase( szToken, szMapName, sizeof( szMapName ) );
		V_strlower( szMapName );

		mapList.AddToTail( szMapName );
	}
}

#define DO_UPDATE	0x01
#define DO_ZIP		0x02

//-----------------------------------------------------------------------------
// Purpose: drive the file creation
//-----------------------------------------------------------------------------
void GenerateTargetFiles( CUtlVector<fileList_t> &fileList )
{
	char			sourcePath[MAX_PATH];
	char			sourceFile[MAX_PATH];
	char			targetFile[MAX_PATH];
	struct _stat	sourceStatBuf;
	struct _stat	targetStatBuf;

	strcpy( sourcePath, g_szSourcePath );
	V_StripFilename( sourcePath );
	if ( !sourcePath[0] )
		strcpy( sourcePath, "." );
	V_AppendSlash( sourcePath, sizeof( sourcePath ) );

	// default is to update and zip
	CUtlVector< int > updateList;
	updateList.AddMultipleToTail( fileList.Count() );
	for ( int i=0; i<fileList.Count(); i++ )
	{	
		updateList[i] = DO_UPDATE|DO_ZIP;
	}
	int numMatches = 0;

	// build update list
	for ( int i=0; i<fileList.Count(); i++ )
	{
		if ( fileList[i].fileName.IsEmpty() )
		{
			// ignore entries that have been culled
			updateList[i] = 0;
			continue;
		}

		const char *ptr = fileList[i].fileName.String();
		if ( !strnicmp( ptr, ".\\", 2 ) )
			ptr += 2;
		else if ( !strnicmp( ptr, sourcePath, strlen( sourcePath ) ) )
			ptr += strlen( sourcePath );

		strcpy( sourceFile, sourcePath );
		strcat( sourceFile, ptr );
		strcpy( targetFile, g_targetPath );
		strcat( targetFile, ptr );

		fileType_e fileType = ResolveFileType( sourceFile, targetFile, sizeof( targetFile ) );

		// check the target name for inclusion due to extension modifications
		if ( !IncludeInZip( targetFile ) )
		{
			// exclude from zip
			updateList[i] &= ~DO_ZIP;
		}

		if ( fileType == FILETYPE_UNKNOWN )
		{
			// No conversion function, can't do anything
			updateList[i] &= ~DO_UPDATE;
			continue;
		}
		else
		{
			// known filetype, which has a conversion
			// the wildcard match may catch existing converted files, which need to be rejected
			// cull exisiting conversions from the work list 
			// the non-converted filename is part of the same wildcard match, gets caught and resolved
			// the non-converted filename will then go through the proper conversion path
			if ( V_stristr( sourceFile, ".360." ) )
			{
				// cull completely
				updateList[i] = 0;
				continue;
			}
		}

		if ( fileType == FILETYPE_BSP || fileType == FILETYPE_AIN || fileType == FILETYPE_RESLST )
		{
			if ( g_ValidMapList.Count() && !IsMapNameInList( sourceFile, g_ValidMapList ) )
			{
				// cull completely
				updateList[i] = 0;
				continue;
			}
		}

		int retVal = _stat( sourceFile, &sourceStatBuf );
		if ( retVal != 0 )
		{
			// couldn't get source, skip update or zip
			updateList[i] = 0;
			continue;
		}

		retVal = _stat( targetFile, &targetStatBuf );
		if ( retVal != 0 )
		{
			// target doesn't exit, update is required
			continue;
		}

		// track valid candidates
		numMatches++;

		if ( fileType == FILETYPE_MDL || fileType == FILETYPE_ANI || fileType == FILETYPE_VTX || fileType == FILETYPE_VVD || fileType == FILETYPE_PHY )
		{
			// models are converted in a pre-pass
			// let the update logic run and catch the conversions for zipping
			continue;
		}

		if ( !g_bForce )
		{
			if ( difftime( sourceStatBuf.st_mtime, targetStatBuf.st_mtime ) <= 0 )
			{
				// target is same or older, no update
				updateList[i] &= ~DO_UPDATE;
			}
		}
	}
	
	// cleanse and determine totals, makes succeeding logic simpler
	int numWorkItems = 0;
	int numFilesToUpdate = 0;
	int numFilesToZip = 0;
	for ( int i=0; i<fileList.Count(); i++ )
	{
		if ( updateList[i] & DO_UPDATE )
		{
			numFilesToUpdate++;
		}
		if ( g_bMakeZip && ( updateList[i] & DO_ZIP ) )
		{
			numFilesToZip++;
		}
		else
		{
			updateList[i] &= ~DO_ZIP;
		}
		if ( updateList[i] )
		{
			numWorkItems++;
		}
	}

	Msg( "\n" );
	Msg( "Matched %d/%d files.\n", numMatches, fileList.Count() );
	Msg( "Creating or Updating %d files.\n", numFilesToUpdate );
	if ( g_bMakeZip )
	{
		Msg( "Zipping %d files.\n", numFilesToZip );
	}

	InitConversionModules();

	if ( numFilesToZip && !g_bTest )
	{
		Msg( "Creating Zip: %s\n", g_zipPath );
		if ( !g_MasterXZip.Begin( g_zipPath, XBOX_DVD_SECTORSIZE ) )
		{
			Msg( "ERROR: Failed to open \"%s\" for writing.\n", g_zipPath );
			return;
		}
		else
		{
			SetupCriticalPreloadScript( g_szModPath );
		}
	}

	// iterate work list
	int progress = 0;
	for ( int i=0; i<fileList.Count(); i++ )
	{
		if ( !updateList[i] )
		{
			// no update or zip needed, skip
			continue;
		}
	
		const char *ptr = fileList[i].fileName.String();
		if ( !strnicmp( ptr, ".\\", 2 ) )
			ptr += 2;
		else if ( !strnicmp( ptr, sourcePath, strlen( sourcePath ) ) )
			ptr += strlen( sourcePath );

		strcpy( sourceFile, sourcePath );
		strcat( sourceFile, ptr );
		strcpy( targetFile, g_targetPath );
		strcat( targetFile, ptr );

		fileType_e fileType = ResolveFileType( sourceFile, targetFile, sizeof( targetFile ) );

		if ( !g_bQuiet )
		{
			Msg( "%d/%d:%s -> %s\n", progress+1, numWorkItems, sourceFile, targetFile );
		}

		bool bSuccess = true;
		if ( updateList[i] & DO_UPDATE )
		{
			// generate target file (and optionally zip output)
			bSuccess = CreateTargetFile( sourceFile, targetFile, fileType, (updateList[i] & DO_ZIP) != 0 );
			if ( !bSuccess )
			{
				// add to error list
				int error = g_errorList.AddToTail();
				g_errorList[error].result = false;
				g_errorList[error].fileName.Set( sourceFile );
			}
		}
		else if ( updateList[i] & DO_ZIP )
		{
			// existing target file is zipped
			CUtlBuffer targetBuffer;
			bSuccess = scriptlib->ReadFileToBuffer( targetFile, targetBuffer );
			if ( bSuccess )
			{
				if ( !g_bTest )
				{
					bSuccess = g_MasterXZip.AddBuffer( targetFile, targetBuffer, true );
				}
			}
			if ( !bSuccess )
			{
				// add to error list
				int error = g_errorList.AddToTail();
				g_errorList[error].result = false;
				g_errorList[error].fileName.Set( targetFile );
			}
		}

		progress++;
	}

	DoPostProcessingFunctions( !g_bTest );

	ShutdownConversionModules();

	if ( numFilesToZip && !g_bTest )
	{
		g_MasterXZip.End();
	}

	// iterate error list
	Msg( "\n" );
	for ( int i = 0; i < g_errorList.Count(); i++ )
	{
		Msg( "ERROR: could not process %s\n", g_errorList[i].fileName.String() );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Spew Usage
//-----------------------------------------------------------------------------
void Usage()
{
	Msg( "usage: MakeGameData [filemask] [options]\n" );
	Msg( "options:\n" );
	Msg( "[-v]                    Version\n" );
	Msg( "[-q]                    Quiet (critical spew only)\n" );
	Msg( "[-h] [-help] [-?]       Help\n" );
	Msg( "[-t targetPath]         Alternate output path, will generate output at target\n" );
	Msg( "[-r] [-recurse]         Recurse into source directory\n" );
	Msg( "[-f] [-force]           Force update, otherwise checks timestamps\n" );
	Msg( "[-test]                 Skip writing to disk\n" );
	Msg( "[-z <zipname>]          Generate zip file AND create or update stale conversions\n" );
	Msg( "[-zo <zipname>]         Generate zip file ONLY (existing stale conversions get updated, no new conversions are written)\n" );
	Msg( "[-preloadinfo]          Spew contents of preload section in zip\n" );
	Msg( "[-xmaquality <quality>] XMA Encoding quality override, [0-100]\n" );
	Msg( "[-scenes]               Make 360 scene image cache.\n" );
	Msg( "[-pcscenes]             Make PC scene image cache.\n" );
    Msg( "[-usemaplist]           For BSP related conversions, restricts to maplist.txt.\n" );

	exit( 1 );
}

//-----------------------------------------------------------------------------
// Purpose: default output func
//-----------------------------------------------------------------------------
SpewRetval_t OutputFunc( SpewType_t spewType, char const *pMsg )
{
	printf( pMsg );

	if ( spewType == SPEW_ERROR )
	{
		return SPEW_ABORT;
	}
	return ( spewType == SPEW_ASSERT ) ? SPEW_DEBUGGER : SPEW_CONTINUE; 
}

//-----------------------------------------------------------------------------
// The application object
//-----------------------------------------------------------------------------
bool MakeGameDataApp::Create()
{
	SpewOutputFunc( OutputFunc );

	AppSystemInfo_t appSystems[] = 
	{
		{ "mdllib.dll",	MDLLIB_INTERFACE_VERSION },
		{ "", "" }	// Required to terminate the list
	};

	AddSystem( g_pDataModel, VDATAMODEL_INTERFACE_VERSION );
	AddSystem( g_pDmSerializers, DMSERIALIZERS_INTERFACE_VERSION );

	// Load vphysics.dll
	if ( !Sys_LoadInterface( "vphysics.dll", VPHYSICS_COLLISION_INTERFACE_VERSION, &g_pPhysicsModule, (void**)&g_pPhysicsCollision ) )
	{
		Msg( "Failed to load vphysics interface\n" );
		return false;
	}

	bool bOk = AddSystems( appSystems );
	if ( !bOk )
		return false;

	return true;
}

bool MakeGameDataApp::PreInit()
{
	CreateInterfaceFn factory = GetFactory();

	ConnectTier1Libraries( &factory, 1 );
	ConnectTier2Libraries( &factory, 1 );

	if ( !g_pFullFileSystem || !g_pDataModel || !g_pPhysicsCollision || !mdllib )
	{
		Warning( "MakeGameData is missing a required interface!\n" );
		return false;
	}

	return true;
}

void MakeGameDataApp::PostShutdown()
{
	if ( g_pPhysicsModule )
	{
		Sys_UnloadModule( g_pPhysicsModule );
		g_pPhysicsModule = NULL;
		g_pPhysicsCollision = NULL;
	}
	
	DisconnectTier2Libraries();
	DisconnectTier1Libraries();
}

//-----------------------------------------------------------------------------
//	main
//
//-----------------------------------------------------------------------------
int MakeGameDataApp::Main()
{
	int			argnum;

	// set the valve library printer
	SpewOutputFunc( OutputFunc );

	Msg( "\nMAKEGAMEDATA - Valve Xbox 360 Game Data Compiler (Build: %s %s)\n", __DATE__, __TIME__ );
	Msg( "(C) Copyright 1996-2006, Valve Corporation, All rights reserved.\n\n" );

	if ( CommandLine()->FindParm( "-v" ) || CommandLine()->FindParm( "-version" ) )
	{
		// spew just the version, used by batches for logging
		return 0;
	}

#ifndef MAKESCENESIMAGE
	if ( CommandLine()->ParmCount() < 2 || CommandLine()->FindParm( "?" ) || CommandLine()->FindParm( "-h" ) || CommandLine()->FindParm( "-help" ) )
	{
		Usage();
	}
#endif // MAKESCENESIMAGE

	bool bHasFileMask = false;

	const char *pFirstArg = CommandLine()->GetParm( 1 );
	if ( pFirstArg[0] == '-' )
	{
		// first arg is an option, assume *.*
		strcpy( g_szSourcePath, "*.*" );
	}
	else
	{
		strcpy( g_szSourcePath, pFirstArg );
		bHasFileMask = true;
	}

	bool bRecurse = false;
#ifndef MAKESCENESIMAGE
	bRecurse = CommandLine()->FindParm( "-recurse" ) || CommandLine()->FindParm( "-r" );
	g_bForce = CommandLine()->FindParm( "-force" ) || CommandLine()->FindParm( "-f" );
	g_bTest = CommandLine()->FindParm( "-test" ) != 0;
	g_bQuiet = CommandLine()->FindParm( "-quiet" ) || CommandLine()->FindParm( "-q" );
	g_bMakeScenes = CommandLine()->FindParm( "-scenes" ) != 0;
	g_bMakeScenesPC = CommandLine()->FindParm( "-pcscenes" ) != 0;
#else
	g_bMakeScenesPC = true;
#endif // MAKESCENESIMAGE

#ifndef MAKESCENESIMAGE
	g_bUseMapList = CommandLine()->FindParm( "-usemaplist" ) != 0;
#endif // MAKESCENESIMAGE

	// Set up zip file options
	g_WriteModeForConversions = WRITE_TO_DISK_ALWAYS;
	argnum = CommandLine()->FindParm( "-z" );
	if ( argnum )
	{
		strcpy( g_szSourcePath, "*.*" );
		g_bMakeZip = true;
		g_bMakeScenes = true;
		bRecurse = true;
	}
	else
	{
		argnum = CommandLine()->FindParm( "-zo" );
		if ( argnum )
		{
			strcpy( g_szSourcePath, "*.*" );
			g_bMakeZip = true;
			g_bMakeScenes = true;
			g_WriteModeForConversions = WRITE_TO_DISK_UPDATE;
			bRecurse = true;
		}
	}
	if ( g_bMakeZip )
	{
		strcat( g_zipPath, CommandLine()->GetParm( argnum + 1 ) );
	}

	// default target path is source
	strcpy( g_targetPath, g_szSourcePath );
	V_StripFilename( g_targetPath );
	if ( !g_targetPath[0] )
	{
		strcpy( g_targetPath, "." );
	}

	// override via command line
	argnum = CommandLine()->FindParm( "-t" );
	if ( argnum )
	{
		V_strcpy_safe( g_targetPath, CommandLine()->GetParm( argnum + 1 ) );
	}
	V_AppendSlash( g_targetPath, sizeof( g_targetPath ) );

	if ( CommandLine()->FindParm( "-preloadinfo" ) )
	{
		g_MasterXZip.SpewPreloadInfo( g_szSourcePath );
		return 0;
	}

#ifndef MAKESCENESIMAGE
	GetGamePath();
#endif // MAKESCENESIMAGE

	g_bModPathIsValid = GetModPath();
	if ( !SetupFileSystem() )
	{
		Msg( "ERROR: Failed to setup file system.\n" );
		exit( 1 );
	}

	// data model initialization
	g_pDataModel->SetUndoEnabled( false );
	g_pDataModel->OnlyCreateUntypedElements( true );
	g_pDataModel->SetDefaultElementFactory( NULL );

	g_bIsPlatformZip = g_bMakeZip && ( V_stristr( g_szModPath, "\\platform" ) != NULL );

	// cleanup any zombie temp files left from a possible prior abort or error 
	scriptlib->DeleteTemporaryFiles( "mgd_*.tmp" );

	if ( g_bMakeZip || g_bUseMapList )
	{
		// zips use the map list to narrow the bsp conversion to actual shipping maps
		BuildValidMapList( g_ValidMapList );
	}

	CUtlVector<fileList_t> fileList;
	if ( bHasFileMask || g_bMakeZip )
	{
		scriptlib->FindFiles( g_szSourcePath, bRecurse, fileList );
	}

	// model conversions require seperate pre-processing to achieve grouping
	if ( !PreprocessModelFiles( fileList ) )
	{
		return 0;
	}

	GenerateTargetFiles( fileList );

	return 0;
}