//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Engine specific CRC functions
//
//=============================================================================//


#include "checksum_engine.h"
#include "bspfile.h"
#include "filesystem.h"
#include "filesystem_engine.h"

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

//-----------------------------------------------------------------------------
// Purpose: For proxy protecting
// Turns the passed data into a 
// single byte block CRC based on current network sequence number.
// Input  : *base - 
//			length - 
//			sequence - 
// Output : byte COM_BlockSequenceCRCByte
//-----------------------------------------------------------------------------
byte COM_BlockSequenceCRCByte( byte *base, int length, int sequence )
{
	CRC32_t crc;
	byte *p;
	byte chkb[60 + 4];

	if (sequence < 0)
	{
		Sys_Error("sequence < 0, in COM_BlockSequenceCRCByte\n");
	}

	CRC32_t entry;
	entry = CRC32_GetTableEntry( ( sequence + 1 ) % ( 256 * sizeof(CRC32_t) - 4 ) );
	p = (byte *)&entry;

	// Use up to the first 60 bytes of data and a 4 byte value from the 
	//  CRC lookup table (divided into the sequence)

	if (length > 60)
	{
		length = 60;
	}
	
	memcpy (chkb, base, length);

	chkb[length+0] = p[0];
	chkb[length+1] = p[1];
	chkb[length+2] = p[2];
	chkb[length+3] = p[3];

	length += 4;

	// Compute a crc based on the buffer.
	CRC32_Init(&crc);
	CRC32_ProcessBuffer(&crc, chkb, length);
	CRC32_Final(&crc);
	
	// Chop down to byte size
	return (byte)(crc & 0xFF);
}

// YWB:  5/18
/*
===================
bool CRC_File(unsigned short *crcvalue, char *pszFileName)

  Computes CRC for given file.  If there is an error opening/reading the file, returns false,
  otherwise returns true and sets the crc value passed to it.  The value should be initialized
  with CRC_Init
 ==================
 */
bool CRC_File(CRC32_t *crcvalue, const char *pszFileName)
{
	// Always re-init the CRC buffer
	CRC32_Init( crcvalue );

	FileHandle_t fp;
	byte chunk[1024];
	int nBytesRead;
	
	int nSize;

	nSize = COM_OpenFile(pszFileName, &fp);
	if ( !fp || ( nSize == -1 ) )
		return FALSE;

	// Now read in 1K chunks
	while (nSize > 0)
	{
		if (nSize > 1024)
			nBytesRead = g_pFileSystem->Read(chunk, 1024, fp);
		else
			nBytesRead = g_pFileSystem->Read(chunk, nSize, fp);

		// If any data was received, CRC it.
		if (nBytesRead > 0)
		{
			nSize -= nBytesRead;
			CRC32_ProcessBuffer(crcvalue, chunk, nBytesRead);
		}

		// We we are end of file, break loop and return
		if ( g_pFileSystem->EndOfFile( fp ) )
		{
			g_pFileSystem->Close( fp );
			fp = 0;
			break;
		}
		// If there was a disk error, indicate failure.
		else if ( nBytesRead <= 0 || !g_pFileSystem->IsOk(fp) )
		{
			if ( fp )
				g_pFileSystem->Close(fp);
			return FALSE;
		}
	}	

	if ( fp )
		g_pFileSystem->Close(fp);
	return TRUE;
}

// YWB:  5/18
/*
===================
bool CRC_MapFile(unsigned short *crcvalue, char *pszFileName)

  Computes CRC for given map file.  If there is an error opening/reading the file, returns false,
  otherwise returns true and sets the crc value passed to it.  The value should be initialized
  with CRC_Init

  For map (.bsp) files, the entity lump is not included in the CRC.
  //FIXME make this work
 ==================
 */
