/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

///////////////////////////////////////////////////////////////////////////
/// @file	common.c
/// @brief	Misc functions used in client and server.
/// 
/// All of Quake's data access is through a hierarchal file system, but the
/// contents of the file system can be transparently merged from several sources.
/// 
/// The "base directory" is the path to the directory holding the quake.exe
/// and all game directories. The sys_* files pass this to host_init in
/// quakeparms_t->basedir.  This can be overridden with the "-basedir"
/// command line parm to allow code debugging in a different directory.
/// The base directory is only used during filesystem initialization.
/// 
/// The "game directory" is the first tree on the search path and directory
/// that all generated files (savegames, screenshots, demos, config files)
/// will be saved to.  This can be overridden with the "-game" command line
/// parameter.  The game directory can never be changed while quake is executing.
/// This is a precaution against having a malicious server instruct clients
/// to write files over areas they shouldn't.
/// 
/// The "cache directory" is only used during development to save network
/// bandwidth, especially over ISDN / T1 lines.  If there is a cache
/// directory specified, when a file is found by the normal search path,
/// it will be mirrored into the cache directory, then opened there.
/// 
/// 
/// @todo	FIXME: The file "parms.txt" will be read out of the
/// game directory and appended to the current command line arguments
/// to allow different games to initialize startup parms differently.
/// This could be used to add a "-sspeed 22050" for the high quality sound
/// edition.  Because they are added at the end, they will not override an
/// explicit setting on the original command line.
/// 
/// @defgroup common_functions Common Functions
/// @defgroup library_functions Library Functions
/// @defgroup message_functions Message Functions
///////////////////////////////////////////////////////////////////////////

#include "quakedef.h"

#define NUM_SAFE_ARGVS  7

static char *largv[MAX_NUM_ARGVS + NUM_SAFE_ARGVS + 1];
static char *argvdummy = " ";

static char *safeargvs[NUM_SAFE_ARGVS] = {"-stdvid", "-nolan", "-nosound", "-nocdaudio", "-nojoy", "-nomouse", "-dibonly"};

cvar_t registered = {"registered", "0"};
cvar_t cmdline = {"cmdline", "0", false, true};

bool   com_modified;   // set true if using non-id files
bool   proghack;
int    static_registered = 1;  // only for startup check, then set
bool   msg_suppress_1 = 0;

void COM_InitFilesystem (void);

/// if a packfile directory differs from this, it is assumed to be hacked
#define PAK0_COUNT	339
#define PAK0_CRC	32981

char	com_token[1024];	///< global Command Token
int		com_argc;			///< global Command argument count
char	**com_argv;			///< global Command argument vector

#define CMDLINE_LENGTH	256
char	com_cmdline[CMDLINE_LENGTH];

bool	standard_quake = true, rogue, hipnotic;

/// This graphic needs to be in the pak file to use registered features.
unsigned short pop[] =
{
	0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
	, 0x0000, 0x0000, 0x6600, 0x0000, 0x0000, 0x0000, 0x6600, 0x0000
	, 0x0000, 0x0066, 0x0000, 0x0000, 0x0000, 0x0000, 0x0067, 0x0000
	, 0x0000, 0x6665, 0x0000, 0x0000, 0x0000, 0x0000, 0x0065, 0x6600
	, 0x0063, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6563
	, 0x0064, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6564
	, 0x0064, 0x6564, 0x0000, 0x6469, 0x6969, 0x6400, 0x0064, 0x6564
	, 0x0063, 0x6568, 0x6200, 0x0064, 0x6864, 0x0000, 0x6268, 0x6563
	, 0x0000, 0x6567, 0x6963, 0x0064, 0x6764, 0x0063, 0x6967, 0x6500
	, 0x0000, 0x6266, 0x6769, 0x6a68, 0x6768, 0x6a69, 0x6766, 0x6200
	, 0x0000, 0x0062, 0x6566, 0x6666, 0x6666, 0x6666, 0x6562, 0x0000
	, 0x0000, 0x0000, 0x0062, 0x6364, 0x6664, 0x6362, 0x0000, 0x0000
	, 0x0000, 0x0000, 0x0000, 0x0062, 0x6662, 0x0000, 0x0000, 0x0000
	, 0x0000, 0x0000, 0x0000, 0x0061, 0x6661, 0x0000, 0x0000, 0x0000
	, 0x0000, 0x0000, 0x0000, 0x0000, 0x6500, 0x0000, 0x0000, 0x0000
	, 0x0000, 0x0000, 0x0000, 0x0000, 0x6400, 0x0000, 0x0000, 0x0000
};

//============================================================================

// *Link functions are used for the world (?) maps

///////////////////////////////////////////////////////////////////////////
/// @brief	ClearLink is used for new headnodes.
///			Make a @e link point to itself.
///////////////////////////////////////////////////////////////////////////
void ClearLink (link_t *l)
{
	l->prev = l->next = l;
}

void RemoveLink (link_t *l)
{
	l->next->prev = l->prev;
	l->prev->next = l->next;
}

void InsertLinkBefore (link_t *l, link_t *before)
{
	l->next = before;
	l->prev = before->prev;
	l->prev->next = l;
	l->next->prev = l;
}
void InsertLinkAfter (link_t *l, link_t *after)
{
	l->next = after->next;
	l->prev = after;
	l->prev->next = l;
	l->next->prev = l;
}

