/*
Copyright (C) 1997-2001 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.

*/
// common.c -- misc functions used in client and server
#include "qcommon.h"
#include "version.h"
#include <setjmp.h>
#include <zlib.h>

#define COM_LOGFILE_NAME	"qconsole.log"
#define COM_CONFIG_NAME		"q2config.cfg"
#define COM_DEFAULTCFG_NAME		"default.cfg"
#define COM_AUTOEXECCFG_NAME		"autoexec.cfg"

commonAPI_t	com;

static jmp_buf abortframe;		// an ERR_DROP occured, exit the entire frame

static char		com_errorMsg[MAXPRINTMSG];

cvar_t	*host_speeds;
cvar_t	*developer;
cvar_t	*timescale;
cvar_t	*fixedtime;
cvar_t	*dedicated;

cvar_t	*logfile_active;	// 1 = create new, 2 = append to existing
cvar_t	*logfile_flush;		// 1 = flush after each print
cvar_t	*logfile_name;

cvar_t	*sv_running;
cvar_t	*sv_paused;
cvar_t	*cl_running;
cvar_t	*cl_paused;
cvar_t	*com_timedemo;
cvar_t	*com_sleep;
cvar_t	*com_date_format;
cvar_t	*com_time_format;
cvar_t	*com_debug_break;

//
// userinfo
//
cvar_t	*info_password;
cvar_t	*info_spectator;
cvar_t	*info_name;
cvar_t	*info_skin;
cvar_t	*info_rate;
cvar_t	*info_fov;
cvar_t	*info_msg;
cvar_t	*info_hand;
cvar_t	*info_gender;

fileHandle_t	com_logFile;
uint32		com_framenum;
uint32		com_eventTime;

// host_speeds times
int		time_before_game;
int		time_after_game;
int		time_before_ref;
int		time_after_ref;

void Con_Init( void );
void Prompt_Init( void );
void SCR_EndLoadingPlaque( void );

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

CLIENT / SERVER interactions

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

static int	rd_target;
static char	*rd_buffer;
static int	rd_buffersize;
static void	(*rd_flush)(int target, char *buffer);

void Com_BeginRedirect (int target, char *buffer, int buffersize, void (*flush))
{
	if (!target || !buffer || !buffersize || !flush)
		return;
	rd_target = target;
	rd_buffer = buffer;
	rd_buffersize = buffersize;
	rd_flush = flush;

	*rd_buffer = 0;
}

void Com_EndRedirect (void)
{
	rd_flush(rd_target, rd_buffer);

	rd_target = 0;
	rd_buffer = NULL;
	rd_buffersize = 0;
	rd_flush = NULL;
}

static void LogFile_Close( void ) {
	if( !com_logFile ) {
		return;
	}

	Com_Printf( "Closing %s\n", FS_GetFileName( com_logFile ) );

	FS_FCloseFile( com_logFile );
	com_logFile = 0;
}

static void LogFile_Open( void ) {
	uint32		mode;

	mode = logfile_active->integer > 1 ? FS_MODE_APPEND : FS_MODE_WRITE;

	if( logfile_flush->integer ) {
		mode |= FS_FLUSH_SYNC;
	}

	FS_FOpenFile( logfile_name->string, &com_logFile, mode );

	if( !com_logFile ) {
		Com_WPrintf( "Couldn't open %s\n", logfile_name->string );
		Cvar_SetInteger( "logfile", 0 );
		return;
	}

	Com_Printf( "Logging console to %s\n", logfile_name->string );
}

static void LogFileActive_OnChange( cvar_t *self, void *arg ) {
	if( !self->integer ) {
		LogFile_Close();
	} else {
		LogFile_Open();
	}	
}

static void LogFileParam_OnChange( cvar_t *self, void *arg ) {
	if( logfile_active->integer ) {
		LogFile_Close();
		LogFile_Open();
	}	
}

static void LogFile_Output( const char *string ) {
	char text[MAXPRINTMSG];
	char *p;
	int length;

	/* not checking for overflow - buffer size is
	 * large enough, and length may only decrease.
	 */
	p = text;
	while( *string ) {
		if( Q_IsColorString( string ) ) {
			string += 2;
			continue;
		}
		
		*p++ = *string++;
	}
	*p = 0;

	length = p - text;
	FS_Write( text, length, com_logFile );
}

/*
=============
Com_Printf

Both client and server can use this, and it will output
to the apropriate place.
=============
*/
void Com_Printf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];
	static int	recursive;
	int			length;

	if( recursive == 2 ) {
		return;
	}

	recursive++;

	va_start( argptr, fmt );
	length = Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );

	if( rd_target ) {
		if( length + strlen( rd_buffer ) > rd_buffersize - 1 ) {
			rd_flush( rd_target, rd_buffer );
			*rd_buffer = 0;
		}
		Q_strcat( rd_buffer, rd_buffersize, msg );
	} else {
		Con_Print( msg );
			
		// also echo to debugging console
		Sys_ConsoleOutput( msg );

		// logfile
		if( com_logFile ) {
			LogFile_Output( msg );
		}
	}

	recursive--;
		
}


/*
================
Com_DPrintf

A Com_Printf that only shows up if the "developer" cvar is set
================
*/
void Com_DPrintf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];
		
	if( !developer || !developer->integer )
		return;			// don't confuse non-developers with techie stuff...

	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );
	
	Com_Printf( S_COLOR_BLUE"%s", msg );
}

/*
================
Com_WPrintf

================
*/
void Com_WPrintf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );
	
	Com_Printf( S_COLOR_YELLOW"WARNING: %s", msg );
}

/*
================
Com_EPrintf

================
*/
void Com_EPrintf( const char *fmt, ... ) {
	va_list		argptr;
	char		msg[MAXPRINTMSG];

	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );
	
	Com_Printf( S_COLOR_RED"ERROR: %s", msg );
}