bool CRC_MapFile(CRC32_t *crcvalue, const char *pszFileName)
{
	FileHandle_t fp;
	byte chunk[1024];
	int i, l;
	int nBytesRead;
	dheader_t	header;
	int nSize;
	lump_t *curLump;
	long startOfs;

	nSize = COM_OpenFile(pszFileName, &fp);
	if ( !fp || ( nSize == -1 ) )
		return false;

	startOfs = g_pFileSystem->Tell(fp);

	// Don't CRC the header.
	if (g_pFileSystem->Read(&header, sizeof(dheader_t), fp) == 0)
	{
		ConMsg("Could not read BSP header for map [%s].\n", pszFileName);
		g_pFileSystem->Close(fp);
		return false;
	}

	i = header.version;
	if ( i < MINBSPVERSION || i > BSPVERSION )
	{
		g_pFileSystem->Close(fp);
		ConMsg("Map [%s] has incorrect BSP version (%i should be %i).\n", pszFileName, i, BSPVERSION);
		return false;
	}

	if ( IsX360() )
	{
		// 360 bsp's store the pc checksum in the flags lump header
		g_pFileSystem->Close(fp);
		*crcvalue = header.lumps[LUMP_MAP_FLAGS].version;
		return true;
	}

	// CRC across all lumps except for the Entities lump
	for (l = 0; l < HEADER_LUMPS; l++)
	{
		if (l == LUMP_ENTITIES)
			continue;

		curLump = &header.lumps[l];
		nSize = curLump->filelen;

		g_pFileSystem->Seek( fp, startOfs + curLump->fileofs, FILESYSTEM_SEEK_HEAD );

		// Now read in 1K chunks
		while (nSize > 0)
		{
			if (nSize > 1024)
				nBytesRead = g_pFileSystem->Read(chunk, 1024, fp);
			else
				nBytesRead = g_pFileSystem->Read(chunk, nSize, fp);

			// If any data was received, CRC it.
			if (nBytesRead > 0)
			{
				nSize -= nBytesRead;
				CRC32_ProcessBuffer(crcvalue, chunk, nBytesRead);
			}

			// If there was a disk error, indicate failure.
			if ( !g_pFileSystem->IsOk(fp) )
			{
				if ( fp )
					g_pFileSystem->Close(fp);
				return false;
			}
		}	
	}
	
	if ( fp )
		g_pFileSystem->Close(fp);
	return true;
}