/*
============================================================================

					LIBRARY REPLACEMENT FUNCTIONS

============================================================================
*/

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake memory set
/// 
/// Set the memory of @b dest to the var value of @b fill for
/// @b count integers(?)
/// @todo	fix brief
/// @param	dest The starting point of the memory block to fill.
/// @param	fill The value to fill the memory with.
/// @param	count The size of the memory block to fill.
/// @return void
///////////////////////////////////////////////////////////////////////////
void Q_memset (void *dest, int fill, int count)
{
	int i;
	
	// what now?
	if ((((long) dest | count) & 3) == 0)
	{
		/// Divide count by 4 (?).
		count >>= 2;

		// what now?
		fill = fill | (fill << 8) | (fill << 16) | (fill << 24);

		/// Fill the memory with value @b fill.
		for (i = 0 ; i < count ; i++)
		{
			((int *) dest) [i] = fill;
		}
	}
	else
	{
		/// Fill the memory with value @b fill.
		for (i = 0; i < count; i++)
		{
			((byte *) dest) [i] = fill;
		}
	}
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake memory copy
/// 
/// Copy the memory from @b src to @b dest
/// @param	dest The memory block to copy @b to.
/// @param	src The memory block to copy @b from.
/// @param	count 
///////////////////////////////////////////////////////////////////////////
void Q_memcpy (void *dest, void *src, int count)
{
	int i;
	
	// if what now?
	if ((((long) dest | (long) src | count) & 3) == 0)
	{
		/// Divide count by 4 (?).
		count >>= 2;

		/// Copy memory from @b src to @b dest
		for (i = 0 ; i < count ; i++)
		{
			((int *) dest)[i] = ((int *) src)[i];
		}
	}
	else
	{
		/// Copy memory from @b src to @b dest
		for (i = 0 ; i < count ; i++)
		{
			((byte *) dest)[i] = ((byte *) src)[i];
		}
	}
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake memory compare
/// 
/// Compare 2 blocks of memory with each other.
/// @param	m1 The first memory block.
/// @param	m2 The second memory block.
/// @param	count The amount of integers to check.
/// @return 0 if the memory is equal, -1 if unequal.
///////////////////////////////////////////////////////////////////////////
int Q_memcmp (void *m1, void *m2, int count)
{
	while (count)
	{
		count--;
		
		/// If @b m1 does not equal @b m2 return -1
		if (((byte *) m1) [count] != ((byte *) m2) [count])
		{
			return -1;
		}
	}

	/// No inequality has been found. Return 0
	return 0;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @param	dest The destination pointer
/// @param	src The source pointer
/// @brief	Copy all characters from src to dest
///////////////////////////////////////////////////////////////////////////
void Q_strcpy (char *dest, char *src)
{
	while (*src)
	{
		*dest++ = *src++;
	}
	*dest++ = 0;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake string copy up until @b count characters
/// @param	m1 
/// @param	m2 
/// @param	count 
/// @return void
///////////////////////////////////////////////////////////////////////////
void Q_strncpy (char *dest, char *src, int count)
{
	while (*src && count--)
	{
		*dest++ = *src++;
	}
	if (count)
		*dest++ = 0;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Check the string's length
/// @param	str The string to check its length
/// @return	The string's length
///////////////////////////////////////////////////////////////////////////
int Q_strlen (char *str)
{
	int count;
	
	count = 0;
	while (str[count])
		count++;
		
	return count;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	
/// @param	s
/// @param	c
/// @brief	Quake string ???
/// @todo	fix brief
///////////////////////////////////////////////////////////////////////////
char *Q_strrchr (char *s, char c)
{
	int len = Q_strlen (s);
	s += len;
	
	while (len--)
	{
		if (*--s == c)
		{
			return s;
		}
	}
	
	return 0;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	
/// @param	dest The string to concatenate to.
/// @param	src The string to concatenate
/// @brief	Quake string concatenate (string them together)
///////////////////////////////////////////////////////////////////////////
void Q_strcat (char *dest, char *src)
{
	dest += Q_strlen (dest);
	Q_strcpy (dest, src);
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake string compare
/// @param	s1 First string
/// @param	s2 Second string
/// @return 0 if equal, else -1
///////////////////////////////////////////////////////////////////////////
int Q_strcmp (char *s1, char *s2)
{
	while (1)
	{
		/// Compare characters, if unequal return -1.
		if (*s1 != *s2)
		{
			return -1;              // strings not equal
		}

		/// If the pointer has reached the end of the string
		/// (and thus, the strings are still equal) return 0.
		if (!*s1)
		{
			return 0;               // strings are equal
		}

		/// Point to the next @b char of the string
		s1++;
		s2++;
	}
	
	return -1;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake string compare up to @b count characters
/// 
/// Compares up to @b count characters of the string @b s1 to those of
/// the string @b s2.
/// @param	s1 First string
/// @param	s2 Second string
/// @param	count The amount of characters to check
/// @return 0 if equal, else -1
///////////////////////////////////////////////////////////////////////////
int Q_strncmp (char *s1, char *s2, int count)
{
	while (1)
	{
		/// If count is zero, return 0. Else decrement count.
		if (!count--)
		{
			return 0;
		}

		/// Compare characters, if unequal return -1.
		if (*s1 != *s2)
		{
			return -1;              // strings not equal
		}

		/// If the pointer has reached the end of the string
		/// (and thus, the strings are still equal) return 0.
		if (!*s1)
		{
			return 0;               // strings are equal
		}

		/// Point to the next @b char of the string
		s1++;
		s2++;
	}
	
	return -1;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
int Q_strncasecmp (char *s1, char *s2, int n)
{
	int             c1, c2;
	
	while (1)
	{
		c1 = *s1++;
		c2 = *s2++;
		
		if (!n--)
			return 0;               // strings are equal until end point
			
		if (c1 != c2)
		{
			if (c1 >= 'a' && c1 <= 'z')
				c1 -= ('a' - 'A');
			if (c2 >= 'a' && c2 <= 'z')
				c2 -= ('a' - 'A');
			if (c1 != c2)
				return -1;              // strings not equal
		}
		if (!c1)
			return 0;               // strings are equal
//              s1++;
//              s2++;
	}
	
	return -1;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
int Q_strcasecmp (char *s1, char *s2)
{
	return Q_strncasecmp (s1, s2, 99999);
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	Quake ASCII to integer.
///			Input a string to get an int.
/// @param	str input string
/// @return The integer
///////////////////////////////////////////////////////////////////////////
int Q_atoi (char *str)
{
	int val;
	int sign;
	int c;
	
	if (*str == '-')
	{
		sign = -1;
		str++;
	}
	else
		sign = 1;
		
	val = 0;
	
	//
	// check for hex
	//
	if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
	{
		str += 2;
		while (1)
		{
			c = *str++;
			if (c >= '0' && c <= '9')
				val = (val << 4) + c - '0';
			else if (c >= 'a' && c <= 'f')
				val = (val << 4) + c - 'a' + 10;
			else if (c >= 'A' && c <= 'F')
				val = (val << 4) + c - 'A' + 10;
			else
				return val * sign;
		}
	}
	
	//
	// check for character
	//
	if (str[0] == '\'')
	{
		return sign * str[1];
	}
	
	//
	// assume decimal
	//
	while (1)
	{
		c = *str++;
		if (c < '0' || c > '9')
			return val * sign;
		val = val * 10 + c - '0';
	}
	
	return 0;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup library_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
float Q_atof (char *str)
{
	double			val;
	int             sign;
	int             c;
	int             decimal, total;
	
	if (*str == '-')
	{
		sign = -1;
		str++;
	}
	else
		sign = 1;
		
	val = 0;
	
	//
	// check for hex
	//
	if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
	{
		str += 2;
		while (1)
		{
			c = *str++;
			if (c >= '0' && c <= '9')
				val = (val * 16) + c - '0';
			else if (c >= 'a' && c <= 'f')
				val = (val * 16) + c - 'a' + 10;
			else if (c >= 'A' && c <= 'F')
				val = (val * 16) + c - 'A' + 10;
			else
				return val * sign;
		}
	}
	
	//
	// check for character
	//
	if (str[0] == '\'')
	{
		return sign * str[1];
	}
	
	//
	// assume decimal
	//
	decimal = -1;
	total = 0;
	while (1)
	{
		c = *str++;
		if (c == '.')
		{
			decimal = total;
			continue;
		}
		if (c < '0' || c > '9')
			break;
		val = val * 10 + c - '0';
		total++;
	}
	
	if (decimal == -1)
		return val * sign;
	while (total > decimal)
	{
		val /= 10;
		total--;
	}
	
	return val * sign;
}

/*
============================================================================

					BYTE ORDER FUNCTIONS

============================================================================
*/

bool        bigendien;

short (*BigShort) (short l);
short (*LittleShort) (short l);
int (*BigLong) (int l);
int (*LittleLong) (int l);
float (*BigFloat) (float l);
float (*LittleFloat) (float l);

short ShortSwap (short l)
{
	byte    b1, b2;
	
	b1 = l & 255;
	b2 = (l >> 8) & 255;
	
	return (b1 << 8) + b2;
}

short   ShortNoSwap (short l)
{
	return l;
}

int    LongSwap (int l)
{
	byte    b1, b2, b3, b4;
	
	b1 = l & 255;
	b2 = (l >> 8) & 255;
	b3 = (l >> 16) & 255;
	b4 = (l >> 24) & 255;
	
	return ( (int) b1 << 24) + ( (int) b2 << 16) + ( (int) b3 << 8) + b4;
}

int     LongNoSwap (int l)
{
	return l;
}

float FloatSwap (float f)
{
	union
	{
		float   f;
		byte    b[4];
	} dat1, dat2;
	
	
	dat1.f = f;
	dat2.b[0] = dat1.b[3];
	dat2.b[1] = dat1.b[2];
	dat2.b[2] = dat1.b[1];
	dat2.b[3] = dat1.b[0];
	return dat2.f;
}

float FloatNoSwap (float f)
{
	return f;
}

/*
==============================================================================

			MESSAGE IO FUNCTIONS

Handles byte ordering and avoids alignment errors
==============================================================================
*/

//
// writing functions
//

///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief
///////////////////////////////////////////////////////////////////////////
void MSG_WriteChar (sizebuf_t *sb, int c)
{
	byte    *buf;
	
#ifdef PARANOID
	if (c < -128 || c > 127)
		Sys_Error ("MSG_WriteChar: range error");
#endif
		
	buf = SZ_GetSpace (sb, 1);
	buf[0] = c;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	 
/// @param	c 
/// @param	sb 
///////////////////////////////////////////////////////////////////////////
void MSG_WriteByte (sizebuf_t *sb, int c)
{
	byte *buf;
	
#ifdef PARANOID
	if (c < 0 || c > 255)
		Sys_Error ("MSG_WriteByte: range error");
#endif
		
	buf = (byte *) SZ_GetSpace (sb, 1);
	buf[0] = c;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void MSG_WriteShort (sizebuf_t *sb, int c)
{
	byte    *buf;
	
#ifdef PARANOID
	if (c < ( (short) 0x8000) || c > (short) 0x7fff)
		Sys_Error ("MSG_WriteShort: range error");
#endif
		
	buf = SZ_GetSpace (sb, 2);
	buf[0] = c & 0xff;
	buf[1] = c >> 8;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void MSG_WriteLong (sizebuf_t *sb, int c)
{
	byte    *buf;
	
	buf = SZ_GetSpace (sb, 4);
	buf[0] = c & 0xff;
	buf[1] = (c >> 8) & 0xff;
	buf[2] = (c >> 16) & 0xff;
	buf[3] = c >> 24;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void MSG_WriteFloat (sizebuf_t *sb, float f)
{
	union
	{
		float   f;
		int     l;
	} dat;
	
	
	dat.f = f;
	dat.l = LittleLong (dat.l);
	
	SZ_Write (sb, &dat.l, 4);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void MSG_WriteString (sizebuf_t *sb, char *s)
{
	if (!s)
		SZ_Write (sb, "", 1);
	else
		SZ_Write (sb, s, Q_strlen (s) + 1);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief
///////////////////////////////////////////////////////////////////////////
void MSG_WriteCoord (sizebuf_t *sb, float f)
{
	MSG_WriteShort (sb, (int) (f * 8));
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void MSG_WriteAngle (sizebuf_t *sb, float f)
{
	MSG_WriteByte (sb, ( (int) f * 256 / 360) & 255);
}

//
// reading functions
//
int	 msg_readcount;
bool msg_badread;

///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void MSG_BeginReading (void)
{
	msg_readcount = 0;
	msg_badread = false;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	Read and return a char.
/// @return	-1 and sets msg_badread if no more characters are available.
///////////////////////////////////////////////////////////////////////////
int MSG_ReadChar (void)
{
	int     c;
	
	if (msg_readcount + 1 > net_message.cursize)
	{
		msg_badread = true;
		return -1;
	}
	
	c = (signed char) net_message.data[msg_readcount];
	msg_readcount++;
	
	return c;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	Read and return the next char from the @e net_message
/// @return	-1, if @e msg_readcount is bigger than @e net_message.cursize
/// @return	The read char.
///////////////////////////////////////////////////////////////////////////
int MSG_ReadByte (void)
{
	int c;
	
	if (msg_readcount + 1 > net_message.cursize)
	{
		msg_badread = true;
		return -1;
	}
	
	c = (unsigned char) net_message.data[msg_readcount];
	msg_readcount++;
	
	return c;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	Read a short int from @e net_message and return it.
/// @return	-1, if there's a bad read.
/// @return	The read short int.
///////////////////////////////////////////////////////////////////////////
int MSG_ReadShort (void)
{
	int	c;
	
	if (msg_readcount + 2 > net_message.cursize)
	{
		msg_badread = true;
		return -1;
	}
	
	c = (short) (net_message.data[msg_readcount]
				 + (net_message.data[msg_readcount+1] << 8));
				 
	msg_readcount += 2;
	
	return c;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	Read a long int from @e net_message and return it.
/// @return	-1, if there's a bad read.
/// @return	The read long int.
///////////////////////////////////////////////////////////////////////////
int MSG_ReadLong (void)
{
	int		c;
	
	if (msg_readcount + 4 > net_message.cursize)
	{
		msg_badread = true;
		return -1;
	}
	
	c = net_message.data[msg_readcount]
		+ (net_message.data[msg_readcount+1] << 8)
		+ (net_message.data[msg_readcount+2] << 16)
		+ (net_message.data[msg_readcount+3] << 24);
		
	msg_readcount += 4;
	
	return c;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup message_functions
/// @brief	Read a float from @e net_message and return it.
/// @return	The read float.
///////////////////////////////////////////////////////////////////////////
float MSG_ReadFloat (void)
{
	union
	{
		byte    b[4];
		float   f;
		int     l;
	} dat;
	
	dat.b[0] =      net_message.data[msg_readcount];
	dat.b[1] =      net_message.data[msg_readcount+1];
	dat.b[2] =      net_message.data[msg_readcount+2];
	dat.b[3] =      net_message.data[msg_readcount+3];
	msg_readcount += 4;
	
	dat.l = LittleLong (dat.l);
	
	return dat.f;
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Read the characters from net_message and return it as a string.
/// @return The read string.
///////////////////////////////////////////////////////////////////////////
char *MSG_ReadString (void)
{
	static char	string[2048];
	int			l, c;
	
	l = 0;
	do
	{
		c = MSG_ReadChar ();
		if (c == -1 || c == 0)
			break;
		string[l] = c;
		l++;
	}
	while (l < sizeof (string) - 1);
	
	string[l] = 0;
	
	return string;
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Read the coords and return them as a float.
///////////////////////////////////////////////////////////////////////////
float MSG_ReadCoord (void)
{
	return MSG_ReadShort() * (1.0f / 8);
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Read the angle and return them as a float.
///////////////////////////////////////////////////////////////////////////
float MSG_ReadAngle (void)
{
	return MSG_ReadChar() * (360.0f / 256);
}


//===========================================================================

///////////////////////////////////////////////////////////////////////////
/// @brief	Allocate a memory chunk for writing ASCII text to.
/// @param	buf The buffer to allocate memory for.
/// @param	startsize The size of the to be allocated memory.
///////////////////////////////////////////////////////////////////////////
void SZ_Alloc (sizebuf_t *buf, int startsize)
{
	/// Minimum @e startsize of @b 256
	if (startsize < 256)
		startsize = 256;

	buf->data = (byte *) Hunk_AllocName (startsize, "sizebuf");
	buf->maxsize = startsize;
	buf->cursize = 0;
}


void SZ_Free (sizebuf_t *buf)
{
//      Z_Free (buf->data);
//      buf->data = NULL;
//      buf->maxsize = 0;
	buf->cursize = 0;
}


void SZ_Clear (sizebuf_t *buf)
{
	buf->cursize = 0;
}


///////////////////////////////////////////////////////////////////////////
/// @brief	
/// @param	buf 
/// @param	length 
///////////////////////////////////////////////////////////////////////////
void *SZ_GetSpace (sizebuf_t *buf, int length)
{
	void *data;
	
	if (buf->cursize + length > buf->maxsize)
	{
		if (!buf->allowoverflow)
			Sys_Error ("SZ_GetSpace: overflow without allowoverflow set");
			
		if (length > buf->maxsize)
			Sys_Error ("SZ_GetSpace: %i is > full buffer size", length);
			
		buf->overflowed = true;
		Con_Printf ("SZ_GetSpace: overflow");
		SZ_Clear (buf);
	}
	
	data = buf->data + buf->cursize;
	buf->cursize += length;
	
	return data;
}


void SZ_Write (sizebuf_t *buf, void *data, int length)
{
	Q_memcpy (SZ_GetSpace (buf, length), data, length);
}


/// strcats onto the sizebuf
void SZ_Print (sizebuf_t *buf, char *data)
{
	int		len;
	
	len = Q_strlen (data) + 1;
	
	if (buf->data[buf->cursize-1])
		Q_memcpy ( (byte *) SZ_GetSpace (buf, len), data, len); // no trailing 0
	else
		Q_memcpy ( (byte *) SZ_GetSpace (buf, len - 1) - 1, data, len); // write over trailing 0
}


//============================================================================


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
/// @param	pathname	The path to skip (?)
/// @return	The last character (?)
///////////////////////////////////////////////////////////////////////////
char *COM_SkipPath (char *pathname)
{
	char    *last;
	
	last = pathname;
	while (*pathname)
	{
		if (*pathname == '/')
			last = pathname + 1;
		pathname++;
	}
	return last;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
/// @param	int	
/// @param	out	
///////////////////////////////////////////////////////////////////////////
void COM_StripExtension (char *in, char *out)
{
	while (*in && *in != '.')
		*out++ = *in++;
	*out = 0;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Search for the . in a filename and make return.
/// @param	in The string to search.
/// @return	An empty string, if no extension found
/// @return	A file extension as a string.
///////////////////////////////////////////////////////////////////////////
char *COM_FileExtension (char *in)
{
	static char	exten[8];
	int			i;
	
	while (*in && *in != '.')
		in++;
	if (!*in)
		return "";
	in++;
	for (i = 0 ; i < 7 && *in ; i++, in++)
		exten[i] = *in;
	exten[i] = 0;
	return exten;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
/// @param in	
/// @param out	
///////////////////////////////////////////////////////////////////////////
void COM_FileBase (char *in, char *out)
{
	char *s, *s2;
	
	s = in + strlen (in) - 1;
	
	while (s != in && *s != '.')
		s--;
		
	for (s2 = s ; *s2 && *s2 != '/' ; s2--)
		;
		
	if (s - s2 < 2)
		strcpy (out, "?model?");
	else
	{
		s--;
		strncpy (out, s2 + 1, s - s2);
		out[s-s2] = 0;
	}
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
/// @param	path		
/// @param	extension	
///////////////////////////////////////////////////////////////////////////
void COM_DefaultExtension (char *path, char *extension)
{
	char	*src;

	//
	// if path doesn't have a .EXT, append extension
	// (extension should include the .)
	//
	src = path + strlen (path) - 1;
	
	while (*src != '/' && src != path)
	{
		if (*src == '.')
			return;                 // it has an extension
		src--;
	}
	
	strcat (path, extension);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Parse a token out of a string. Parse the @e data string.
///			Used for reading .cfg files? Or maybe .q1 files?
/// @param	data	The string to parse
/// @return	Pointer to the parsed token
///////////////////////////////////////////////////////////////////////////
char *COM_Parse (char *data)
{
	int		c;
	int		len;
	
	len = 0;
	com_token[0] = 0;
	
	if (!data)
		return NULL;
		
	// skip whitespace
	// skip all characters before the space char
	// ! WARNING ! EVIL GOTO ABOUND!
	skipwhite:
	while ((c = *data) <= ' ')
	{
		if (c == 0)
			return NULL;                    // end of file;
		data++;
	}
	
	// skip // comments
	if (c == '/' && data[1] == '/')
	{
		while (*data && *data != '\n')
			data++;
		goto skipwhite;
	}
	
	
	// handle quoted strings specially
	if (c == '\"')
	{
		data++;
		while (1)
		{
			c = *data++;
			if (c == '\"' || !c)
			{
				com_token[len] = 0;
				return data;
			}
			com_token[len] = c;
			len++;
		}
	}
	
	// parse single characters
	if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':')
	{
		com_token[len] = c;
		len++;
		com_token[len] = 0;
		return data + 1;
	}
	
	// parse a regular word
	do
	{
		com_token[len] = c;
		data++;
		len++;
		c = *data;
		if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':')
			break;
	}
	while (c > 32);
	
	com_token[len] = 0;
	return data;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Returns the position (1 to argc-1) in the program's argument
///			list where the given parameter appears, or 0 if not present
///	@param	parm The parameter to check for.
///	@return	The nth counted argument. 0 if not found
///////////////////////////////////////////////////////////////////////////
int COM_CheckParm (char *parm)
{
	for (int i = 1; i < com_argc; i++)
	{
		if (!com_argv[i])
			continue;               // NEXTSTEP sometimes clears appkit vars.
		if (!Q_strcmp (parm, com_argv[i]))
			return i;
	}
	
	return 0;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Looks for the pop.txt file and verifies it. Sets the
///			"registered" cvar. Immediately exits out if an alternate game
///			was attempted to be started without being registered.
///////////////////////////////////////////////////////////////////////////
void COM_CheckRegistered (void)
{
	int h;
	unsigned short check[128];
	int i;
	
	COM_OpenFile ("gfx/pop.lmp", &h);
	static_registered = 0;
	
	if (h == -1)
	{
#if WINDED
		Sys_Error ("This dedicated server requires a full registered copy of Quake");
#endif
		Con_Printf ("Playing shareware version.\n");
		if (com_modified)
			Sys_Error ("You must have the registered version to use modified games");
		return;
	}
	
	Sys_FileRead (h, check, sizeof (check));
	COM_CloseFile (h);
	
	for (i = 0 ; i < 128 ; i++)
		if (pop[i] != (unsigned short) BigShort (check[i]))
			Sys_Error ("Corrupted data file.");
			
	Cvar_Set ("cmdline", com_cmdline);
	Cvar_Set ("registered", "1");
	static_registered = 1;
	Con_Printf ("Playing registered version.\n");
}


void COM_Path_f (void);


//////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Copy the argument count and vector into com_cmdline
///			and into largv
/// @param	argc Argument count. The amount of arguments that have been
///			passed to the program.
/// @param	argv Argument vector. The pointer to a list of strings that
///			hold the actual arguments.
///	@return void
//////////////////////////////////////////////////////////////////////////
void COM_InitArgv (int argc, char **argv)
{
	bool	safe;
	int		i, j, n;
	
	// reconstitute the command line for the cmdline externally visible cvar
	n = 0;
	
	// Does this create an argv that is space separated?
	for (j = 0; (j < MAX_NUM_ARGVS) && (j < argc); j++)
	{
		i = 0;
		
		while ((n < (CMDLINE_LENGTH - 1)) && argv[j][i])
		{
			com_cmdline[n++] = argv[j][i++];
		}
		
		if (n < (CMDLINE_LENGTH - 1))
			com_cmdline[n++] = ' ';
		else
			break;
	}
	
	com_cmdline[n] = 0;
	
	safe = false;
	
	// check if the -safe parameter has been given
	// if so, set @b safe to true
	for (com_argc = 0; (com_argc < MAX_NUM_ARGVS) && (com_argc < argc); com_argc++)
	{
		largv[com_argc] = argv[com_argc];
		if (!Q_strcmp ("-safe", argv[com_argc]))
			safe = true;
	}
	
	if (safe)
	{
		// force all the safe-mode switches. Note that we reserved extra space in
		// case we need to add these, so we don't need an overflow check
		for (i = 0 ; i < NUM_SAFE_ARGVS ; i++)
		{
			largv[com_argc] = safeargvs[i];
			com_argc++;
		}
	}
	
	largv[com_argc] = argvdummy;
	com_argv = largv;
	
	if (COM_CheckParm ("-rogue"))
	{
		rogue = true;
		standard_quake = false;
	}
	
	if (COM_CheckParm ("-hipnotic"))
	{
		hipnotic = true;
		standard_quake = false;
	}
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Command initialize. 
/// @param	basedir
///////////////////////////////////////////////////////////////////////////
void COM_Init (char *basedir)
{
	byte	swaptest[2] = {1, 0};
	
	// set the byte swapping variables in a portable manner
	if (*(short *) swaptest == 1)
	{
		bigendien = false;
		BigShort = ShortSwap;
		LittleShort = ShortNoSwap;
		BigLong = LongSwap;
		LittleLong = LongNoSwap;
		BigFloat = FloatSwap;
		LittleFloat = FloatNoSwap;
	}
	else
	{
		bigendien = true;
		BigShort = ShortNoSwap;
		LittleShort = ShortSwap;
		BigLong = LongNoSwap;
		LittleLong = LongSwap;
		BigFloat = FloatNoSwap;
		LittleFloat = FloatSwap;
	}
	
	Cvar_RegisterVariable (&registered);
	Cvar_RegisterVariable (&cmdline);
	Cmd_AddCommand ("path", COM_Path_f);
	
	COM_InitFilesystem ();
	COM_CheckRegistered ();
}


///////////////////////////////////////////////////////////////////////////
/// @brief	Does a varargs printf into a temp buffer, so I don't need to
///			have varargs versions of all text functions.
/// @todo	Make this buffer size safe someday.
///////////////////////////////////////////////////////////////////////////
char *va (char *format, ...)
{
	va_list     argptr;
	static char string[1024];
	
	va_start (argptr, format);
	vsprintf (string, format, argptr);
	va_end (argptr);
	
	return string;
}


/// just for debugging
int memsearch (byte *start, int count, int search)
{
	int i;
	
	for (i = 0 ; i < count ; i++)
		if (start[i] == search)
			return i;

	return -1;
}

/*
=============================================================================

QUAKE FILESYSTEM

=============================================================================
*/

int com_filesize;


//
// in memory
//

typedef struct
{
	char    name[MAX_QPATH];
	int             filepos, filelen;
} packfile_t;

typedef struct pack_s
{
	char    filename[MAX_OSPATH];
	int             handle;
	int             numfiles;
	packfile_t      *files;
} pack_t;

//
// on disk
//
typedef struct
{
	char    name[56];
	int             filepos, filelen;
} dpackfile_t;

typedef struct
{
	char    id[4];
	int             dirofs;
	int             dirlen;
} dpackheader_t;

#define MAX_FILES_IN_PACK       2048

char    com_cachedir[MAX_OSPATH];
char    com_gamedir[MAX_OSPATH];

///////////////////////////////////////////////////////////////////////////
/// @brief	Linked list, 
///////////////////////////////////////////////////////////////////////////
typedef struct searchpath_s
{
	char    filename[MAX_OSPATH];		///< 
	pack_t  *pack;						///< Only one of filename / pack will be used.
	struct searchpath_s *next;			///< 
} searchpath_t;

searchpath_t    *com_searchpaths;


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
void COM_Path_f (void)
{
	searchpath_t *s;
	
	Con_Printf ("Current search path:\n");
	for (s = com_searchpaths ; s ; s = s->next)
	{
		if (s->pack)
			Con_Printf ("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
		else
			Con_Printf ("%s\n", s->filename);
	}
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	The filename will be prefixed by the current game directory
///////////////////////////////////////////////////////////////////////////
void COM_WriteFile (char *filename, void *data, int len)
{
	int             handle;
	char    name[MAX_OSPATH];
	
	sprintf (name, "%s/%s", com_gamedir, filename);
	
	handle = Sys_FileOpenWrite (name);
	if (handle == -1)
	{
		Sys_Printf ("COM_WriteFile: failed on %s\n", name);
		return;
	}
	
	Sys_Printf ("COM_WriteFile: %s\n", name);
	Sys_FileWrite (handle, data, len);
	Sys_FileClose (handle);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Only used for CopyFile
///////////////////////////////////////////////////////////////////////////
void    COM_CreatePath (char *path)
{
	char    *ofs;
	
	for (ofs = path + 1 ; *ofs ; ofs++)
	{
		if (*ofs == '/')
		{
			// create the directory
			*ofs = 0;
			Sys_mkdir (path);
			*ofs = '/';
		}
	}
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief Copy file from a network path to local path
/// 
/// Copies a file over from the net to the local cache, creating any
/// directories needed. This is for the convenience of developers using
/// ISDN from home.
/// @param netpath File to copy
/// @param cachepath Path to copy @b netpath to
///////////////////////////////////////////////////////////////////////////
void COM_CopyFile (char *netpath, char *cachepath)
{
	int  in, out;
	int  remaining, count;
	char buf[4096];
	
	remaining = Sys_FileOpenRead (netpath, &in);
	COM_CreatePath (cachepath);     // create directories up to the cache file
	out = Sys_FileOpenWrite (cachepath);
	
	while (remaining)
	{
		if (remaining < sizeof (buf))
			count = remaining;
		else
			count = sizeof (buf);
		Sys_FileRead (in, buf, count);
		Sys_FileWrite (out, buf, count);
		remaining -= count;
	}
	
	Sys_FileClose (in);
	Sys_FileClose (out);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Finds the file in the search path.
///			Sets com_filesize and one of handle or file
///	@param	filename The file to find.
///	@param	handle The handle to attach the found file to.
///	@param	file 
///////////////////////////////////////////////////////////////////////////
int COM_FindFile (char *filename, int *handle, FILE **file)
{
	searchpath_t *search;
	char   netpath[MAX_OSPATH];
	char   cachepath[MAX_OSPATH];
	pack_t *pak;
	int    i;
	int    findtime, cachetime;
	
	if (file && handle)
		Sys_Error ("COM_FindFile: both handle and file set");
	if (!file && !handle)
		Sys_Error ("COM_FindFile: neither handle or file set");
		
	//
	// Search through the path, one element at a time
	//
	search = com_searchpaths;
	if (proghack)
	{
		// gross hack to use quake 1 progs with quake 2 maps
		if (!strcmp (filename, "progs.dat"))
			search = search->next;
	}
	
	for (; search ; search = search->next)
	{
		// is the element a pak file?
		if (search->pack)
		{
			// look through all the pak file elements
			pak = search->pack;
			for (i = 0 ; i < pak->numfiles ; i++)
			{
				if (!strcmp (pak->files[i].name, filename))
				{
					// found it!
					Sys_Printf ("PackFile: %s : %s\n", pak->filename, filename);
					if (handle)
					{
						*handle = pak->handle;
						Sys_FileSeek (pak->handle, pak->files[i].filepos);
					}
					else
					{
						// open a new file on the pakfile
						*file = fopen (pak->filename, "rb");
						if (*file)
							fseek (*file, pak->files[i].filepos, SEEK_SET);
					}
					com_filesize = pak->files[i].filelen;
					return com_filesize;
				}
			}
		}
		else
		{
			// check a file in the directory tree
			if (!static_registered)
			{
				// if not a registered version, don't ever go beyond base
				if (strchr (filename, '/') || strchr (filename, '\\'))
					continue;
			}
			
			sprintf (netpath, "%s/%s", search->filename, filename);
			
			findtime = Sys_FileTime (netpath);
			if (findtime == -1)
				continue;
				
			// see if the file needs to be updated in the cache
			if (!com_cachedir[0])
				strcpy (cachepath, netpath);
			else
			{
#if defined(_WIN32)
				if ( (strlen (netpath) < 2) || (netpath[1] != ':'))
					sprintf (cachepath, "%s%s", com_cachedir, netpath);
				else
					sprintf (cachepath, "%s%s", com_cachedir, netpath + 2);
#else
				sprintf (cachepath, "%s%s", com_cachedir, netpath);
#endif
					
				cachetime = Sys_FileTime (cachepath);
				
				if (cachetime < findtime)
					COM_CopyFile (netpath, cachepath);
				strcpy (netpath, cachepath);
			}
			
			Sys_Printf ("FindFile: %s\n", netpath);
			com_filesize = Sys_FileOpenRead (netpath, &i);
			if (handle)
				*handle = i;
			else
			{
				Sys_FileClose (i);
				*file = fopen (netpath, "rb");
			}
			return com_filesize;
		}
		
	}
	
	Sys_Printf ("FindFile: can't find %s\n", filename);
	
	if (handle)
		*handle = -1;
	else
		*file = NULL;
	com_filesize = -1;
	return -1;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	@b Filename never has a leading slash, but may contain
///			directory walks returns a handle and a length it may actually
///			be inside a pak file.
///	@param	filename File to open.
///	@param	handle Handle to attach the file to.
///////////////////////////////////////////////////////////////////////////
int COM_OpenFile (char *filename, int *handle)
{
	return COM_FindFile (filename, handle, NULL);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	If the requested file is inside a packfile, a new FILE * will
/// be opened into the file.
///////////////////////////////////////////////////////////////////////////
int COM_FOpenFile (char *filename, FILE **file)
{
	return COM_FindFile (filename, NULL, file);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	If it is a pak file handle, don't really close it
///////////////////////////////////////////////////////////////////////////
void COM_CloseFile (int h)
{
	searchpath_t	*s;
	
	for (s = com_searchpaths ; s ; s = s->next)
		if (s->pack && s->pack->handle == h)
			return;
			
	Sys_FileClose (h);
}


cache_user_t *loadcache;
byte	*loadbuf;
int		loadsize;

///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Filename are relative to the quake directory.
///			Always appends a 0 byte.
///	@param	path 
///	@param	usehunk 
///	@return	
///////////////////////////////////////////////////////////////////////////
byte *COM_LoadFile (char *path, int usehunk)
{
	int  h;
	byte *buf;
	char base[32];
	int  len;
	
	buf = NULL;     // quiet compiler warning
	
	// look for it in the filesystem or pack files
	len = COM_OpenFile (path, &h);
	if (h == -1)
		return NULL;
		
	// extract the filename base name for hunk tag
	COM_FileBase (path, base);
	
	if (usehunk == 1)
		buf = Hunk_AllocName (len + 1, base);
	else if (usehunk == 2)
		buf = Hunk_TempAlloc (len + 1);
	else if (usehunk == 0)
		buf = Z_Malloc (len + 1);
	else if (usehunk == 3)
		buf = Cache_Alloc (loadcache, len + 1, base);
	else if (usehunk == 4)
	{
		if (len + 1 > loadsize)
			buf = Hunk_TempAlloc (len + 1);
		else
			buf = loadbuf;
	}
	else
		Sys_Error ("COM_LoadFile: bad usehunk");
		
	if (!buf)
		Sys_Error ("COM_LoadFile: not enough space for %s", path);
		
	( (byte *) buf) [len] = 0;
	
	Draw_BeginDisc ();
	Sys_FileRead (h, buf, len);
	COM_CloseFile (h);
	Draw_EndDisc ();
	
	return buf;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
///////////////////////////////////////////////////////////////////////////
byte *COM_LoadHunkFile (char *path)
{
	return COM_LoadFile (path, 1);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
/// @param	path
/// @return	
///////////////////////////////////////////////////////////////////////////
byte *COM_LoadTempFile (char *path)
{
	return COM_LoadFile (path, 2);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	
/// @param path
/// @param cache_user_s
///////////////////////////////////////////////////////////////////////////
void COM_LoadCacheFile (char *path, struct cache_user_s *cu)
{
	loadcache = cu;
	COM_LoadFile (path, 3);
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	uses temp hunk if larger than bufsize
/// @param path
/// @param buffer
/// @param bufsize
///////////////////////////////////////////////////////////////////////////
byte *COM_LoadStackFile (char *path, void *buffer, int bufsize)
{
	byte    *buf;
	
	loadbuf = (byte *) buffer;
	loadsize = bufsize;
	buf = COM_LoadFile (path, 4);
	
	return buf;
}

///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Takes an explicit (not game tree related) path to a pak file.
/// 
/// Loads the header and directory, adding the files at the beginning
/// of the list so they override previous pack files.
/// @param	packfile
/// @return	
///////////////////////////////////////////////////////////////////////////
pack_t *COM_LoadPackFile (char *packfile)
{
	dpackheader_t   header;
	int                             i;
	packfile_t              *newfiles;
	int                             numpackfiles;
	pack_t                  *pack;
	int                             packhandle;
	dpackfile_t             info[MAX_FILES_IN_PACK];
	unsigned short          crc;
	
	if (Sys_FileOpenRead (packfile, &packhandle) == -1)
	{
//              Con_Printf ("Couldn't open %s\n", packfile);
		return NULL;
	}
	Sys_FileRead (packhandle, (void *) &header, sizeof (header));
	if (header.id[0] != 'P' || header.id[1] != 'A'
			|| header.id[2] != 'C' || header.id[3] != 'K')
		Sys_Error ("%s is not a packfile", packfile);
	header.dirofs = LittleLong (header.dirofs);
	header.dirlen = LittleLong (header.dirlen);
	
	numpackfiles = header.dirlen / sizeof (dpackfile_t);
	
	if (numpackfiles > MAX_FILES_IN_PACK)
		Sys_Error ("%s has %i files", packfile, numpackfiles);
		
	if (numpackfiles != PAK0_COUNT)
		com_modified = true;    // not the original file
		
	newfiles = Hunk_AllocName (numpackfiles * sizeof (packfile_t), "packfile");
	
	Sys_FileSeek (packhandle, header.dirofs);
	Sys_FileRead (packhandle, (void *) info, header.dirlen);
	
// crc the directory to check for modifications
	CRC_Init (&crc);
	for (i = 0 ; i < header.dirlen ; i++)
		CRC_ProcessByte (&crc, ( (byte *) info) [i]);
	if (crc != PAK0_CRC)
		com_modified = true;
		
// parse the directory
	for (i = 0 ; i < numpackfiles ; i++)
	{
		strcpy (newfiles[i].name, info[i].name);
		newfiles[i].filepos = LittleLong (info[i].filepos);
		newfiles[i].filelen = LittleLong (info[i].filelen);
	}
	
	pack = Hunk_Alloc (sizeof (pack_t));
	strcpy (pack->filename, packfile);
	pack->handle = packhandle;
	pack->numfiles = numpackfiles;
	pack->files = newfiles;
	
	Con_Printf ("Added packfile %s (%i files)\n", packfile, numpackfiles);
	return pack;
}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Sets com_gamedir, adds the directory to the head of the path,
///			then loads and adds pak1.pak pak2.pak ...
///////////////////////////////////////////////////////////////////////////
void COM_AddGameDirectory (char *dir)
{
	int                             i;
	searchpath_t    *search;
	pack_t                  *pak;
	char                    pakfile[MAX_OSPATH];
	
	strcpy (com_gamedir, dir);
	
	//
	// add the directory to the search path
	//
	search = Hunk_Alloc (sizeof (searchpath_t));
	strcpy (search->filename, dir);
	search->next = com_searchpaths;
	com_searchpaths = search;
	
	//
	// add any pak files in the format pak0.pak pak1.pak, ...
	//
	for (i = 0 ; ; i++)
	{
		sprintf (pakfile, "%s/pak%i.pak", dir, i);
		pak = COM_LoadPackFile (pakfile);
		if (!pak)
			break;
		search = Hunk_Alloc (sizeof (searchpath_t));
		search->pack = pak;
		search->next = com_searchpaths;
		com_searchpaths = search;
	}
	
	//
	// add the contents of the parms.txt file to the end of the command line
	//

}


///////////////////////////////////////////////////////////////////////////
/// @ingroup common_functions
/// @brief	Initialize the file system
///////////////////////////////////////////////////////////////////////////
void COM_InitFilesystem (void)
{
	int  i, j;
	char basedir[MAX_OSPATH];
	searchpath_t *search;
	
	//
	// -basedir <path>
	// Overrides the system supplied base directory (under GAMENAME)
	//
	i = COM_CheckParm ("-basedir");
	if (i && i < com_argc - 1)
		strcpy (basedir, com_argv[i+1]);
	else
		strcpy (basedir, host_parms.basedir);
		
	j = strlen (basedir);
	
	if (j > 0)
	{
		if ((basedir[j - 1] == '\\') || (basedir[j - 1] == '/'))
			basedir[j - 1] = 0;
	}
	
	//
	// -cachedir <path>
	// Overrides the system supplied cache directory (NULL or /qcache)
	// -cachedir - will disable caching.
	//
	i = COM_CheckParm ("-cachedir");
	if (i && i < com_argc - 1)
	{
		if (com_argv[i+1][0] == '-')
			com_cachedir[0] = 0;
		else
			strcpy (com_cachedir, com_argv[i+1]);
	}
	else if (host_parms.cachedir)
		strcpy (com_cachedir, host_parms.cachedir);
	else
		com_cachedir[0] = 0;
		
	//
	// start up with GAMENAME by default (id1)
	//
	COM_AddGameDirectory (va ("%s/"GAMENAME, basedir));
	
	if (COM_CheckParm ("-rogue"))
		COM_AddGameDirectory (va ("%s/rogue", basedir));
	if (COM_CheckParm ("-hipnotic"))
		COM_AddGameDirectory (va ("%s/hipnotic", basedir));
		
	//
	// -game <gamedir>
	// Adds basedir/gamedir as an override game
	//
	i = COM_CheckParm ("-game");
	if (i && i < com_argc - 1)
	{
		com_modified = true;
		COM_AddGameDirectory (va ("%s/%s", basedir, com_argv[i+1]));
	}
	
	//
	// -path <dir or packfile> [<dir or packfile>] ...
	// Fully specifies the exact search path, overriding the generated one
	//
	i = COM_CheckParm ("-path");
	if (i)
	{
		com_modified = true;
		com_searchpaths = NULL;
		while (++i < com_argc)
		{
			if (!com_argv[i] || com_argv[i][0] == '+' || com_argv[i][0] == '-')
				break;
				
			search = Hunk_Alloc (sizeof (searchpath_t));
			if (!strcmp (COM_FileExtension (com_argv[i]), "pak"))
			{
				search->pack = COM_LoadPackFile (com_argv[i]);
				if (!search->pack)
					Sys_Error ("Couldn't load packfile: %s", com_argv[i]);
			}
			else
				strcpy (search->filename, com_argv[i]);
			search->next = com_searchpaths;
			com_searchpaths = search;
		}
	}
	
	if (COM_CheckParm ("-proghack"))
		proghack = true;
}