/*
=============
Com_Error

Both client and server can use this, and it will
do the apropriate things.
=============
*/
void Com_Error( comErrorType_t code, const char *fmt, ... ) {
	va_list		argptr;
	static	qboolean	recursive;

	if( recursive ) {
#ifndef NDEBUG
		Sys_DebugBreak();
#endif
		Sys_Error( "recursive error after: %s", com_errorMsg );
	}
	recursive = qtrue;

	va_start( argptr, fmt );
	Q_vsnprintf( com_errorMsg, sizeof( com_errorMsg ), fmt, argptr );
	va_end( argptr );

    /* fix up drity message buffers */
    MSG_Init(); 
	
	if( code == ERR_DISCONNECT || code == ERR_SILENT ) {
		SV_Shutdown( va( "Server was killed: %s", com_errorMsg ),
                KILL_DISCONNECT );
		CL_Disconnect( code, com_errorMsg );
		Com_Printf( S_COLOR_YELLOW "%s\n", com_errorMsg );
		recursive = qfalse;
		longjmp( abortframe, -1 );
	}

	if( com_debug_break && com_debug_break->integer ) {
		Sys_DebugBreak();
	}

	if( code == ERR_DROP ) {
		Com_Printf( S_COLOR_RED "********************\n"
                                "ERROR: %s\n"
                                "********************\n", com_errorMsg );
		SV_Shutdown( va( "Server crashed: %s\n", com_errorMsg ), KILL_DROP );
		CL_Disconnect( ERR_DROP, com_errorMsg );
		recursive = qfalse;
		longjmp( abortframe, -1 );
	}

	if( com_logFile ) {
		FS_FPrintf( com_logFile, "FATAL: %s\n", com_errorMsg );
	}

	SV_Shutdown( va( "Server fatal crashed: %s\n", com_errorMsg ), KILL_DROP );
	CL_Shutdown();
	Qcommon_Shutdown( qtrue );

	Sys_Error( "%s", com_errorMsg );
}

/*
===================
Com_LevelPrint
===================
*/
void Com_LevelPrint( comPrintType_t type, const char *str ) {
	switch( type ) {
	case PRINT_DEVELOPER:
		Com_DPrintf( "%s", str );
		break;
	case PRINT_WARNING:
		Com_WPrintf( "%s", str );
		break;
	case PRINT_ERROR:
		Com_EPrintf( "%s", str );
		break;
	default:
		Com_Printf( "%s", str );
		break;
	}
}

/*
===================
Com_LevelError
===================
*/
void Com_LevelError( comErrorType_t code, const char *str ) {
	Com_Error( code, "%s", str );
}

/*
=============
Com_Quit

Both client and server can use this, and it will
do the apropriate things.
=============
*/
void Com_Quit( void ) {
	SV_Shutdown( "Server quit\n", KILL_DROP );
	CL_Shutdown();
	Qcommon_Shutdown( qfalse );

	Sys_Quit();
}


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

#ifndef DEDICATED_ONLY

/*
===============
Com_WriteConfiguration

Writes key bindings and archived cvars to config.cfg
===============
*/
static qboolean Com_WriteConfiguration( const char *path ) {
	fileHandle_t f;

	FS_FOpenFile( path, &f, FS_MODE_WRITE );
	if( !f ) {
		Com_WPrintf( "Couldn't write %s\n", path );
		return qfalse;
	}

	FS_FPrintf( f, "// generated by q2pro, do not modify\n" );

	Key_WriteBindings( f );
	Cvar_WriteVariables( f );
	
	FS_FCloseFile( f );

	return qtrue;
}

/*
===============
Com_WriteConfig_f
===============
*/
static void Com_WriteConfig_f( void ) {
	char buffer[MAX_QPATH];

	if( Cmd_Argc() > 2 ) {
		Com_Printf( "Usage: %s [cfgfile]\n", Cmd_Argv( 0 ) );
		return;
	}

	if( Cmd_Argc() < 2 ) {
		strcpy( buffer, COM_CONFIG_NAME );
	} else {
		Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) );
		COM_DefaultExtension( buffer, ".cfg", sizeof( buffer ) );
	}

	if( Com_WriteConfiguration( buffer ) ) {
		Com_Printf( "Wrote %s\n", buffer );
	}
}

#endif

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

						ZONE MEMORY ALLOCATION

just cleared malloc with counters now...

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

#define	Z_MAGIC		0x1d0d
#define	Z_TAIL		0x5b7b

typedef struct zhead_s {
	uint16	magic;
	uint16	tag;			// for group free
	size_t	size;
	struct zhead_s	*prev, *next;
} zhead_t;

static zhead_t		z_chain;

typedef struct zstatic_s {
	zhead_t	z;
	char	data[2]; /* !!make sure 'tail' field is aligned properly */
	uint16	tail;
} zstatic_t;

static zstatic_t		z_static[] = {
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '0', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '1', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '2', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '3', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '4', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '5', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '6', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '7', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '8', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '9', '\0' }, Z_TAIL },
	{ { Z_MAGIC, TAG_STATIC, sizeof( zstatic_t ) }, { '\0'		}, Z_TAIL },
};

typedef struct zstats_s {
	size_t  count;
	size_t	bytes;
	char	*name;
} zstats_t;

static zstats_t		z_stats[TAG_MAX];

void Z_Check( void ) {
	zhead_t	*z;

	for( z = z_chain.next; z != &z_chain; z = z->next ) {
		if( z->magic != Z_MAGIC ) {
			Com_Error( ERR_FATAL, "Z_Check: bad magic" );
        }

		if( *( uint16 * )( ( byte * )z + z->size - sizeof( uint16 ) ) !=
                Z_TAIL )
        {
			Com_Error( ERR_FATAL, "Z_Check: bad tail" );
        }

		if( z->tag == TAG_FREE ) {
			Com_Error( ERR_FATAL, "Z_Check: bad tag" );
        }
	}
}

