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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#ifdef _WIN32
#include <process.h>
#include <io.h>
#endif
#include <stddef.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "tier1/utlbuffer.h"
#include "tier1/strtools.h"
#include "tier2/riff.h"

#if defined( _WIN32 ) && !defined( _X360 )
#include <windows.h>
#endif

#ifdef MAKE_GAMEDATA_TOOL
	#include "../public/materialsystem/shader_vcs_version.h"
	#include "../public/materialsystem/imaterial.h"
	#include "../public/materialsystem/hardwareverts.h"
	#include "../public/vtf/vtf.h"
#else
	#include "materialsystem/shader_vcs_version.h"
	#include "materialsystem/imaterial.h"
	#include "materialsystem/hardwareverts.h"
#endif

#include "xwvfile.h"
#include "xzp.h"

CByteswap g_xzpSwap;
extern IFileReadBinary *g_pSndIO;

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// Datadesc blocks for byteswapping:
//-----------------------------------------------------------------------------
BEGIN_BYTESWAP_DATADESC( xZipHeader_t )
	DEFINE_FIELD( Magic, FIELD_INTEGER ),
	DEFINE_FIELD( Version, FIELD_INTEGER ),
	DEFINE_FIELD( PreloadDirectoryEntries, FIELD_INTEGER ),
	DEFINE_FIELD( DirectoryEntries, FIELD_INTEGER ),
	DEFINE_FIELD( PreloadBytes, FIELD_INTEGER ),
	DEFINE_FIELD( HeaderLength, FIELD_INTEGER ),
	DEFINE_FIELD( FilenameEntries, FIELD_INTEGER ),
	DEFINE_FIELD( FilenameStringsOffset, FIELD_INTEGER ),
	DEFINE_FIELD( FilenameStringsLength, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()

BEGIN_BYTESWAP_DATADESC( xZipDirectoryEntry_t )
	DEFINE_FIELD( FilenameCRC, FIELD_INTEGER ),
	DEFINE_FIELD( Length, FIELD_INTEGER ),
	DEFINE_FIELD( StoredOffset, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()

BEGIN_BYTESWAP_DATADESC( xZipFilenameEntry_t )
	DEFINE_FIELD( FilenameCRC, FIELD_INTEGER ),
	DEFINE_FIELD( FilenameOffset, FIELD_INTEGER ),
	DEFINE_FIELD( TimeStamp, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()

BEGIN_BYTESWAP_DATADESC( xZipFooter_t )
	DEFINE_FIELD( Size, FIELD_INTEGER ),
	DEFINE_FIELD( Magic, FIELD_INTEGER ),
END_BYTESWAP_DATADESC()

CXZip::CXZip()
{
	// Ensure that the header doesn't contain a valid magic yet.
	m_Header.Magic = 0;	
	m_pPreloadedData = NULL;
	m_nPreloadStart = 0;
	m_pDirectory = NULL;
	m_pPreloadDirectory = NULL;
	m_nRegular2PreloadEntryMapping = NULL;

	m_bByteSwapped = false;

	m_pFilenames = NULL;
	m_hZip = NULL;
	
	m_pRead = NULL;
	m_hUser = 0;
	m_nMonitorLevel = 0;
}

CXZip::CXZip( const char* filename )
{
	// Ensure that the header doesn't contain a valid magic yet.
	m_Header.Magic = 0;	
	m_nPreloadStart = 0;
	m_pPreloadedData = NULL;
	m_pDirectory = NULL;
	m_pPreloadDirectory = NULL;
	m_nRegular2PreloadEntryMapping = NULL;

	m_bByteSwapped = false;

	m_pFilenames = NULL;
	m_hZip = NULL;

	m_pRead = NULL;
	m_hUser = 0;
	m_nMonitorLevel = 0;

	Load( filename );
}

CXZip::CXZip( FILE* handle, int offset, int size )	// file handle and offset of the zip file
{
	m_pRead = NULL;
	m_hUser = 0;
	m_nPreloadStart = 0;
	m_pDirectory = NULL;
	m_pPreloadDirectory = NULL;
	m_nRegular2PreloadEntryMapping = NULL;

	m_bByteSwapped = false;

	m_pFilenames = NULL;
	m_pPreloadedData = NULL;
	m_nMonitorLevel = 0;

	Load( handle, offset, size );
}

CXZip::~CXZip()
{
	Unload();
}

bool CXZip::InstallAlternateIO( int (*read)( void* buffer, int offset, int length, int nDestLength, int hUser), int hUser )
{
	m_pRead = read;
	m_hUser = hUser;
	return true;
}


// Loads an xZip file into memory:
bool CXZip::Load( const char* filename, bool bPreload )
{
	FILE* hZip = fopen( filename, "rb" );
	fseek(hZip,0,SEEK_END);
	int nSize = ftell( hZip );
	return Load( hZip, 0, nSize, bPreload );
}

bool CXZip::Load( FILE* handle, int nOffset, int nSize, bool bPreload )	// Load a pack file into this instance.  Returns true on success.
{
	Unload();

	m_bByteSwapped = false;

	m_hZip = handle;
	m_nOffset = nOffset;
	m_nSize = nSize;

	// Hacky, clean up:
	if( m_hZip && !m_pRead )
	{
		InstallAlternateIO( defaultRead, (int)m_hZip );
	}

	if( m_hZip == NULL && m_pRead == NULL )
	{
		return false;
	}

	// Read the header:
	m_pRead( &m_Header, 0, -1, sizeof(m_Header), m_hUser );

	// Validate the Magic number and at the same time determine if I am reading a regular or swappped xZip file:
	switch( m_Swap.SourceIsNativeEndian<int>( m_Header.Magic, xZipHeader_t::MAGIC ) )
	{
		// Does the magic match exactly?
		case 1:
			m_Swap.ActivateByteSwapping( false );
			m_bByteSwapped = false;
			break;

		// Does the magic match, but is swapped?
		case 0:
			m_bByteSwapped = true;
			m_Swap.ActivateByteSwapping( true );	// We must be reading the opposite endianness.
			m_Swap.SwapFieldsToTargetEndian<xZipHeader_t>( &m_Header );
			break;

		default: 
			assert( 0 );
			// Fail gently in release:
		
		// The magic doesn't match in any respect:
		case -1:
			{
				printf("Invalid xZip file\n");

				if( m_hZip )
				{
					fclose( m_hZip );
					m_hZip = NULL;
				}
				return false;
			}
	}

	// Validate the archive version:
	if( m_Header.Version != xZipHeader_t::VERSION )
	{
		// Backward compatable support for version 1
		Msg("Incorrect xZip version found %u - expected %u\n", m_Header.Version, xZipHeader_t::VERSION );
		if( m_hZip )
		{
			fclose( m_hZip );
			m_hZip = NULL;
		}

		m_Header.Magic = xZipHeader_t::FREE;
		return false;
	}

	// Read the directory:
	{
		MEM_ALLOC_CREDIT();

		m_pDirectory = (xZipDirectoryEntry_t*)malloc( sizeof(xZipDirectoryEntry_t) * m_Header.DirectoryEntries );
		m_pRead( m_pDirectory, m_Header.HeaderLength, -1, sizeof( xZipDirectoryEntry_t ) * m_Header.DirectoryEntries, m_hUser );

		// Swap the directory entries if nessecary
		if( m_bByteSwapped )
		{
			for( unsigned nDirectoryEntry = 0; nDirectoryEntry < m_Header.DirectoryEntries; nDirectoryEntry++ )
			{
				m_Swap.SwapFieldsToTargetEndian<xZipDirectoryEntry_t>(  &( m_pDirectory[nDirectoryEntry] ) );
			}
		}


		m_nPreloadStart = m_Header.HeaderLength + ( sizeof( xZipDirectoryEntry_t ) * m_Header.DirectoryEntries );
	}

	// Preload the preload chunk if desired:
	if( bPreload )
	{
		PreloadData();
	}

	return true;
}

void CXZip::Unload()
{
	DiscardPreloadedData();

	// Dump the directory:
	if( m_pDirectory )
	{
		free( m_pDirectory );
		m_pDirectory = NULL;
	}

	if( m_pFilenames )
	{
		free( m_pFilenames );
		m_pFilenames = NULL;
	}

	// Invalidate the header:
	m_Header.Magic = 0;

	if( m_hZip )
	{
		fclose( m_hZip );
		m_hZip = NULL;
	}

}

//-----------------------------------------------------------------------------
// CXZip::PreloadData
//
// Loads the preloaded data if it isn't already.
//-----------------------------------------------------------------------------

void CXZip::PreloadData()
{
	Assert( IsValid() );

	// Ensure it isn't already preloaded
	if( m_pPreloadedData )
		return;

	// If I don't have a preloaded section, ignore the request.
	if( !m_Header.PreloadBytes || !m_Header.PreloadDirectoryEntries )
		return;

	// Allocate and read the data block in:
#ifndef _X360
	MEM_ALLOC_CREDIT_( "xZip" );
	m_pPreloadedData = malloc( m_Header.PreloadBytes );

	// Just drop out if allocation fails;
	if ( !m_pPreloadedData )
		return;

	m_pRead( m_pPreloadedData, m_nPreloadStart, -1, m_Header.PreloadBytes, m_hUser );
#else
	int nAlignedStart = AlignValue( ( m_nPreloadStart - XBOX_HDD_SECTORSIZE ) + 1, XBOX_HDD_SECTORSIZE );
	int nBytesToRead = AlignValue( ( m_nPreloadStart - nAlignedStart ) + m_Header.PreloadBytes, XBOX_HDD_SECTORSIZE );
	int nBytesBuffer = AlignValue( nBytesToRead, XBOX_HDD_SECTORSIZE );
	byte *pReadData = (byte *)malloc( nBytesBuffer );
	
	// Just drop out if allocation fails;
	if ( !pReadData )
		return; 
	
	MEM_ALLOC_CREDIT_( "xZip" );
	m_pRead( pReadData, nAlignedStart, nBytesBuffer,nBytesToRead, m_hUser );
	m_pPreloadedData = pReadData + ( m_nPreloadStart - nAlignedStart );
#endif

	// Set up the preload directory:
	m_pPreloadDirectory = (xZipDirectoryEntry_t*)m_pPreloadedData;

	// Swap the preload directory:
	if ( m_bByteSwapped )
	{
		for ( unsigned nDirectoryEntry = 0; nDirectoryEntry < m_Header.PreloadDirectoryEntries; nDirectoryEntry++ )
		{
			m_Swap.SwapFieldsToTargetEndian<xZipDirectoryEntry_t>( &( m_pPreloadDirectory[nDirectoryEntry] ) );
		}
	}

	// Set up the regular 2 preload mapping section:
	m_nRegular2PreloadEntryMapping = (unsigned short*)(((unsigned char*)m_pPreloadDirectory) + ( sizeof(xZipDirectoryEntry_t) * m_Header.PreloadDirectoryEntries ));

	// Swap the regular to preload mapping
	if ( m_bByteSwapped )
	{
		m_Swap.SwapBufferToTargetEndian<short>( (short *)m_nRegular2PreloadEntryMapping,  (short *)m_nRegular2PreloadEntryMapping, m_Header.DirectoryEntries );
	}

}

//-----------------------------------------------------------------------------
// CXZip::DiscardPreloadedData
//
// frees the preloaded data cache if it's present.
//-----------------------------------------------------------------------------

void CXZip::DiscardPreloadedData()
{
	if ( m_pPreloadedData )
	{
#ifndef _X360
		free( m_pPreloadedData );
#else
		int nAlignedStart = AlignValue( ( m_nPreloadStart - XBOX_HDD_SECTORSIZE ) + 1, XBOX_HDD_SECTORSIZE );
		byte *pReadData = (byte *)m_pPreloadedData - ( m_nPreloadStart - nAlignedStart );
		free( pReadData );
#endif
		m_pPreloadedData = NULL;
		m_pPreloadDirectory = NULL;
		m_nRegular2PreloadEntryMapping = NULL;
	}
}

int CXZip::defaultRead( void* buffer, int offset, int destLength, int length, int hUser)
{
	fseek( (FILE*)hUser, offset, SEEK_SET );
	return fread( buffer, 1, length, (FILE*)hUser );
}

char* CXZip::GetEntryFileName( unsigned CRC, char* pDefault )
{
	Assert( IsValid() );

	if( IsRetail() )
	{
		return pDefault;
	}
	else
	{

		// Make sure I have a filename section:
		if( m_Header.FilenameStringsOffset == 0 || m_Header.FilenameEntries == 0 || CRC == 0 )
		{
			return pDefault;
		}

		// If the filename chunk isn't here, load it up:
		if( !m_pFilenames )
		{
			MEM_ALLOC_CREDIT_("xZip");
			m_pFilenames = (xZipFilenameEntry_t*)malloc( m_Header.FilenameStringsLength );
			m_pRead( m_pFilenames, m_Header.FilenameStringsOffset, -1, m_Header.FilenameStringsLength, m_hUser );

			// TODO: Swap!
			for( unsigned int i=0; i< m_Header.FilenameEntries;i++ )
			{
				m_Swap.SwapFieldsToTargetEndian<xZipFilenameEntry_t>(&m_pFilenames[i]);
			}
		}

		// Find this entry in the preload directory
		xZipFilenameEntry_t entry;
		entry.FilenameCRC = CRC;

		xZipFilenameEntry_t* found = (xZipFilenameEntry_t*)bsearch( &entry, m_pFilenames, m_Header.FilenameEntries, sizeof(xZipFilenameEntry_t), xZipFilenameEntry_t::xZipFilenameEntryCompare );

		if( !found ) 
			return pDefault;

		return (((char*)m_pFilenames) + found->FilenameOffset) - m_Header.FilenameStringsOffset; 	
	}
}

// Sanity checks that the zip file is ready and readable:
bool CXZip::IsValid()
{
	if( m_Header.Magic != xZipHeader_t::MAGIC )
		return false;

	if( m_Header.Version > xZipHeader_t::VERSION )
		return false;

	if( !m_pDirectory )
		return false;

	return true;
}

void CXZip::WarningDir()
{
	Assert( IsValid());
	
	for( unsigned i = 0; i< m_Header.DirectoryEntries; i++ )
	{
		Msg( GetEntryFileName( m_pDirectory[i].FilenameCRC ) );
	}
}


int CXZip::ReadIndex( int nEntryIndex, int nFileOffset, int nDestBytes, int nLength, void* pBuffer )
{
	Assert( IsValid() );

	if( nLength <=0 || nEntryIndex < 0 ) 
		return 0;

	// HACK HACK HACK - convert the pack file index to a local file index (ie, assuming the full file index is being passed in)
	nFileOffset -= m_pDirectory[nEntryIndex].StoredOffset;
	// HACK HACK HACK

	// If I've got my preload section loaded, first check there:
	xZipDirectoryEntry_t* pPreloadEntry = GetPreloadEntry(nEntryIndex);

	if( pPreloadEntry )
	{
		Assert( pPreloadEntry->FilenameCRC == m_pDirectory[nEntryIndex].FilenameCRC );

		if( nFileOffset + nLength <= (int)pPreloadEntry->Length )
		{
			if( m_nMonitorLevel >= 2 )
			{
				char* filename = GetEntryFileName( m_pDirectory[nEntryIndex].FilenameCRC, "(!!! unknown !!!)" );

				Msg("PACK(preload) %s: length:%i offset:%i",filename,nLength, nFileOffset);

			}

			memcpy( pBuffer, (char*)m_pPreloadedData + pPreloadEntry->StoredOffset + nFileOffset - m_nPreloadStart, nLength );
			return nLength;
		}
	}

	// Offset int the zip to start the read:
	int ZipOffset = m_pDirectory[nEntryIndex].StoredOffset + nFileOffset;
	int nBytesRead = m_pRead( pBuffer, ZipOffset, nDestBytes, nLength, m_hUser);

	if( m_nMonitorLevel )
	{
		char* filename = GetEntryFileName( m_pDirectory[nEntryIndex].FilenameCRC, "(!!! unknown !!!)" );

		unsigned preload = 0;
		if( m_pPreloadedData && m_nRegular2PreloadEntryMapping[nEntryIndex] != 0xFFFF )
		{
			// Find this entry in the preload directory
			xZipDirectoryEntry_t* entry = &(m_pPreloadDirectory[m_nRegular2PreloadEntryMapping[nEntryIndex]]);
			Assert(entry->FilenameCRC == m_pDirectory[nEntryIndex].FilenameCRC);

			preload = entry->Length;
		}

		Msg("PACK %s: length:%i offset:%i (preload bytes:%i)",filename,nLength, nFileOffset, preload);
	}
	
	return nBytesRead;
}

bool CXZip::GetSimpleFileOffsetLength( const char* FileName, int& nBaseIndex, int &nFileOffset, int &nLength )
{
	Assert( IsValid() );

	xZipDirectoryEntry_t entry;
	entry.FilenameCRC = xZipCRCFilename( FileName );

	xZipDirectoryEntry_t* found = (xZipDirectoryEntry_t*)bsearch( &entry, m_pDirectory, m_Header.DirectoryEntries, sizeof(xZipDirectoryEntry_t), xZipDirectoryEntry_t::xZipDirectoryEntryFindCompare );

	if( found == NULL )
		return false;

	nFileOffset = found[0].StoredOffset;
	nLength = found[0].Length;
	nBaseIndex = (((int)((char*)found - (char*)m_pDirectory))/sizeof(xZipDirectoryEntry_t));

	return true;
}

bool CXZip::ExtractFile( const char* FileName )
{
	return false;
}

// Compares to xZipDirectoryEntries.
//
// Sorts in the following order:  
//		FilenameCRC
//		FileOffset
//		Length
//		StoredOffset
//
// The sort function may look overly complex, but it is actually useful for locating different pieces of 
// the same file in a meaningful order.
//
int __cdecl xZipDirectoryEntry_t::xZipDirectoryEntrySortCompare( const void* left, const void* right )
{
	xZipDirectoryEntry_t *l = (xZipDirectoryEntry_t*)left,
					     *r = (xZipDirectoryEntry_t*)right;

	if( l->FilenameCRC < r->FilenameCRC )
	{
		return -1;
	}

	else if( l->FilenameCRC > r->FilenameCRC )
	{
		return 1;
	}

	// else l->FileOffset == r->FileOffset
	if( l->Length < r->Length )
	{
		return -1;
	}
	else if( l->Length > r->Length )
	{
		return 1;
	}

	// else l->Length == r->Length
	if( l->StoredOffset < r->StoredOffset )
	{
		return -1;
	}
	else if( l->StoredOffset > r->StoredOffset )
	{
		return 1;
	}

	// else everything is identical:
	return 0;

}

// Find an entry with matching CRC only
int __cdecl xZipDirectoryEntry_t::xZipDirectoryEntryFindCompare( const void* left, const void* right )
{
	xZipDirectoryEntry_t *l = (xZipDirectoryEntry_t*)left,
					     *r = (xZipDirectoryEntry_t*)right;

	if( l->FilenameCRC < r->FilenameCRC )
	{
		return -1;
	}

	else if( l->FilenameCRC > r->FilenameCRC )
	{
		return 1;
	}

	return 0;

}

int __cdecl xZipFilenameEntry_t::xZipFilenameEntryCompare( const void* left, const void* right )
{
	xZipFilenameEntry_t *l = (xZipFilenameEntry_t*)left,
					    *r = (xZipFilenameEntry_t*)right;

	if( l->FilenameCRC < r->FilenameCRC )
	{
		return -1;
	}

	else if( l->FilenameCRC > r->FilenameCRC )
	{
		return 1;
	}

	return 0;

}


// CRC's an individual xZip filename:
unsigned xZipCRCFilename( const char* filename )
{
	unsigned hash = 0xAAAAAAAA;	// Alternating 1's and 0's

	for( ; *filename ; filename++ )
	{
		char c = *filename;
	
		// Fix slashes
		if( c == '/' )
			c = '\\';
		else
			c = (char)tolower(c);

		hash = hash * 33 + c;
	}

	return hash;
}

#if defined( MAKE_GAMEDATA_TOOL )

// ------------
xZipHeader_t Header;
xZipDirectoryEntry_t	 *pDirectoryEntries = NULL;
xZipDirectoryEntry_t	 *pPreloadDirectoryEntries = NULL;
xZipFilenameEntry_t		 *pFilenameEntries = NULL;
char					 *pFilenameData = NULL;
unsigned				  nFilenameDataLength = 0;

unsigned			     InputFileBytes = 0;

char* CleanFilename( char* filename )
{
	// Trim leading white space:
	while( isspace(*filename) )
		filename++;

	// Trim trailing white space:
	while( isspace( filename[strlen(filename)-1] ) )
	{
		filename[strlen(filename)-1] = '\0';
	}

	return filename;
}


bool CopyFileBytes( FILE* hDestination, FILE* hSource, unsigned nBytes )
{
	char buffer[16384];

	while( nBytes > 0 )
	{
		int nBytesRead = fread( buffer, 1, nBytes > sizeof(buffer) ? sizeof(buffer) : nBytes,  hSource );
		fwrite(buffer, 1, nBytesRead,  hDestination );
		nBytes -= nBytesRead;
	}

	return true;
}

bool WriteFileBytes( FILE* hDestination, CUtlBuffer &source, unsigned nBytes )
{
	unsigned int nBytesWritten = fwrite(source.Base(), 1, nBytes,  hDestination );
	return (nBytesWritten == nBytes);
}

void PadFileBytes(FILE* hFile, int nPreloadPadding )
{
	if( nPreloadPadding < 0 || nPreloadPadding >= 512)
	{
		puts("Invalid padding");
		return;
	}
	
	char padding[512];
	memset(padding,0,nPreloadPadding);
	fwrite(padding,1,nPreloadPadding,hFile);
}

void AddFilename( const char* filename )
{
	unsigned CRCfilename = xZipCRCFilename( filename );

	// If we already have this filename don't add it again:
	for( int i = 0; i < (int)Header.FilenameEntries; i++ )
	{
		if( pFilenameEntries[i].FilenameCRC == CRCfilename )
		{
			return;
		}
	}

	Header.FilenameEntries++;
	
	// Add the file to the file string table:
	pFilenameEntries = (xZipFilenameEntry_t*)realloc( pFilenameEntries, sizeof(xZipFilenameEntry_t) * Header.FilenameEntries );
	
	int filenameLength = (int)strlen(filename) + 1;
	pFilenameEntries[Header.FilenameEntries-1].FilenameCRC = CRCfilename;
	pFilenameEntries[Header.FilenameEntries-1].FilenameOffset = nFilenameDataLength;

	// Grab the timestamp for the file:
	struct stat buf;
	if( stat( filename, &buf ) != -1 )
	{
		pFilenameEntries[Header.FilenameEntries - 1].TimeStamp = buf.st_mtime;
	}
	else
	{
		pFilenameEntries[Header.FilenameEntries - 1].TimeStamp = 0;
	}

	nFilenameDataLength += filenameLength;
	pFilenameData = (char*)realloc(pFilenameData, nFilenameDataLength);
	memcpy(pFilenameData + nFilenameDataLength - filenameLength, filename, filenameLength);
}

FILE* hTempFilePreload;
FILE* hTempFileData;
FILE* hOutputFile;

bool xZipAddFile( const char* filename, CUtlBuffer &fileBuff, bool bPrecacheEntireFile, bool bProcessPrecacheHeader, bool bProcessPrecacheHeaderOnly )
{
	unsigned int fileSize = fileBuff.TellMaxPut();

	// Track total input bytes for stats reasons
	InputFileBytes += fileSize;

	unsigned customPreloadSize = 0;
	
	if( bPrecacheEntireFile  )
	{
		customPreloadSize = fileSize;
	} 
	else if( bProcessPrecacheHeader )
	{
		customPreloadSize = xZipComputeCustomPreloads( filename );
	}
	else if( bProcessPrecacheHeaderOnly )
	{
		customPreloadSize = xZipComputeCustomPreloads( filename );
		fileSize = min( fileSize, customPreloadSize );
	}

	unsigned CRC = xZipCRCFilename( filename );

	// Does this file have a split header?
	if( customPreloadSize > 0  ) 
	{
		// Initialize the entry header:
		xZipDirectoryEntry_t entry;
		memset( &entry, 0, sizeof( entry ) );
		
		entry.FilenameCRC = CRC;
		entry.Length = customPreloadSize;
		entry.StoredOffset = ftell(hTempFilePreload);

		// Add the directory entry to the preload table:
		Header.PreloadDirectoryEntries++;
		pPreloadDirectoryEntries = (xZipDirectoryEntry_t*)realloc( pPreloadDirectoryEntries, sizeof( xZipDirectoryEntry_t ) * Header.PreloadDirectoryEntries );
		memcpy( pPreloadDirectoryEntries + Header.PreloadDirectoryEntries - 1, &entry, sizeof( entry ) );

		// Concatenate the data in the preload file:
		fileBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
		WriteFileBytes( hTempFilePreload, fileBuff, entry.Length );
		fileBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );

		
		// Add the filename entry:
		AddFilename( filename );

		// Spew it:
		printf("+Preload: \"%s\": Length:%u\n", filename, entry.Length );
	}

	// Copy the file to the regular data region:
	xZipDirectoryEntry_t entry;
	memset(&entry,0,sizeof(entry));
	entry.FilenameCRC = CRC;
	entry.Length = fileSize;
	entry.StoredOffset = ftell(hTempFileData);		

	// Add the directory entry to the table:
	Header.DirectoryEntries++;
	pDirectoryEntries = (xZipDirectoryEntry_t*)realloc( pDirectoryEntries, sizeof( xZipDirectoryEntry_t ) * Header.DirectoryEntries );
	memcpy( pDirectoryEntries + Header.DirectoryEntries - 1, &entry, sizeof( entry ) );

	WriteFileBytes( hTempFileData, fileBuff, entry.Length );

	// Align the data region to a 512 byte boundry:  (has to be on last entry as well to ensure enough space to perform the final read,
	// and initial alignment is taken careof by assembexzip)
	int nPadding = ( XBOX_HDD_SECTORSIZE - ( ftell( hTempFileData ) % XBOX_HDD_SECTORSIZE) ) % XBOX_HDD_SECTORSIZE;

	PadFileBytes( hTempFileData, nPadding );

	// Add the file to the file string table:
	AddFilename( filename );

	// Print a summary
	printf("+File: \"%s\": Length:%u Padding:%i\n", filename,  entry.Length, nPadding );

	return true;
}

bool xZipAddFile( const char* zipname, bool bPrecacheEntireFile, bool bProcessPrecacheHeader, bool bProcessPrecacheHeaderOnly )
{
	// Clean up the filename:
	char buffer[MAX_PATH];
	strcpy(buffer, zipname);

	// Fix slashes and convert it to lower case:
	char *filename;
	for( filename = buffer; *filename; filename++ )
	{
		if( *filename == '/' )
			*filename = '\\';
		else
		{
			*filename = (char)tolower(*filename);
		}
	}

	// Skip leading white space:
	for( filename = buffer; isspace(*filename); filename++ )
		;

	// Obliterate trailing white space:
	for(;;)
	{
		int len = (int)strlen( filename );
		if( len <= 0 )
		{
			printf("!!!! BAD FILENAME: \"%s\"\n", filename );
			return false;
		}
		
		if( isspace( filename[len-1] ) )
			filename[len-1]='\0';
		else
			break;
	}

	// Ensure we don't already have this file:
	unsigned CRC = xZipCRCFilename( filename );

	for( unsigned i=0; i < Header.DirectoryEntries; i++ )
	{
		if( pDirectoryEntries[i].FilenameCRC == CRC )
		{
			printf("!!!! NOT ADDING DUPLICATE FILENAME: \"%s\"\n", filename );
			return false;
		}
	}

	// Attempt to open the file:
	FILE* hFile = fopen( filename, "rb" );
	if( !hFile )
	{
		printf("!!!! FAILED TO OPEN FILE: \"%s\"\n", filename );
		return false;
	}

	// Get the length of the file:
	fseek(hFile,0,SEEK_END);
	unsigned fileSize = ftell(hFile);
	fseek(hFile,0,SEEK_SET);

	CUtlBuffer fileBuff;
	fileBuff.EnsureCapacity( fileSize );
	fread( fileBuff.Base(), fileSize, 1, hFile );
	fclose( hFile );

	fileBuff.SeekPut( CUtlBuffer::SEEK_HEAD, fileSize );

	return xZipAddFile( zipname, fileBuff, bPrecacheEntireFile, bProcessPrecacheHeader, bProcessPrecacheHeaderOnly );
}

int xZipBegin( const char* fileNameXzip )
{
	// Create and initialize the header:
	memset( &Header, 0, sizeof(Header) );	// Zero out the header:
	Header.Magic = xZipHeader_t::MAGIC;
	Header.Version = xZipHeader_t::VERSION;
	Header.HeaderLength = sizeof(Header);

	// Open the output file:
	hOutputFile = fopen(fileNameXzip,"wb+");
	if( !hOutputFile )
	{
		printf("Failed to open \"%s\" for writing.\n", fileNameXzip);
		exit( EXIT_FAILURE);
	}
	
	// Create a temporary file for storing the preloaded data:
	hTempFilePreload = tmpfile();
	if( !hTempFilePreload )
	{
		printf( "Error: failed to create temporary file\n" );
		return EXIT_FAILURE;
	}

	// Create a temporary file for storing the non preloaded data
	hTempFileData = tmpfile();
	if( !hTempFileData )
	{
		printf( "Error: failed to create temporary file\n");
		return EXIT_FAILURE;
	}
	
	return EXIT_SUCCESS;
}

bool xZipEnd()
{
	int nPreloadDirectorySize = sizeof(xZipDirectoryEntry_t)*Header.PreloadDirectoryEntries;
	int nRegular2PreloadSize = sizeof(unsigned short) * Header.DirectoryEntries;

	// Compute the size of the preloaded section:
	if( Header.PreloadDirectoryEntries )
	{
		fseek( hTempFilePreload, 0, SEEK_END );
		Header.PreloadBytes = ftell(hTempFilePreload) + nPreloadDirectorySize + nRegular2PreloadSize;	// Raw# of bytes to preload
		fseek( hTempFilePreload, 0, SEEK_SET );
	}
	else
	{
		Header.PreloadBytes = 0;
	}

	// Number of bytes preceeding the preloaded section:
	int nPreloadOffset = sizeof( Header ) + ( sizeof( xZipDirectoryEntry_t ) * Header.DirectoryEntries );

	// Number of bytes to pad between the end of the preload section and the start of the data section:
	int nPadding = ( 512 - ( ( nPreloadOffset + Header.PreloadBytes ) % 512) ) %512;				// Number of alignment bytes after the preload section

	// Offset past the preload section:
	int nDataOffset = nPreloadOffset + Header.PreloadBytes + nPadding;

	// Write out the header: (will need to be rewritten at the end as well) - note: not even bothering to byteswap at this point
	fwrite(&Header,sizeof(Header),1,hOutputFile);


	// Fixup each of the directory entries to make them relative to the beginning of the file.
	for( unsigned i=0; i< Header.DirectoryEntries;i++ )
	{
		xZipDirectoryEntry_t* pDir = &(pDirectoryEntries[i]);

		// Adjust files in the regular data area:
		pDir->StoredOffset = nDataOffset + pDir->StoredOffset;
	}

	// Sort and write the directory:
	printf("Sorting and writing %i directory entries...\n",Header.DirectoryEntries);
	qsort(pDirectoryEntries,Header.DirectoryEntries,sizeof(xZipDirectoryEntry_t),&xZipDirectoryEntry_t::xZipDirectoryEntrySortCompare); 

	// Swap the directory entries:
	for( unsigned i=0; i < Header.DirectoryEntries; i++ )
	{
		g_xzpSwap.SwapFieldsToTargetEndian<xZipDirectoryEntry_t>(&pDirectoryEntries[i]);
	}

	fwrite(pDirectoryEntries,Header.DirectoryEntries*sizeof(xZipDirectoryEntry_t),1, hOutputFile);

	// Swap the directory back for later use:
	for( unsigned i=0; i < Header.DirectoryEntries; i++ )
	{
		g_xzpSwap.SwapFieldsToTargetEndian<xZipDirectoryEntry_t>(&pDirectoryEntries[i]);
	}

	// Copy the preload section:
	if( Header.PreloadBytes > 0 )
	{
		printf("Generating the preload section...(%u)\n", Header.PreloadBytes);


		// Fixup each of the directory entries to make them relative to the beginning of the file.
		for( unsigned i=0; i< Header.PreloadDirectoryEntries;i++ )
		{
			xZipDirectoryEntry_t* pDir = &(pPreloadDirectoryEntries[i]);

			// Shift preload data down by preload bytes (and skipping over the directory):
			pDir->StoredOffset += nPreloadOffset + nPreloadDirectorySize + nRegular2PreloadSize;
		}

		printf("Sorting %u preload directory entries...\n",Header.PreloadDirectoryEntries);
		qsort(pPreloadDirectoryEntries,Header.PreloadDirectoryEntries,sizeof(xZipDirectoryEntry_t),&xZipDirectoryEntry_t::xZipDirectoryEntrySortCompare); 

		printf("Building regular to preload mapping table for %u entries...\n", Header.DirectoryEntries );
		unsigned short* Regular2Preload = (unsigned short*)malloc( nRegular2PreloadSize );
		for( unsigned i = 0; i < Header.DirectoryEntries; i++ )
		{
			unsigned short j;
			for( j = 0; j < Header.PreloadDirectoryEntries; j++ )
			{
				if( pDirectoryEntries[i].FilenameCRC == pPreloadDirectoryEntries[j].FilenameCRC )
					break;
			}

			// If I couldn't find it mark it as non-existant:
			if( j == Header.PreloadDirectoryEntries )
				j = 0xFFFF;

			Regular2Preload[i] = j;
		}

		printf("Writing preloaded directory entreis...\n" );

		// Swap the preload directory entries:
		for( unsigned i=0; i < Header.PreloadDirectoryEntries; i++ )
		{
			g_xzpSwap.SwapFieldsToTargetEndian<xZipDirectoryEntry_t>(&pPreloadDirectoryEntries[i]);
		}

		fwrite( pPreloadDirectoryEntries, Header.PreloadDirectoryEntries*sizeof(xZipDirectoryEntry_t),1, hOutputFile );

		// Swap them back:
		for( unsigned i=0; i < Header.PreloadDirectoryEntries; i++ )
		{
			g_xzpSwap.SwapFieldsToTargetEndian<xZipDirectoryEntry_t>(&pPreloadDirectoryEntries[i]);
		}

		printf("Writing regular to preload mapping (%u bytes)...\n", sizeof(unsigned short)*Header.DirectoryEntries );
		
		// Swap regular to preload mapping:
		g_xzpSwap.SwapBufferToTargetEndian<short>((short*)Regular2Preload, (short*)Regular2Preload, nRegular2PreloadSize / sizeof(short) );

		fwrite( Regular2Preload, nRegular2PreloadSize,1,hOutputFile );

		// Swap it back
		g_xzpSwap.SwapBufferToTargetEndian<short>((short*)Regular2Preload, (short*)Regular2Preload, nRegular2PreloadSize / sizeof(short) );

		printf("Copying %u Preloadable Bytes...\n", Header.PreloadBytes - nPreloadDirectorySize - nRegular2PreloadSize );
		fseek(hTempFilePreload,0,SEEK_SET);
		CopyFileBytes(hOutputFile, hTempFilePreload, Header.PreloadBytes - nPreloadDirectorySize - nRegular2PreloadSize  );
	}

	// Align the data section following the preload section:
	if( nPadding )
	{
		printf("Aligning Data Section Start by %u bytes...\n", nPadding );
		PadFileBytes(hOutputFile, nPadding );
	}

	// Copy the data section:
	fseek(hTempFileData, 0, SEEK_END );
	unsigned length = ftell( hTempFileData );
	fseek(hTempFileData, 0, SEEK_SET );
	printf("Copying %u Bytes...\n",length);

	CopyFileBytes(hOutputFile, hTempFileData, length);

	// Write out the filename data if present:
	if( nFilenameDataLength && Header.FilenameEntries )
	{
		Header.FilenameStringsOffset = ftell(hOutputFile);
		Header.FilenameStringsLength = (Header.FilenameEntries*sizeof(xZipFilenameEntry_t)) + nFilenameDataLength;
		
		// Adjust the offset in each of the filename offsets to absolute position in the file.
		for( unsigned i=0;i<Header.FilenameEntries;i++ )
		{
			pFilenameEntries[i].FilenameOffset += ( Header.FilenameStringsOffset + (Header.DirectoryEntries*sizeof(xZipFilenameEntry_t)));
		}

		printf("Sorting and writing %u filename directory entries...\n",Header.FilenameEntries);

		// Sort the data:
		qsort(pFilenameEntries,Header.FilenameEntries,sizeof(xZipFilenameEntry_t),&xZipFilenameEntry_t::xZipFilenameEntryCompare); 

		// Write the data out:
		for( unsigned int i = 0; i < Header.FilenameEntries; i++ )
		{
			g_xzpSwap.SwapFieldsToTargetEndian<xZipFilenameEntry_t>(&pFilenameEntries[i]);
		}

		fwrite(pFilenameEntries,1,Header.FilenameEntries*sizeof(xZipFilenameEntry_t),hOutputFile);

		// Swap them back:
		for( unsigned int i = 0; i < Header.FilenameEntries; i++ )
		{
			g_xzpSwap.SwapFieldsToTargetEndian<xZipFilenameEntry_t>(&pFilenameEntries[i]);
		}

		printf("Writing %u bytes of filename data...\n",nFilenameDataLength);
		fwrite(pFilenameData,1,nFilenameDataLength,hOutputFile);
	}

	// Compute the total file size, including the size of the footer:
	unsigned OutputFileBytes = ftell(hOutputFile) + sizeof(xZipFooter_t);

	// Write the footer:  (block used to keep possibly swapped footer from being used later)
	{
		xZipFooter_t footer;
		footer.Magic = xZipFooter_t::MAGIC;
		footer.Size = OutputFileBytes;

		g_xzpSwap.SwapFieldsToTargetEndian<xZipFooter_t>( &footer );	// Swap the footer
		fwrite( &footer, 1, sizeof(footer), hOutputFile );
	}

	// Seek back and rewrite the header (filename data changes it for example)
	fseek(hOutputFile,0,SEEK_SET);
	g_xzpSwap.SwapFieldsToTargetEndian<xZipHeader_t>( &Header );	// Swap it to write out:
	fwrite(&Header,1,sizeof(Header),hOutputFile);
	g_xzpSwap.SwapFieldsToTargetEndian<xZipHeader_t>( &Header );	// But then swap it back so we can use it in  memory

	// Shut down
	fclose(hOutputFile);

	// Print the summary
	printf("\n\nSummary: Input:%u, XZip:%u, Directory Entries:%u (%u preloaded), Preloaded Bytes:%u\n\n",InputFileBytes,OutputFileBytes,Header.DirectoryEntries, Header.PreloadDirectoryEntries, Header.PreloadBytes);

	// Shut down:
	fclose(hTempFileData);
	fclose(hTempFilePreload);

	return true;
}

#define PADD_ID	MAKEID('P','A','D','D')

//-----------------------------------------------------------------------------
// xZipComputeWAVPreload
//
// Returns the number of bytes from a xbox compliant WAV file that should go into 
// the preload section:
//-----------------------------------------------------------------------------
unsigned xZipComputeWAVPreload( char *pFileName )
{
	InFileRIFF riff( pFileName, *g_pSndIO );
	if ( riff.RIFFName() != RIFF_WAVE )
	{
		return 0;
	}

	IterateRIFF walk( riff, riff.RIFFSize() );

	while ( walk.ChunkAvailable() )
	{
		// xbox compliant wavs have a single PADD chunk
		if ( walk.ChunkName() == PADD_ID )
		{
			// want to preload data up through PADD chunk header
			// and not the actual pad bytes
			return walk.ChunkFilePosition() + 2*sizeof( int );
		}
		walk.ChunkNext();
	}

	return 0;
}


//-----------------------------------------------------------------------------
// xZipComputeXWVPreload
//
// Returns the number of bytes from a XWV file that should go into the preload
// section:
//-----------------------------------------------------------------------------
unsigned xZipComputeXWVPreload( const char* filename )
{
	FILE* hFile = fopen( filename, "rb" );
	if ( !hFile )
	{
		printf( "Failed to open xwv file: %s\n", filename );
		return 0;
	}

	// Read and validate the XWV header:
	xwvHeader_t header;
	memset( &header, 0, sizeof(header) );
	fread( &header, 1, sizeof(header), hFile );
	fclose( hFile );
	
	if ( header.id != XWV_ID || header.headerSize != sizeof(header) )
		return 0;

	return header.GetPreloadSize();
}

unsigned xZipComputeXTFPreload( const char* filename )
{
#if 0 // X360TBD: Not using XTF anymore
	FILE* hFile = fopen( filename, "rb" );
	if ( !hFile )
	{
		printf("Failed to open file: %s\n", filename);
		return 0;
	}

	XTFFileHeader_t header;
	memset( &header,0, sizeof( header ) );
	fread( &header,1,sizeof(header),hFile);

	fclose(hFile);

	if ( !strncmp( header.fileTypeString, "XTF", 4 ) )
		return header.preloadDataSize;
#endif
	return 0;
}

// TODO: ONLY store them in the preload section:
unsigned xZipComputeVMTPreload( const char* filename )
{
	// Store VMT's entirely
	if ( !strstr(filename,".vmt") )
		return 0;

	FILE* hFile = fopen( filename, "rb" );
	if ( !hFile )
	{
		printf("Failed to open file: %s\n", filename);
		return 0;
	}

	fseek( hFile, 0, SEEK_END );
	unsigned offset = ftell( hFile );
	fclose( hFile );
	return offset;
}

// TODO: ONLY store them in the preload section:
unsigned xZipComputeVHVPreload( const char* filename )
{
	// Store VMT's entirely
	if ( !strstr(filename,".vhv") )
		return 0;

	FILE* hFile = fopen( filename, "rb" );
	if ( !hFile )
	{
		printf("Failed to open file: %s\n", filename);
		return 0;
	}

	fclose( hFile );

	// Just load the header:
	return sizeof(HardwareVerts::FileHeader_t);
}

unsigned xZipComputeXCSPreload( const char* filename )
{
	if( !strstr(filename,".vcs") )
		return 0;

	FILE* hFile = fopen( filename, "rb" );
	if ( !hFile )
	{
		printf("Failed to open file: %s\n", filename);
		return 0;
	}

	XShaderHeader_t header;
	fread(&header,1,sizeof(XShaderHeader_t), hFile);
	fseek(hFile,0,SEEK_END);
	fclose(hFile);
	
	if (!header.IsValid())
		return 0;

	return header.BytesToPreload();
}

unsigned xZipComputeCustomPreloads( const char* filename )
{
	// X360TBD: These all need to act on a utlbuffer
	Assert( 0 );
	return 0;

//	strlwr(filename);

	unsigned offset = xZipComputeXWVPreload( filename );
	if ( offset )
		return offset;

	offset = xZipComputeVMTPreload( filename );
	if ( offset )
		return offset;

	offset = xZipComputeXCSPreload( filename );
	if ( offset )
		return offset;

	offset = xZipComputeVHVPreload( filename );
	if ( offset )
		return offset;

	return xZipComputeXTFPreload( filename );
}

#endif // MAKE_GAMEDATA_TOOL