bool MD5_MapFile(MD5Value_t *md5value, const char *pszFileName)
{
	FileHandle_t fp;
	byte chunk[1024];
	int i, l;
	int nBytesRead;
	dheader_t	header;
	int nSize;
	lump_t *curLump;
	long startOfs;

	nSize = COM_OpenFile(pszFileName, &fp);
	if ( !fp || ( nSize == -1 ) )
		return false;

	MD5Context_t ctx;
	V_memset( &ctx, 0, sizeof(MD5Context_t) );
	MD5Init( &ctx );

	startOfs = g_pFileSystem->Tell(fp);

	// Don't MD5 the header.
	if (g_pFileSystem->Read(&header, sizeof(dheader_t), fp) == 0)
	{
		ConMsg("Could not read BSP header for map [%s].\n", pszFileName);
		g_pFileSystem->Close(fp);
		return false;
	}

	i = header.version;
	if ( i < MINBSPVERSION || i > BSPVERSION )
	{
		g_pFileSystem->Close(fp);
		ConMsg("Map [%s] has incorrect BSP version (%i should be %i).\n", pszFileName, i, BSPVERSION);
		return false;
	}

	if ( IsX360() )
	{
		// 360 bsp's store the pc checksum in the flags lump header
		g_pFileSystem->Close(fp);
		char versionString[65] = {0};
		V_snprintf( versionString, ARRAYSIZE(versionString), "%d", header.lumps[LUMP_MAP_FLAGS].version );
		V_memcpy( md5value->bits, versionString, MD5_DIGEST_LENGTH );
		return true;
	}

	// MD5 across all lumps except for the Entities lump
	for (l = 0; l < HEADER_LUMPS; l++)
	{
		if (l == LUMP_ENTITIES)
			continue;

		curLump = &header.lumps[l];
		nSize = curLump->filelen;

		g_pFileSystem->Seek( fp, startOfs + curLump->fileofs, FILESYSTEM_SEEK_HEAD );

		// Now read in 1K chunks
		while (nSize > 0)
		{
			if (nSize > 1024)
				nBytesRead = g_pFileSystem->Read(chunk, 1024, fp);
			else
				nBytesRead = g_pFileSystem->Read(chunk, nSize, fp);

			// If any data was received, CRC it.
			if (nBytesRead > 0)
			{
				nSize -= nBytesRead;
				MD5Update( &ctx, chunk, nBytesRead );
			}

			// If there was a disk error, indicate failure.
			if ( !g_pFileSystem->IsOk(fp) )
			{
				if ( fp )
					g_pFileSystem->Close(fp);
				return false;
			}
		}	
	}

	if ( fp )
		g_pFileSystem->Close(fp);

	MD5Final( md5value->bits, &ctx );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : digest[16] - 
//			*pszFileName - 
//			bSeed - 
//			seed[4] - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool MD5_Hash_File(unsigned char digest[16], const char *pszFileName, bool bSeed /* = FALSE */, unsigned int seed[4] /* = NULL */)
{
	FileHandle_t fp;
	byte chunk[1024];
	int nBytesRead;
	MD5Context_t ctx;
	
	int nSize;

	nSize = COM_OpenFile( pszFileName, &fp );
	if ( !fp || ( nSize == -1 ) )
		return false;

	memset(&ctx, 0, sizeof(MD5Context_t));

	MD5Init(&ctx);

	if (bSeed)
	{
		// Seed the hash with the seed value
		MD5Update( &ctx, (const unsigned char *)&seed[0], 16 );
	}

	// Now read in 1K chunks
	while (nSize > 0)
	{
		if (nSize > 1024)
			nBytesRead = g_pFileSystem->Read(chunk, 1024, fp);
		else
			nBytesRead = g_pFileSystem->Read(chunk, nSize, fp);

		// If any data was received, CRC it.
		if (nBytesRead > 0)
		{
			nSize -= nBytesRead;
			MD5Update(&ctx, chunk, nBytesRead);
		}

		// We we are end of file, break loop and return
		if ( g_pFileSystem->EndOfFile( fp ) )
		{
			g_pFileSystem->Close( fp );
			fp = NULL;
			break;
		}
		// If there was a disk error, indicate failure.
		else if ( !g_pFileSystem->IsOk(fp) )
		{
			if ( fp )
				g_pFileSystem->Close(fp);
			return FALSE;
		}
	}	

	if ( fp )
		g_pFileSystem->Close(fp);

	MD5Final(digest, &ctx);

	return TRUE;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool MD5_Hash_Buffer( unsigned char pDigest[16], const unsigned char *pBuffer, int nSize, bool bSeed /* = FALSE */, unsigned int seed[4] /* = NULL */ )
{
	MD5Context_t ctx;
	
	if ( !pBuffer || !nSize )
		return false;

	memset( &ctx, 0, sizeof( MD5Context_t ) );
	MD5Init( &ctx );

	if ( bSeed )
	{
		// Seed the hash with the seed value
		MD5Update( &ctx, (const unsigned char *)&seed[0], 16 );
	}

	// Now read in 1024 chunks
	const unsigned char *pChunk = pBuffer;
	while ( nSize > 0 )
	{
		const int nChunkSize = MIN( 1024, nSize );
		MD5Update( &ctx, pChunk, nChunkSize );
		nSize -= nChunkSize;
		pChunk += nChunkSize;	AssertValidReadPtr( pChunk );
	}	

	MD5Final( pDigest, &ctx );

	return true;
}