void Z_LeakTest( memtag_t tag ) {
	zhead_t	*z;
	zstats_t *s;
	size_t numLeaks, numBytes;
	
	numLeaks = numBytes = 0;
	for( z = z_chain.next; z != &z_chain; z = z->next ) {
		if( z->magic != Z_MAGIC )
			Com_Error( ERR_FATAL, "Z_LeakTest: bad magic" );

		if( *( uint16 * )( ( byte * )z + z->size - sizeof( uint16 ) ) !=
                Z_TAIL )
        {
			Com_Error( ERR_FATAL, "Z_LeakTest: bad tail" );
        }

		if( z->tag == TAG_FREE ) {
			Com_Error( ERR_FATAL, "Z_LeakTest: bad tag" );
        }

		if( z->tag == tag ) {
			numLeaks++;
			numBytes += z->size;
		}
	}

	if( numLeaks ) {
		s = &z_stats[tag < TAG_MAX ? tag : TAG_FREE];
		Com_Printf( S_COLOR_YELLOW "************* Z_LeakTest *************\n"
						           "%s leaked %u bytes of memory (%u object%s)\n"
						           "**************************************\n",
								   s->name, numBytes, numLeaks,
								   ( numLeaks % 10 ) == 1 ? "" : "s" );
	}
}

/*
========================
Z_Free
========================
*/
void Z_Free( void *ptr ) {
	zhead_t	*z;
	zstats_t *s;

	if( !ptr ) {
        return;
	}

	z = ( ( zhead_t * )ptr ) - 1;

	if( z->magic != Z_MAGIC ) {
		Com_Error( ERR_FATAL, "Z_Free: bad magic" );
    }

	if( *( uint16 * )( ( byte * )z + z->size - sizeof( uint16 ) ) != Z_TAIL ) {
		Com_Error( ERR_FATAL, "Z_Free: bad tail" );
    }

	if( z->tag == TAG_FREE ) {
		Com_Error( ERR_FATAL, "Z_Free: bad tag" );
    }

	s = &z_stats[z->tag < TAG_MAX ? z->tag : TAG_FREE];
	s->count--;
	s->bytes -= z->size;
    
	if( z->tag != TAG_STATIC ) {
    	z->prev->next = z->next;
	    z->next->prev = z->prev;

	    free( z );
    }
}

/*
========================
Z_Realloc
========================
*/
void *Z_Realloc( void *ptr, size_t size ) {
	zhead_t	*z;
	zstats_t *s;

	if( !ptr ) {
        return Z_Malloc( size );
	}

    if( !size ) {
        Z_Free( ptr );
        return NULL;
    }

	z = ( ( zhead_t * )ptr ) - 1;

	if( z->magic != Z_MAGIC ) {
		Com_Error( ERR_FATAL, "Z_Realloc: bad magic" );
    }

	if( *( uint16 * )( ( byte * )z + z->size - sizeof( uint16 ) ) != Z_TAIL ) {
		Com_Error( ERR_FATAL, "Z_Realloc: bad tail" );
    }

	if( z->tag == TAG_FREE ) {
		Com_Error( ERR_FATAL, "Z_Realloc: bad tag" );
    }

	if( z->tag == TAG_STATIC ) {
        Z_Free( ptr );
        return Z_Malloc( size );
    }

	s = &z_stats[z->tag < TAG_MAX ? z->tag : TAG_FREE];
	s->bytes -= z->size;

	size += sizeof( zhead_t ) + sizeof( uint16 );
	size = ( size + 3 ) & ~3;
    
    z = realloc( z, size );
    if( !z ) {
		Com_Error( ERR_FATAL, "Z_Realloc: couldn't reallocate %u bytes", size );
    }

	z->size = size;
    z->prev->next = z;
    z->next->prev = z;

	s->bytes += size;

	*( uint16 * )( ( byte * )z + size - sizeof( uint16 ) ) = Z_TAIL;

	return ( void * )( z + 1 );
}

/*
========================
Z_Stats_f
========================
*/
void Z_Stats_f( void ) {
	size_t z_bytes, z_count;
	zstats_t *s;
	int i;

	Com_Printf( "-----------------\n" );
	Com_Printf( "    bytes  blocks\n\n" );

	z_bytes = 0;
	z_count = 0;
	for( i = 0, s = z_stats; i < TAG_MAX; i++, s++ ) {
        if( !s->count ) {
            continue;
        }
            
		Com_Printf( "%9u  %6u  %s\n", s->bytes, s->count, s->name );
		z_bytes += s->bytes;
		z_count += s->count;
	}

	Com_Printf( "-----------------\n" );
	Com_Printf( "%9u  %6u  %s\n", z_bytes, z_count, "total" );
}

/*
========================
Z_FreeTags
========================
*/
void Z_FreeTags( memtag_t tag ) {
	zhead_t	*z, *next;

	for( z = z_chain.next; z != &z_chain; z = next ) {
		if( z->magic != Z_MAGIC ) {
			Com_Error( ERR_FATAL, "Z_FreeTags: bad magic" );
        }

		if( *( uint16 * )( ( byte * )z + z->size - sizeof( uint16 ) ) !=
                Z_TAIL )
        {
			Com_Error( ERR_FATAL, "Z_FreeTags: bad tail" );
        }

		if( z->tag == TAG_FREE ) {
			Com_Error( ERR_FATAL, "Z_FreeTags: bad tag" );
        }
		next = z->next;
		if( z->tag == tag ) {
			Z_Free( ( void * )( z + 1 ) );
		}
	}
}

/*
========================
Z_TagMalloc
========================
*/
void *Z_TagMalloc( size_t size, memtag_t tag ) {
	zhead_t	*z;
	zstats_t *s;

	if( !size ) {
		return NULL;
	}

	if( tag == TAG_FREE )
		Com_Error( ERR_FATAL, "Z_TagMalloc: bad tag" );
	
	size += sizeof( zhead_t ) + sizeof( uint16 );
	size = ( size + 3 ) & ~3;
	z = malloc( size );
	if( !z ) {
		Com_Error( ERR_FATAL, "Z_TagMalloc: couldn't allocate %u bytes", size );
    }
	z->magic = Z_MAGIC;
	z->tag = tag;
	z->size = size;

	z->next = z_chain.next;
	z->prev = &z_chain;
	z_chain.next->prev = z;
	z_chain.next = z;

	*( uint16 * )( ( byte * )z + size - sizeof( uint16 ) ) = Z_TAIL;

	s = &z_stats[tag < TAG_MAX ? tag : TAG_FREE];
	s->count++;
	s->bytes += size;

	return ( void * )( z + 1 );
}

void *Z_TagMallocz( size_t size, memtag_t tag ) {
    void *ptr;

    if( !size ) {
        return NULL;
    }

    ptr = Z_TagMalloc( size, tag );
    memset( ptr, 0, size );

    return ptr;
}

static byte *z_reserved;
static size_t z_reservedUnuse;
static size_t z_reservedTotal;

void Z_TagReserve( size_t size, memtag_t tag ) {
	z_reserved = Z_TagMalloc( size, tag );
	z_reservedTotal = size;
	z_reservedUnuse = 0;
}

void *Z_ReservedAlloc( size_t size ) {
	byte *ptr;

	if( z_reservedUnuse + size > z_reservedTotal ) {
		Com_Error( ERR_FATAL, "Z_ReservedAlloc: out of space" );
	}

	ptr = z_reserved + z_reservedUnuse;
	z_reservedUnuse += size;

	return ( void * )ptr;

}

char *Z_ReservedCopyString( const char *in ) {
	char	*out;
	int length;

	if( !in ) {
		return NULL;
	}

	length = strlen( in ) + 1;
	
	out = Z_ReservedAlloc( length );
	strcpy( out, in );

	return out;
}

/*
========================
Z_Init
========================
*/
void Z_Init( void ) {
	zstats_t *s;
	int i;
	static char *names[TAG_MAX] = {
		"game",
		"static",
		"generic",
		"cmd",
		"cvar",
		"filesystem",
		"renderer",
		"ui",
		"cgame",
        "server",
		"mvd",
        "sound",
		"cmodel",
        "lua"
	};

	z_chain.next = z_chain.prev = &z_chain;

	for( i = 0, s = z_stats; i < TAG_MAX; i++, s++ ) {
		s->name = names[i] ? names[i] : "unknown";
	}
}

/*
================
Z_TagCopyString
================
*/
char *Z_TagCopyString( const char *in, memtag_t tag ) {
	char	*out;
	int length;

	if( !in ) {
		return NULL;
	}

	length = strlen( in ) + 1;
	
	out = Z_TagMalloc( length, tag );
	strcpy( out, in );

	return out;
}

/*
================
Cvar_CopyString
================
*/
char *Cvar_CopyString( const char *in ) {
	char	*out;
	int length;
    zstatic_t *z;

	if( !in ) {
		return NULL;
	}

	if( !in[0] ) {
        z = &z_static[10];
        z_stats[TAG_STATIC].count++;
        z_stats[TAG_STATIC].bytes += z->z.size;
		return z->data;
	}

	if( !in[1] && Q_isdigit( in[0] ) ) {
        z = &z_static[ in[0] - '0' ];
        z_stats[TAG_STATIC].count++;
        z_stats[TAG_STATIC].bytes += z->z.size;
		return z->data;
	}

	length = strlen( in ) + 1;
	
	out = Z_TagMalloc( length, TAG_CVAR );
	strcpy( out, in );

	return out;
}

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

						INTERFACE TO ZLIB

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

static z_stream		com_zStream;
static sizebuf_t	*com_zOutputBuf;

#define ZLIB_WINDOW_BITS	( -15 )

/*
=============
Com_ZLibDeflateStart
=============
*/
qboolean Com_ZLibDeflateStart( sizebuf_t *outputBuf ) {
	memset( &com_zStream, 0, sizeof( com_zStream ) );
	com_zStream.next_out = outputBuf->data;
	com_zStream.avail_out = outputBuf->maxsize;

	if( deflateInit2( &com_zStream, Z_BEST_COMPRESSION, Z_DEFLATED,
                ZLIB_WINDOW_BITS, 9, Z_DEFAULT_STRATEGY ) != Z_OK )
    {
		return qfalse;
	}

	SZ_Clear( outputBuf );
	com_zOutputBuf = outputBuf;

	return qtrue;
}

/*
=============
Com_ZLibDeflateChunk
=============
*/
qboolean Com_ZLibDeflateChunk( sizebuf_t *inputBuf ) {
	com_zStream.next_in = inputBuf->data;
	com_zStream.avail_in = inputBuf->cursize;

	if( deflate( &com_zStream, Z_SYNC_FLUSH ) != Z_OK ) {
		return qfalse;
	}

	if( !com_zStream.avail_out ) {
		return qfalse;
	}

	com_zOutputBuf->cursize = com_zStream.total_out;

	return qtrue;
}

/*
=============
Com_ZLibDeflateAbort
=============
*/
void Com_ZLibDeflateAbort( void ) {
	deflateEnd( &com_zStream );
	com_zOutputBuf = NULL;
}

/*
=============
Com_ZLibDeflateFinish
=============
*/
qboolean Com_ZLibDeflateFinish( void ) {
	if( deflate( &com_zStream, Z_FINISH ) != Z_STREAM_END ) {
		Com_ZLibDeflateAbort();
		return qfalse;
	}

	com_zOutputBuf->cursize = com_zStream.total_out;
	com_zOutputBuf = NULL;

	if( deflateEnd( &com_zStream ) != Z_OK ) {
		return qfalse;
	}

	return qtrue;
}

/*
=============
Com_ZLibDeflateBuffer
=============
*/
qboolean Com_ZLibDeflateBuffer( sizebuf_t *inputBuf, sizebuf_t *outputBuf ) {
	z_stream	z;

	memset( &z, 0, sizeof( z ) );

	z.next_in = inputBuf->data;
	z.avail_in = inputBuf->cursize;

	SZ_Clear( outputBuf );
	z.next_out = outputBuf->data;
	z.avail_out = outputBuf->maxsize;

	if( deflateInit2( &z, Z_BEST_COMPRESSION, Z_DEFLATED, ZLIB_WINDOW_BITS, 9,
                Z_DEFAULT_STRATEGY ) != Z_OK )
    {
		return qfalse;
	}

	if( deflate( &z, Z_FINISH ) != Z_STREAM_END ) {
		deflateEnd( &z );
		return qfalse;
	}

	if( deflateEnd( &z ) != Z_OK ) {
		return qfalse;
	}

	outputBuf->cursize = z.total_out;

	return qtrue;
}

/*
=============
Com_ZLibInflateBuffer
=============
*/
qboolean Com_ZLibInflateBuffer( sizebuf_t *inputBuf, sizebuf_t *outputBuf,
        int length )
{
	z_stream	z;

	memset( &z, 0, sizeof( z ) );

	z.next_in = inputBuf->data + inputBuf->readcount;
	if( length ) {
		if( length < 0 || inputBuf->readcount + length > inputBuf->cursize ) {
			return qfalse;
		}
		z.avail_in = length;
	} else {
		z.avail_in = inputBuf->cursize - inputBuf->readcount;
	}

	SZ_Clear( outputBuf );
	z.next_out = outputBuf->data;
	z.avail_out = outputBuf->maxsize;

	if( inflateInit2( &z, ZLIB_WINDOW_BITS ) != Z_OK ) {
		return qfalse;
	}

	if( inflate( &z, Z_FINISH ) != Z_STREAM_END ) {
		inflateEnd( &z );
		return qfalse;
	}

	if( inflateEnd( &z ) != Z_OK ) {
		return qfalse;
	}

	outputBuf->cursize = z.total_out;

	return qtrue;
}

void MSG_ParseZPacket( void (*parsefunc)( void ) ) {
	sizebuf_t	decompressed, temp;
	byte		decompressed_data[MAX_MSGLEN];
	int			compressedLength, decompressedLength;

	if( msg_read.data != msg_read_buffer ) {
		Com_Error( ERR_DROP, "MSG_ParseZPacket: recursively entered" );
	}

	compressedLength = MSG_ReadShort();
	decompressedLength = MSG_ReadShort();

	SZ_Init( &decompressed, decompressed_data, sizeof( decompressed_data ) );
	if( !Com_ZLibInflateBuffer( &msg_read, &decompressed, compressedLength ) ||
		decompressed.cursize != decompressedLength )
	{
		Com_Error( ERR_DROP,
                "MSG_ParseZPacket: decompression of %d bytes into %d failed",
			        compressedLength, decompressedLength );
	}

	temp = msg_read;
	msg_read = decompressed;

	(*parsefunc)();

	msg_read = temp;
	msg_read.readcount += compressedLength;
}

#ifndef NDEBUG
/*
=============
Com_ZLibTest_f
=============
*/
static void Com_ZLibTest_f( void ) {
	sizebuf_t in, out;
	byte in_buf[1024], out_buf[1200];
	int i;
	int checksum;
	int time = Sys_Milliseconds();

	Com_Printf( "Testing ZLib on 1024 bytes of data...\n" );

	SZ_Init( &in, in_buf, sizeof( in_buf ) );
	SZ_Init( &out, out_buf, sizeof( out_buf ) );

	for( i = 0; i < sizeof( in_buf ); i++ ) {
		in_buf[i] = 'a' + ( rand() % ( 'z' - 'a' ) );
	}

	checksum = Com_BlockChecksum( in_buf, sizeof( in_buf ) );

	if( !Com_ZLibDeflateStart( &out ) ) {
		Com_Printf( "Com_ZLibDeflateStart() failed\n" );
		return;
	}

	in.cursize = 256;
	for( i = 0; i < sizeof( in_buf ) / 256; i++ ) {
		if( !Com_ZLibDeflateChunk( &in ) ) {
			Com_ZLibDeflateAbort();
			Com_Printf( "Com_ZLibDeflateChunk() failed\n" );
			return;
		}
		in.data += 256;
	}

	if( !Com_ZLibDeflateFinish() ) {
		Com_Printf( "Com_ZLibDeflateFinish() failed\n" );
		return;
	}

	Com_Printf( "Compressed to %i bytes\n", out.cursize );

	SZ_Init( &in, in_buf, sizeof( in_buf ) );
	memset( in_buf, 0, sizeof( in_buf ) );

	if( !Com_ZLibInflateBuffer( &out, &in, 0 ) ) {
		Com_Printf( "Com_ZLibInflateBuffer() failed\n" );
		return;
	}

	Com_Printf( "Decompressed to %i bytes\n", in.cursize );

	if( checksum != Com_BlockChecksum( in_buf, sizeof( in_buf ) ) ) {
		Com_Printf( "Checksum mismatch!\n" );
		return;
	}

	Com_Printf( "ZLib tested successfully (%i msec)\n",
            Sys_Milliseconds() - time );
}

#endif


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

						INIT / SHUTDOWN

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

/*
=============
Com_FillAPI
=============
*/
void Com_FillAPI( commonAPI_t *api ) {
	api->Print = Com_LevelPrint;
	api->Error = Com_LevelError;
	api->TagMalloc = Z_TagMalloc;
    api->Realloc = Z_Realloc;
	api->Free = Z_Free;
}

/*
=============
Com_Time_m
=============
*/
void Com_Time_m( char *buffer, int bufferSize ) {
#ifdef _WIN32_WCE
	buffer[0] = 0;
#else
	time_t	clock;
	struct tm	*localTime;

	time( &clock );
	localTime = localtime( &clock );

	strftime( buffer, bufferSize, com_time_format->string, localTime );
#endif
}

/*
=============
Com_Date_m
=============
*/
static void Com_Date_m( char *buffer, int bufferSize ) {
#ifdef _WIN32_WCE
	buffer[0] = 0;
#else
	time_t	clock;
	struct tm	*localTime;

	time( &clock );
	localTime = localtime( &clock );

	strftime( buffer, bufferSize, com_date_format->string, localTime );
#endif
}

static void Com_LastError_f( void ) {
	if( com_errorMsg[0] ) {
		Com_Printf( "%s\n", com_errorMsg );
	} else {
		Com_Printf( "No error.\n" );
	}
}

#ifndef NDEBUG

/*
=============
Com_Error_f

Just throw a fatal error to
test error shutdown procedures
=============
*/
void Com_Error_f( void ) {
	Com_Error( ERR_FATAL, "%s", Cmd_Argv( 1 ) );
}

void Com_ErrorDrop_f( void ) {
	Com_Error( ERR_DROP, "%s", Cmd_Argv( 1 ) );
}

void Com_Freeze_f( void ) {
	int seconds, time;

	if( Cmd_Argc() < 2 ) {
		Com_Printf( "Usage: %s <seconds>\n", Cmd_Argv( 0 ) );
		return;
	}

	seconds = atoi( Cmd_Argv( 1 ) );
	if( seconds < 1 ) {
		return;
	}

	time = Sys_Milliseconds() + seconds * 1000;
	while( Sys_Milliseconds() < time )
		;

}

void Com_Crash_f( void ) {
	*( uint32 * )0 = 0x123456;
}

static void Com_VsnprintfTest_f( void ) {
	char buffer[32];
	int ret;
	qboolean fail;

	/* test if returned length is valid, no overflow, large buffer */
	Com_Printf( "test 1\n" );
	fail = qfalse;
	memset( buffer, '@', sizeof( buffer ) );
	ret = Com_sprintf( buffer, sizeof( buffer ), "%s", "abcd" );
	if( ret != 4 ) {
		Com_Printf( "returned %d instead of %d\n", ret, 4 );
		fail = qtrue;
	}
	if( buffer[4] != 0 ) {
		Com_Printf( "buffer left unterminated\n" );
		fail = qtrue;
	}
	if( buffer[5] != '@' ) {
		Com_Printf( "buffer overflowed\n" );
		fail = qtrue;
	}
	if( !fail ) {
		Com_Printf( "succeeded\n" );
	} else {
		Com_Printf( "failed\n" );
	}

	/* test if returned length is valid, no overflow, small buffer */
	Com_Printf( "test 2\n" );
	fail = qfalse;
	memset( buffer, '@', sizeof( buffer ) );
	ret = Com_sprintf( buffer, 5, "%s", "abcd" );
	if( ret != 4 ) {
		Com_Printf( "returned %d instead of %d\n", ret, 4 );
	}
	if( buffer[4] != 0 ) {
		Com_Printf( "buffer left unterminated\n" );
		fail = qtrue;
	}
	if( buffer[5] != '@' ) {
		Com_Printf( "buffer overflowed\n" );
		fail = qtrue;
	}
	if( !fail ) {
		Com_Printf( "succeeded\n" );
	} else {
		Com_Printf( "failed\n" );
	}

	/* test if returned length is valid, with overflow */
	Com_Printf( "test 3\n" );
	fail = qfalse;
	memset( buffer, '@', sizeof( buffer ) );
	ret = Com_sprintf( buffer, 5, "%s", "abcdefgh" );
	if( ret != 4 ) {
		Com_Printf( "returned %d instead of %d\n", ret, 4 );
		fail = qtrue;
	}
	if( buffer[4] != 0 ) {
		Com_Printf( "buffer left unterminated\n" );
		fail = qtrue;
	}
	if( buffer[5] != '@' ) {
		Com_Printf( "buffer overflowed\n" );
		fail = qtrue;
	}

	if( !fail ) {
		Com_Printf( "succeeded\n" );
	} else {
		Com_Printf( "failed\n" );
	}
}

#endif

const char *Com_FileNameGenerator( const char *path, const char *ext,
        const char *partial, qboolean stripExtension, int state ) {
    static int length, numFiles;
    static char **list;
    static int curpos;
    char *s, *p;

	if( state == 2 ) {
		goto finish;
	}
    
    if( !state ) {
        length = strlen( partial );
        list = FS_ListFiles( path, ext, 0, &numFiles );
        curpos = 0;
    }

    while( curpos < numFiles ) {
        s = list[curpos++];
		if( stripExtension ) {
			p = COM_FileExtension( s );
			*p = 0;
		}
        if( !strncmp( s, partial, length ) ) {
            return s;
        }
    }

finish:
    if( list ) {
        FS_FreeFileList( list );
        list = NULL;
    }
    return NULL;
}

const char *Com_FileNameGeneratorByFilter( const char *path, const char *filter,
        const char *partial, qboolean stripExtension, int state ) {
    static int length, numFiles;
    static char **list;
    static int curpos;
    char *s, *p;

	if( state == 2 ) {
		goto finish;
	}
    
    if( !state ) {
        length = strlen( partial );
        list = FS_ListFiles( path, filter, FS_SEARCH_SAVEPATH |
                FS_SEARCH_BYFILTER, &numFiles );
        curpos = 0;
    }

    while( curpos < numFiles ) {
        s = list[curpos++];
		if( stripExtension ) {
			p = COM_FileExtension( s );
			*p = 0;
		}
        if( !strncmp( s, partial, length ) ) {
            return s;
        }
    }

finish:
    if( list ) {
        FS_FreeFileList( list );
        list = NULL;
    }
    return NULL;
}

#define MAX_LINE_COMMANDS	128

static char	*com_commands[MAX_LINE_COMMANDS];
static int		com_numCommands;

/*
===============
Com_ParseCommandLine

===============
*/
static void Com_ParseCommandLine( char *commandLine ) {
	qboolean inquote = qfalse;

    if( !commandLine ) {
        return;
    }

	com_numCommands = 0;
	while( *commandLine ) {
		if( *commandLine == '\"' ) {
			inquote ^= 1;
		} else if( *commandLine == '+' && !inquote ) {
			com_commands[com_numCommands] = commandLine + 1;
			com_numCommands++;

			*commandLine = 0;

			if( com_numCommands == MAX_LINE_COMMANDS ) {
				break;
			}
		}

		commandLine++;

	}
}

/*
===============
Com_AddEarlyCommands

Adds command line parameters as script statements.
Commands lead with a +, and continue until another +

Set commands are added early, so they are guaranteed to be set before
the client and server initialize for the first time.

Other commands are added late, after all initialization is complete.
===============
*/
static void Com_AddEarlyCommands( qboolean clear ) {
	int		i;
	char	*s, *name;

	for( i = 0; i < com_numCommands; i++ ) {
		s = com_commands[i];
		if( !*s ) {
			continue;
		}
		Cmd_TokenizeString( s, qfalse );
		if( strcmp( Cmd_Argv( 0 ), "set" ) ) {
			continue;
		}
        if( Cmd_Argc() != 3 ) {
            Com_Printf( "Usage: +set <variable> <value>\n" );
        } else {
			name = Cmd_Argv( 1 );
			if( !strcmp( name, "port" ) ) {
				Com_WPrintf( "'port' variable is deprecated. Please use 'net_port' instead.\n" );
				name = "net_port";
			}
    		Cvar_SetEx( name, Cmd_Argv( 2 ), CVAR_SET_COMMAND_LINE );
        }
		if( clear ) {
			*s = 0;
		}
	}
}

/*
=================
Com_AddLateCommands

Adds command line parameters as script statements
Commands lead with a + and continue until another +

Returns qtrue if any late commands were added, which
will keep the demoloop from immediately starting

Assumes +set commands are already filtered out
=================
*/
static qboolean Com_AddLateCommands( void ) {
	int		i;
	char	*s;
	qboolean ret = qfalse;

	for( i = 0; i < com_numCommands; i++ ) {
		s = com_commands[i];
		if( !*s ) {
			continue;
		}
		Cbuf_AddText( s );
		Cbuf_AddText( "\n" );
		ret = qtrue;
	}

	if( ret ) {
		Cbuf_Execute();
	}

	return ret;
}

/*
=================
Qcommon_Init
=================
*/
void Qcommon_Init( char *commandLine ) {
	static const char *version = APPLICATION " " VERSION " " __DATE__ " " BUILDSTRING "-" CPUSTRING;

	if( _setjmp( abortframe ) )
		Sys_Error( "Error during initialization: %s", com_errorMsg );

	Com_Printf( S_COLOR_CYAN "%s\n", version );

	Com_ParseCommandLine( commandLine );

	// prepare enough of the subsystems to handle
	// cvar and command buffer management
	Z_Init();
	MSG_Init();
	Cbuf_Init();
	Cmd_Init();
	Cvar_Init();
	Key_Init();
	Prompt_Init();
	Con_Init();
	
	Com_FillAPI( &com );

	//
	// init commands and vars
	//
	host_speeds = Cvar_Get ("host_speeds", "0", 0);
	developer = Cvar_Get ("developer", "0", 0);
	timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT );
	fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT );
	logfile_active = Cvar_Get( "logfile", "0", 0 );
	logfile_flush = Cvar_Get( "logfile_flush", "0", 0 );
	logfile_name = Cvar_Get( "logfile_name", COM_LOGFILE_NAME, 0 );
#ifdef DEDICATED_ONLY
	dedicated = Cvar_Get ("dedicated", "1", CVAR_ROM);
#else
	dedicated = Cvar_Get ("dedicated", "0", CVAR_NOSET);
#endif
	sv_running = Cvar_Get( "sv_running", "0", CVAR_ROM );
	sv_paused = Cvar_Get( "sv_paused", "0", CVAR_ROM );
	cl_running = Cvar_Get( "cl_running", "0", CVAR_ROM );
	cl_paused = Cvar_Get( "cl_paused", "0", CVAR_ROM );
	com_timedemo = Cvar_Get( "timedemo", "0", CVAR_CHEAT );
	com_sleep = Cvar_Get( "com_sleep", "1", 0 );
	com_date_format = Cvar_Get( "com_date_format", "%Y-%m-%d", 0 );
	com_time_format = Cvar_Get( "com_time_format", "%H.%M.%S", 0 );
	com_debug_break = Cvar_Get( "com_debug_break", "0", 0 );
    
    //
    // userinfo - shared by regular client and MVD client
    //
    info_password = Cvar_Get( "password", "", CVAR_USERINFO );
	info_spectator = Cvar_Get( "spectator", dedicated->integer ? "1" : "0",
		CVAR_USERINFO );
    info_name = Cvar_Get( "name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE );
    info_skin = Cvar_Get( "skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE );
	info_rate = Cvar_Get( "rate", dedicated->integer ? "15000" : "5000",
		CVAR_USERINFO | CVAR_ARCHIVE );
    info_msg = Cvar_Get( "msg", "1", CVAR_USERINFO | CVAR_ARCHIVE );
    info_hand = Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE );
    info_fov = Cvar_Get( "fov", "90", CVAR_USERINFO | CVAR_ARCHIVE );
    info_gender = Cvar_Get( "gender", "male", CVAR_USERINFO | CVAR_ARCHIVE );

#ifdef NDEBUG
	Cvar_Get( "version", version, CVAR_SERVERINFO|CVAR_ROM );
#else
	Cvar_Get( "version", version, CVAR_SERVERINFO );
#endif

	Cmd_AddCommand ("z_stats", Z_Stats_f);

#ifndef DEDICATED_ONLY
	if( !dedicated->integer ) {
		Cmd_AddCommand( "writeconfig", Com_WriteConfig_f );
	}
#endif


	Cmd_AddMacro( "com_date", Com_Date_m );
	Cmd_AddMacro( "com_time", Com_Time_m );
	
	// we need to add the early commands twice, because
	// a basedir or cddir needs to be set before execing
	// config files, but we want other parms to override
	// the settings of the config files
	Com_AddEarlyCommands( qfalse );

	Sys_Init();
	FS_Init();

	/* after FS is initialized, open logfile */
	logfile_active->changedFunc = LogFileActive_OnChange;
	logfile_flush->changedFunc = LogFileParam_OnChange;
	logfile_name->changedFunc = LogFileParam_OnChange;
	LogFileActive_OnChange( logfile_active, NULL );

	Cbuf_AddText( "exec "COM_DEFAULTCFG_NAME"\n" );
	Cbuf_Execute();
	
	Cbuf_AddText( "exec "COM_CONFIG_NAME"\n" );
	Cbuf_Execute();

	Cbuf_AddText( "exec "COM_AUTOEXECCFG_NAME"\n" );
	Cbuf_Execute();

	Com_AddEarlyCommands( qtrue );

#ifndef NDEBUG
	Cmd_AddCommand( "error", Com_Error_f );
	Cmd_AddCommand( "errordrop", Com_ErrorDrop_f );
	Cmd_AddCommand( "freeze", Com_Freeze_f );
	Cmd_AddCommand( "crash", Com_Crash_f );
	Cmd_AddCommand( "vstest", Com_VsnprintfTest_f );
	Cmd_AddCommand( "zlibtest", Com_ZLibTest_f );
#endif

	Cmd_AddCommand( "lasterror", Com_LastError_f );

	Cmd_AddCommand( "quit", Com_Quit );

	srand( Sys_Milliseconds() );

	Netchan_Init();
	CM_Init();
	SV_Init();
	CL_Init();
	NET_Init();

	if( dedicated->integer ) {
		NET_Config( NET_SERVER );
	}

	// add + commands from command line
	if( !Com_AddLateCommands() ) {
		// if the user didn't give any commands, run default action
		if( dedicated->integer ) {
			Cbuf_AddText( "dedicated_start\n" );
		} else {
			// TODO
			//Cbuf_AddText( "d1\n" );
		}
		Cbuf_Execute();
	} else {
		// the user asked for something explicit
		// so drop the loading plaque
		SCR_EndLoadingPlaque();
	}


	Com_Printf( "====== " APPLICATION " initialized ======\n\n" );
	Com_Printf( S_COLOR_CYAN APPLICATION " " VERSION ", " __DATE__ "\n"
                S_COLOR_RESET   "w/ zlib " ZLIB_VERSION "\n" );
	Com_Printf( "http://q2pro.sf.net\n\n" );

	Com_DPrintf( "Working directory: %s\n", Sys_GetCurrentDirectory() );

	com_numCommands = 0;

	com_eventTime = Sys_Realtime();
}

/*
==============
Com_ProcessEvents
==============
*/
void Com_ProcessEvents( void ) {
	int ret;

	while( ( ret = NET_GetPacket( NS_SERVER, &net_from ) ) != 0 ) {
		SV_PacketEvent( ret );
	}

#ifdef DEDICATED_ONLY
	while( ( ret = NET_GetPacket( NS_CLIENT, &net_from ) ) != 0 ) {
		MVD_PacketEvent( ret );
	}
#else
	while( ( ret = NET_GetPacket( NS_CLIENT, &net_from ) ) != 0 ) {
		if( mvd_running->integer ) {
			MVD_PacketEvent( ret );
		} else {
			CL_PacketEvent( ret );
		}
	}
	CL_PumpEvents();
	CL_InputFrame();
#endif
}

#ifndef DEDICATED_ONLY
/*
==============
Com_ProcessLoopback
==============
*/
static void Com_ProcessLoopback( void ) {
	int i;

	memset( &net_from, 0, sizeof( net_from ) );
	net_from.type = NA_LOOPBACK;

	// Process loopback packets
	for( i = 0; i < 2; i++ ) {
		while( NET_GetLoopPacket( NS_SERVER ) ) {
			if( sv_running->integer ) {
				SV_PacketEvent( 1 );
			}
		}

		while( NET_GetLoopPacket( NS_CLIENT ) ) {
			if( cl_running->integer ) {
				CL_PacketEvent( 1 );
			}
		}
	}
}
#endif

/*
=================
Qcommon_Frame
=================
*/
void Qcommon_Frame( void ) {
	int		time_before, time_event, time_between, time_after;
	uint32	realTime;
    char	*s;
	int		msec;

	if( _setjmp( abortframe ) ) {
		return;			// an ERR_DROP was thrown
	}

	time_before = time_event = time_between = time_after = 0;

	if( host_speeds->integer )
		time_before = Sys_Milliseconds();

	do {
		Com_ProcessEvents();
		realTime = Sys_Realtime();
        if( com_eventTime > realTime ) {
            com_eventTime = realTime;
        }
		msec = realTime - com_eventTime;
	} while( msec < 1 );
	com_eventTime = realTime;

	if( fixedtime->integer ) {
		Cvar_ClampInteger( fixedtime, 1, 1000 );
		msec = fixedtime->integer;
	} else if( timescale->value > 0 ) {
		msec *= timescale->value;
		if( msec < 1 )
			msec = 1;
	}

    if( ( s = Sys_ConsoleInput() ) != NULL ) {
        do {
            Cbuf_AddText( s );
            Cbuf_AddText( "\n" );
        } while( ( s = Sys_ConsoleInput() ) != NULL );
        
        Cbuf_Execute();
    }

	if( host_speeds->integer )
		time_event = Sys_Milliseconds();

	SV_Frame( msec );

	if( host_speeds->integer )
		time_between = Sys_Milliseconds();

#ifndef DEDICATED_ONLY
	Com_ProcessLoopback();
#endif

	CL_Frame( msec );

	if( host_speeds->integer )
		time_after = Sys_Milliseconds();

	if( host_speeds->integer ) {
		int			all, ev, sv, gm, cl, rf;

		all = time_after - time_before;
		ev = time_event - time_before;
		sv = time_between - time_event;
		cl = time_after - time_between;
		gm = time_after_game - time_before_game;
		rf = time_after_ref - time_before_ref;
		sv -= gm;
		cl -= rf;

		Com_Printf( "all:%3i ev:%3i sv:%3i gm:%3i cl:%3i rf:%3i\n",
			all, ev, sv, gm, cl, rf );
	}
    
	cvar_infoModified = 0;

	com_framenum++;
}

/*
=================
Qcommon_Shutdown
=================
*/
void Qcommon_Shutdown( qboolean fatalError ) {
#ifndef DEDICATED_ONLY
	if( !fatalError && dedicated && !dedicated->integer ) {
		Com_WriteConfiguration( COM_CONFIG_NAME );
	}
#endif
	LogFile_Close();
	FS_Shutdown( qtrue );
}

