mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-25 21:51:20 +00:00
4792 lines
108 KiB
C
4792 lines
108 KiB
C
|
/*
|
||
|
===========================================================================
|
||
|
Copyright (C) 1999-2005 Id Software, Inc.
|
||
|
|
||
|
This file is part of Quake III Arena source code.
|
||
|
|
||
|
Quake III Arena source code 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.
|
||
|
|
||
|
Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
|
||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
===========================================================================
|
||
|
*/
|
||
|
// common.c -- misc functions used in client and server
|
||
|
|
||
|
#include "q_shared.h"
|
||
|
#include "qcommon.h"
|
||
|
#include <setjmp.h>
|
||
|
#ifndef _WIN32
|
||
|
#include <netinet/in.h>
|
||
|
#include <sys/stat.h> // umask
|
||
|
#include <sys/time.h>
|
||
|
#else
|
||
|
#include <winsock.h>
|
||
|
#endif
|
||
|
|
||
|
#include "../client/keys.h"
|
||
|
|
||
|
const int demo_protocols[] = { 66, 67, OLD_PROTOCOL_VERSION, NEW_PROTOCOL_VERSION, 0 };
|
||
|
|
||
|
#define USE_MULTI_SEGMENT // allocate additional zone segments on demand
|
||
|
|
||
|
#ifdef DEDICATED //1023 for 32bit - 2047 for 64bit
|
||
|
#define MIN_COMHUNKMEGS 1023
|
||
|
#define DEF_COMHUNKMEGS 1023
|
||
|
#else
|
||
|
#define MIN_COMHUNKMEGS 1023
|
||
|
#define DEF_COMHUNKMEGS 1023
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
#define DEF_COMZONEMEGS 128
|
||
|
#else
|
||
|
#define DEF_COMZONEMEGS 128
|
||
|
#endif
|
||
|
|
||
|
static jmp_buf abortframe; // an ERR_DROP occurred, exit the entire frame
|
||
|
|
||
|
int CPU_Flags = 0;
|
||
|
|
||
|
static fileHandle_t logfile = FS_INVALID_HANDLE;
|
||
|
static fileHandle_t com_journalFile = FS_INVALID_HANDLE ; // events are written here
|
||
|
fileHandle_t com_journalDataFile = FS_INVALID_HANDLE; // config files are written here
|
||
|
|
||
|
cvar_t *com_viewlog;
|
||
|
cvar_t *com_speeds;
|
||
|
cvar_t *com_developer;
|
||
|
cvar_t *com_dedicated;
|
||
|
cvar_t *com_timescale;
|
||
|
static cvar_t *com_fixedtime;
|
||
|
cvar_t *com_journal;
|
||
|
cvar_t *com_protocol;
|
||
|
qboolean com_protocolCompat;
|
||
|
#ifndef DEDICATED
|
||
|
cvar_t *com_maxfps;
|
||
|
cvar_t *com_maxfpsUnfocused;
|
||
|
cvar_t *com_yieldCPU;
|
||
|
cvar_t *com_timedemo;
|
||
|
#endif
|
||
|
#ifdef USE_AFFINITY_MASK
|
||
|
cvar_t *com_affinityMask;
|
||
|
#endif
|
||
|
static cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print
|
||
|
static cvar_t *com_showtrace;
|
||
|
cvar_t *com_version;
|
||
|
static cvar_t *com_buildScript; // for automated data building scripts
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
static cvar_t *com_introPlayed;
|
||
|
cvar_t *com_skipIdLogo;
|
||
|
|
||
|
cvar_t *cl_paused;
|
||
|
cvar_t *cl_packetdelay;
|
||
|
cvar_t *com_cl_running;
|
||
|
#endif
|
||
|
|
||
|
cvar_t *sv_paused;
|
||
|
cvar_t *sv_packetdelay;
|
||
|
cvar_t *com_sv_running;
|
||
|
cvar_t *cl_selectedmod;
|
||
|
cvar_t *cl_arenascriptDebug;
|
||
|
|
||
|
cvar_t *com_cameraMode;
|
||
|
#if defined(_WIN32) && defined(_DEBUG)
|
||
|
cvar_t *com_noErrorInterrupt;
|
||
|
#endif
|
||
|
|
||
|
// com_speeds times
|
||
|
int time_game;
|
||
|
int time_frontend; // renderer frontend time
|
||
|
int time_backend; // renderer backend time
|
||
|
|
||
|
static int lastTime;
|
||
|
int com_frameTime;
|
||
|
static int com_frameNumber;
|
||
|
|
||
|
qboolean com_errorEntered = qfalse;
|
||
|
qboolean com_fullyInitialized = qfalse;
|
||
|
|
||
|
// renderer window states
|
||
|
qboolean gw_minimized = qfalse; // this will be always true for dedicated servers
|
||
|
#ifndef DEDICATED
|
||
|
qboolean gw_active = qtrue;
|
||
|
#endif
|
||
|
|
||
|
static char com_errorMessage[ MAXPRINTMSG ];
|
||
|
|
||
|
static void Com_Shutdown( void );
|
||
|
static void Com_WriteConfig_f( void );
|
||
|
void CIN_CloseAllVideos( void );
|
||
|
|
||
|
//============================================================================
|
||
|
|
||
|
static char *rd_buffer;
|
||
|
static int rd_buffersize;
|
||
|
static qboolean rd_flushing = qfalse;
|
||
|
static void (*rd_flush)( const char *buffer );
|
||
|
|
||
|
void Com_BeginRedirect( char *buffer, int buffersize, void (*flush)(const char *) )
|
||
|
{
|
||
|
if (!buffer || !buffersize || !flush)
|
||
|
return;
|
||
|
rd_buffer = buffer;
|
||
|
rd_buffersize = buffersize;
|
||
|
rd_flush = flush;
|
||
|
|
||
|
*rd_buffer = '\0';
|
||
|
}
|
||
|
|
||
|
|
||
|
void Com_EndRedirect( void )
|
||
|
{
|
||
|
if ( rd_flush ) {
|
||
|
rd_flushing = qtrue;
|
||
|
rd_flush( rd_buffer );
|
||
|
rd_flushing = qfalse;
|
||
|
}
|
||
|
|
||
|
rd_buffer = NULL;
|
||
|
rd_buffersize = 0;
|
||
|
rd_flush = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
Com_Printf
|
||
|
|
||
|
Both client and server can use this, and it will output
|
||
|
to the appropriate place.
|
||
|
|
||
|
A raw string should NEVER be passed as fmt, because of "%f" type crashers.
|
||
|
=============
|
||
|
*/
|
||
|
void QDECL Com_Printf( const char *fmt, ... ) {
|
||
|
static qboolean opening_qconsole = qfalse;
|
||
|
va_list argptr;
|
||
|
char msg[MAXPRINTMSG];
|
||
|
int len;
|
||
|
|
||
|
va_start( argptr, fmt );
|
||
|
len = Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
|
||
|
va_end( argptr );
|
||
|
|
||
|
if ( rd_buffer && !rd_flushing ) {
|
||
|
if ( len + (int)strlen( rd_buffer ) > ( rd_buffersize - 1 ) ) {
|
||
|
rd_flushing = qtrue;
|
||
|
rd_flush( rd_buffer );
|
||
|
rd_flushing = qfalse;
|
||
|
*rd_buffer = '\0';
|
||
|
}
|
||
|
Q_strcat( rd_buffer, rd_buffersize, msg );
|
||
|
// TTimo nooo .. that would defeat the purpose
|
||
|
//rd_flush(rd_buffer);
|
||
|
//*rd_buffer = '\0';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
// echo to client console if we're not a dedicated server
|
||
|
if ( !com_dedicated || !com_dedicated->integer ) {
|
||
|
CL_ConsolePrint( msg );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// echo to dedicated console and early console
|
||
|
Sys_Print( msg );
|
||
|
|
||
|
// logfile
|
||
|
if ( com_logfile && com_logfile->integer ) {
|
||
|
// TTimo: only open the qconsole.log if the filesystem is in an initialized state
|
||
|
// also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on)
|
||
|
if ( logfile == FS_INVALID_HANDLE && FS_Initialized() && !opening_qconsole ) {
|
||
|
const char *logName = "qconsole.log";
|
||
|
int mode;
|
||
|
|
||
|
opening_qconsole = qtrue;
|
||
|
|
||
|
mode = com_logfile->integer - 1;
|
||
|
|
||
|
if ( mode & 2 )
|
||
|
logfile = FS_FOpenFileAppend( logName );
|
||
|
else
|
||
|
logfile = FS_FOpenFileWrite( logName );
|
||
|
|
||
|
if ( logfile != FS_INVALID_HANDLE ) {
|
||
|
struct tm *newtime;
|
||
|
time_t aclock;
|
||
|
char timestr[32];
|
||
|
|
||
|
time( &aclock );
|
||
|
newtime = localtime( &aclock );
|
||
|
strftime( timestr, sizeof( timestr ), "%a %b %d %X %Y", newtime );
|
||
|
|
||
|
Com_Printf( "logfile opened on %s\n", timestr );
|
||
|
|
||
|
if ( mode & 1 ) {
|
||
|
// force it to not buffer so we get valid
|
||
|
// data even if we are crashing
|
||
|
FS_ForceFlush( logfile );
|
||
|
}
|
||
|
} else {
|
||
|
Com_Printf( S_COLOR_YELLOW "Opening %s failed!\n", logName );
|
||
|
Cvar_Set( "logfile", "0" );
|
||
|
}
|
||
|
|
||
|
opening_qconsole = qfalse;
|
||
|
}
|
||
|
if ( logfile != FS_INVALID_HANDLE && FS_Initialized() ) {
|
||
|
FS_Write( msg, len, logfile );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Com_DPrintf
|
||
|
|
||
|
A Com_Printf that only shows up if the "developer" cvar is set
|
||
|
================
|
||
|
*/
|
||
|
void QDECL Com_DPrintf( const char *fmt, ...) {
|
||
|
va_list argptr;
|
||
|
char msg[MAXPRINTMSG];
|
||
|
|
||
|
if ( !com_developer || !com_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_CYAN "%s", msg );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
Com_Error
|
||
|
|
||
|
Both client and server can use this, and it will
|
||
|
do the appropriate things.
|
||
|
=============
|
||
|
*/
|
||
|
void QDECL Com_Error( errorParm_t code, const char *fmt, ... ) {
|
||
|
va_list argptr;
|
||
|
static int lastErrorTime;
|
||
|
static int errorCount;
|
||
|
static qboolean calledSysError = qfalse;
|
||
|
int currentTime;
|
||
|
|
||
|
#if defined(_WIN32) && defined(_DEBUG)
|
||
|
if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) {
|
||
|
if ( !com_noErrorInterrupt->integer ) {
|
||
|
DebugBreak();
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( code != ERR_NOTDROP ) {
|
||
|
if ( com_errorEntered ) {
|
||
|
if ( !calledSysError ) {
|
||
|
calledSysError = qtrue;
|
||
|
Sys_Error( "recursive error after: %s", com_errorMessage );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
com_errorEntered = qtrue;
|
||
|
|
||
|
Cvar_Set( "com_errorCode", va( "%i", code ) );
|
||
|
}
|
||
|
|
||
|
// when we are running automated scripts, make sure we
|
||
|
// know if anything failed
|
||
|
if ( com_buildScript && com_buildScript->integer ) {
|
||
|
code = ERR_FATAL;
|
||
|
}
|
||
|
|
||
|
// if we are getting a solid stream of ERR_DROP, do an ERR_FATAL
|
||
|
if ( code != ERR_NOTDROP ) {
|
||
|
currentTime = Sys_Milliseconds();
|
||
|
if ( currentTime - lastErrorTime < 100 ) {
|
||
|
if ( ++errorCount > 3 ) {
|
||
|
code = ERR_FATAL;
|
||
|
}
|
||
|
} else {
|
||
|
errorCount = 0;
|
||
|
}
|
||
|
}
|
||
|
lastErrorTime = currentTime;
|
||
|
|
||
|
va_start( argptr, fmt );
|
||
|
Q_vsnprintf( com_errorMessage, sizeof( com_errorMessage ), fmt, argptr );
|
||
|
va_end( argptr );
|
||
|
|
||
|
if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) {
|
||
|
// we can't recover from ERR_FATAL so there is no recipients for com_errorMessage
|
||
|
// also if ERR_FATAL was called from S_Malloc - CopyString for a long (2+ chars) text
|
||
|
// will trigger recursive error without proper client/server shutdown
|
||
|
if ( code != ERR_FATAL ) {
|
||
|
Cvar_Set( "com_errorMessage", com_errorMessage );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Cbuf_Init();
|
||
|
|
||
|
if ( code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT ) {
|
||
|
VM_Forced_Unload_Start();
|
||
|
SV_Shutdown( "Server disconnected" );
|
||
|
Com_EndRedirect();
|
||
|
#ifndef DEDICATED
|
||
|
CL_Disconnect( qfalse );
|
||
|
CL_FlushMemory();
|
||
|
#endif
|
||
|
VM_Forced_Unload_Done();
|
||
|
|
||
|
// make sure we can get at our local stuff
|
||
|
FS_PureServerSetLoadedPaks( "", "" );
|
||
|
com_errorEntered = qfalse;
|
||
|
|
||
|
Q_longjmp( abortframe, 1 );
|
||
|
} else if ( code == ERR_DROP ) {
|
||
|
Com_Printf( "********************\nERROR: %s\n********************\n",
|
||
|
com_errorMessage );
|
||
|
VM_Forced_Unload_Start();
|
||
|
SV_Shutdown( va( "Server crashed: %s", com_errorMessage ) );
|
||
|
Com_EndRedirect();
|
||
|
#ifndef DEDICATED
|
||
|
CL_Disconnect( qfalse );
|
||
|
CL_FlushMemory();
|
||
|
#endif
|
||
|
VM_Forced_Unload_Done();
|
||
|
|
||
|
FS_PureServerSetLoadedPaks( "", "" );
|
||
|
com_errorEntered = qfalse;
|
||
|
|
||
|
Q_longjmp( abortframe, 1 );
|
||
|
} else if ( code == ERR_NOTDROP ) {
|
||
|
Com_Printf( "********************\nERROR: %s\n********************\n",
|
||
|
com_errorMessage );
|
||
|
/* VM_Forced_Unload_Start();
|
||
|
SV_Shutdown( va( "Server crashed: %s", com_errorMessage ) );
|
||
|
Com_EndRedirect();
|
||
|
#ifndef DEDICATED
|
||
|
CL_Disconnect( qfalse );
|
||
|
CL_FlushMemory();
|
||
|
#endif
|
||
|
VM_Forced_Unload_Done();
|
||
|
|
||
|
FS_PureServerSetLoadedPaks( "", "" );
|
||
|
com_errorEntered = qfalse;
|
||
|
|
||
|
Q_longjmp( abortframe, 1 );*/
|
||
|
} else if ( code == ERR_NEED_CD ) {
|
||
|
SV_Shutdown( "Server didn't have CD" );
|
||
|
Com_EndRedirect();
|
||
|
#ifndef DEDICATED
|
||
|
if ( com_cl_running && com_cl_running->integer ) {
|
||
|
CL_Disconnect( qfalse );
|
||
|
VM_Forced_Unload_Start();
|
||
|
CL_FlushMemory();
|
||
|
VM_Forced_Unload_Done();
|
||
|
CL_CDDialog();
|
||
|
} else {
|
||
|
Com_Printf( "Server didn't have CD\n" );
|
||
|
}
|
||
|
#endif
|
||
|
FS_PureServerSetLoadedPaks( "", "" );
|
||
|
com_errorEntered = qfalse;
|
||
|
|
||
|
Q_longjmp( abortframe, 1 );
|
||
|
} else {
|
||
|
VM_Forced_Unload_Start();
|
||
|
#ifndef DEDICATED
|
||
|
CL_Shutdown( va( "Server fatal crashed: %s", com_errorMessage ), qtrue );
|
||
|
#endif
|
||
|
SV_Shutdown( va( "Server fatal crashed: %s", com_errorMessage ) );
|
||
|
Com_EndRedirect();
|
||
|
VM_Forced_Unload_Done();
|
||
|
}
|
||
|
if ( code != ERR_NOTDROP ) {
|
||
|
Com_Shutdown();
|
||
|
|
||
|
calledSysError = qtrue;
|
||
|
Sys_Error( "%s", com_errorMessage );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
Com_Quit_f
|
||
|
|
||
|
Both client and server can use this, and it will
|
||
|
do the appropriate things.
|
||
|
=============
|
||
|
*/
|
||
|
void Com_Quit_f( void ) {
|
||
|
const char *p = Cmd_ArgsFrom( 1 );
|
||
|
// don't try to shutdown if we are in a recursive error
|
||
|
if ( !com_errorEntered ) {
|
||
|
// Some VMs might execute "quit" command directly,
|
||
|
// which would trigger an unload of active VM error.
|
||
|
// Sys_Quit will kill this process anyways, so
|
||
|
// a corrupt call stack makes no difference
|
||
|
VM_Forced_Unload_Start();
|
||
|
SV_Shutdown( p[0] ? p : "Server quit" );
|
||
|
#ifndef DEDICATED
|
||
|
CL_Shutdown( p[0] ? p : "Client quit", qtrue );
|
||
|
#endif
|
||
|
VM_Forced_Unload_Done();
|
||
|
Com_Shutdown();
|
||
|
FS_Shutdown( qtrue );
|
||
|
}
|
||
|
Sys_Quit();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============================================================================
|
||
|
|
||
|
COMMAND LINE FUNCTIONS
|
||
|
|
||
|
+ characters separate the commandLine string into multiple console
|
||
|
command lines.
|
||
|
|
||
|
All of these are valid:
|
||
|
|
||
|
quake3 +set test blah +map test
|
||
|
quake3 set test blah+map test
|
||
|
quake3 set test blah + map test
|
||
|
|
||
|
============================================================================
|
||
|
*/
|
||
|
|
||
|
#define MAX_CONSOLE_LINES 32
|
||
|
static int com_numConsoleLines;
|
||
|
static char *com_consoleLines[MAX_CONSOLE_LINES];
|
||
|
|
||
|
// master rcon password
|
||
|
char rconPassword2[MAX_CVAR_VALUE_STRING];
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_ParseCommandLine
|
||
|
|
||
|
Break it up into multiple console lines
|
||
|
==================
|
||
|
*/
|
||
|
static void Com_ParseCommandLine( char *commandLine ) {
|
||
|
static int parsed = 0;
|
||
|
int inq;
|
||
|
|
||
|
if ( parsed )
|
||
|
return;
|
||
|
|
||
|
inq = 0;
|
||
|
com_consoleLines[0] = commandLine;
|
||
|
rconPassword2[0] = '\0';
|
||
|
|
||
|
while ( *commandLine ) {
|
||
|
if (*commandLine == '"') {
|
||
|
inq = !inq;
|
||
|
}
|
||
|
// look for a + separating character
|
||
|
// if commandLine came from a file, we might have real line separators
|
||
|
if ( (*commandLine == '+' && !inq) || *commandLine == '\n' || *commandLine == '\r' ) {
|
||
|
if ( com_numConsoleLines == MAX_CONSOLE_LINES ) {
|
||
|
break;
|
||
|
}
|
||
|
com_consoleLines[com_numConsoleLines] = commandLine + 1;
|
||
|
com_numConsoleLines++;
|
||
|
*commandLine = '\0';
|
||
|
}
|
||
|
commandLine++;
|
||
|
}
|
||
|
parsed = 1;
|
||
|
}
|
||
|
|
||
|
char cl_title[ MAX_CVAR_VALUE_STRING ] = CLIENT_WINDOW_TITLE;
|
||
|
|
||
|
/*
|
||
|
===================
|
||
|
Com_EarlyParseCmdLine
|
||
|
|
||
|
returns qtrue if both vid_xpos and vid_ypos was set
|
||
|
===================
|
||
|
*/
|
||
|
qboolean Com_EarlyParseCmdLine( char *commandLine, char *con_title, int title_size, int *vid_xpos, int *vid_ypos )
|
||
|
{
|
||
|
int flags = 0;
|
||
|
int i;
|
||
|
|
||
|
*con_title = '\0';
|
||
|
Com_ParseCommandLine( commandLine );
|
||
|
|
||
|
for ( i = 0 ; i < com_numConsoleLines ; i++ ) {
|
||
|
Cmd_TokenizeString( com_consoleLines[i] );
|
||
|
if ( !Q_stricmpn( Cmd_Argv(0), "set", 3 ) && !Q_stricmp( Cmd_Argv(1), "cl_title" ) ) {
|
||
|
com_consoleLines[i][0] = '\0';
|
||
|
Q_strncpyz( cl_title, Cmd_ArgsFrom( 2 ), sizeof(cl_title) );
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmp( Cmd_Argv(0), "cl_title" ) ) {
|
||
|
com_consoleLines[i][0] = '\0';
|
||
|
Q_strncpyz( cl_title, Cmd_ArgsFrom( 1 ), sizeof(cl_title) );
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmpn( Cmd_Argv(0), "set", 3 ) && !Q_stricmp( Cmd_Argv(1), "con_title" ) ) {
|
||
|
com_consoleLines[i][0] = '\0';
|
||
|
Q_strncpyz( con_title, Cmd_ArgsFrom( 2 ), title_size );
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmp( Cmd_Argv(0), "con_title" ) ) {
|
||
|
com_consoleLines[i][0] = '\0';
|
||
|
Q_strncpyz( con_title, Cmd_ArgsFrom( 1 ), title_size );
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmpn( Cmd_Argv(0), "set", 3 ) && !Q_stricmp( Cmd_Argv(1), "vid_xpos" ) ) {
|
||
|
*vid_xpos = atoi( Cmd_Argv( 2 ) );
|
||
|
flags |= 1;
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmp( Cmd_Argv(0), "vid_xpos" ) ) {
|
||
|
*vid_xpos = atoi( Cmd_Argv( 1 ) );
|
||
|
flags |= 1;
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmpn( Cmd_Argv(0), "set", 3 ) && !Q_stricmp( Cmd_Argv(1), "vid_ypos" ) ) {
|
||
|
*vid_ypos = atoi( Cmd_Argv( 2 ) );
|
||
|
flags |= 2;
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmp( Cmd_Argv(0), "vid_ypos" ) ) {
|
||
|
*vid_ypos = atoi( Cmd_Argv( 1 ) );
|
||
|
flags |= 2;
|
||
|
continue;
|
||
|
}
|
||
|
if ( !Q_stricmpn( Cmd_Argv(0), "set", 3 ) && !Q_stricmp( Cmd_Argv(1), "rconPassword2" ) ) {
|
||
|
com_consoleLines[i][0] = '\0';
|
||
|
Q_strncpyz( rconPassword2, Cmd_Argv( 2 ), sizeof( rconPassword2 ) );
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (flags == 3) ? qtrue : qfalse ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===================
|
||
|
Com_SafeMode
|
||
|
|
||
|
Check for "safe" on the command line, which will
|
||
|
skip loading of q3config.cfg
|
||
|
===================
|
||
|
*/
|
||
|
qboolean Com_SafeMode( void ) {
|
||
|
int i;
|
||
|
|
||
|
for ( i = 0 ; i < com_numConsoleLines ; i++ ) {
|
||
|
Cmd_TokenizeString( com_consoleLines[i] );
|
||
|
if ( !Q_stricmp( Cmd_Argv(0), "safe" )
|
||
|
|| !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) {
|
||
|
com_consoleLines[i][0] = '\0';
|
||
|
return qtrue;
|
||
|
}
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Com_StartupVariable
|
||
|
|
||
|
Searches for command line parameters that are set commands.
|
||
|
If match is not NULL, only that cvar will be looked for.
|
||
|
That is necessary because cddir and basedir need to be set
|
||
|
before the filesystem is started, but all other sets should
|
||
|
be after execing the config and default.
|
||
|
===============
|
||
|
*/
|
||
|
void Com_StartupVariable( const char *match ) {
|
||
|
int i;
|
||
|
const char *name;
|
||
|
|
||
|
for ( i = 0; i < com_numConsoleLines; i++ ) {
|
||
|
Cmd_TokenizeString( com_consoleLines[i] );
|
||
|
if ( Q_stricmp( Cmd_Argv( 0 ), "set" ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
name = Cmd_Argv( 1 );
|
||
|
if ( !match || Q_stricmp( name, match ) == 0 ) {
|
||
|
if ( Cvar_Flags( name ) == CVAR_NONEXISTENT )
|
||
|
Cvar_Get( name, Cmd_ArgsFrom( 2 ), CVAR_USER_CREATED );
|
||
|
else
|
||
|
Cvar_Set2( name, Cmd_ArgsFrom( 2 ), qfalse );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_AddStartupCommands
|
||
|
|
||
|
Adds command line parameters as script statements
|
||
|
Commands are separated by + signs
|
||
|
|
||
|
Returns qtrue if any late commands were added, which
|
||
|
will keep the demoloop from immediately starting
|
||
|
=================
|
||
|
*/
|
||
|
static qboolean Com_AddStartupCommands( void ) {
|
||
|
int i;
|
||
|
qboolean added;
|
||
|
|
||
|
added = qfalse;
|
||
|
// quote every token, so args with semicolons can work
|
||
|
for (i=0 ; i < com_numConsoleLines ; i++) {
|
||
|
if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// set commands already added with Com_StartupVariable
|
||
|
if ( !Q_stricmpn( com_consoleLines[i], "set ", 4 ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
added = qtrue;
|
||
|
Cbuf_AddText( com_consoleLines[i] );
|
||
|
Cbuf_AddText( "\n" );
|
||
|
}
|
||
|
|
||
|
return added;
|
||
|
}
|
||
|
|
||
|
|
||
|
//============================================================================
|
||
|
|
||
|
void Info_Print( const char *s ) {
|
||
|
char key[BIG_INFO_KEY];
|
||
|
char value[BIG_INFO_VALUE];
|
||
|
|
||
|
do {
|
||
|
s = Info_NextPair( s, key, value );
|
||
|
if ( key[0] == '\0' )
|
||
|
break;
|
||
|
|
||
|
if ( value[0] == '\0' )
|
||
|
strcpy( value, "MISSING VALUE" );
|
||
|
|
||
|
Com_Printf( "%-20s %s\n", key, value );
|
||
|
|
||
|
} while ( *s != '\0' );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Com_StringContains
|
||
|
============
|
||
|
*/
|
||
|
static const char *Com_StringContains( const char *str1, const char *str2, int len2 ) {
|
||
|
int len, i, j;
|
||
|
|
||
|
len = strlen(str1) - len2;
|
||
|
for (i = 0; i <= len; i++, str1++) {
|
||
|
for (j = 0; str2[j]; j++) {
|
||
|
if (locase[(byte)str1[j]] != locase[(byte)str2[j]]) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!str2[j]) {
|
||
|
return str1;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Com_Filter
|
||
|
============
|
||
|
*/
|
||
|
int Com_Filter( const char *filter, const char *name )
|
||
|
{
|
||
|
char buf[ MAX_TOKEN_CHARS ];
|
||
|
const char *ptr;
|
||
|
int i, found;
|
||
|
|
||
|
while(*filter) {
|
||
|
if (*filter == '*') {
|
||
|
filter++;
|
||
|
for (i = 0; *filter; i++) {
|
||
|
if (*filter == '*' || *filter == '?')
|
||
|
break;
|
||
|
buf[i] = *filter;
|
||
|
filter++;
|
||
|
}
|
||
|
buf[i] = '\0';
|
||
|
if ( i ) {
|
||
|
ptr = Com_StringContains( name, buf, i );
|
||
|
if ( !ptr )
|
||
|
return qfalse;
|
||
|
name = ptr + i;
|
||
|
}
|
||
|
}
|
||
|
else if (*filter == '?') {
|
||
|
filter++;
|
||
|
name++;
|
||
|
}
|
||
|
else if (*filter == '[' && *(filter+1) == '[') {
|
||
|
filter++;
|
||
|
}
|
||
|
else if (*filter == '[') {
|
||
|
filter++;
|
||
|
found = qfalse;
|
||
|
while(*filter && !found) {
|
||
|
if (*filter == ']' && *(filter+1) != ']') break;
|
||
|
if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
|
||
|
if (locase[(byte)*name] >= locase[(byte)*filter] &&
|
||
|
locase[(byte)*name] <= locase[(byte)*(filter+2)])
|
||
|
found = qtrue;
|
||
|
filter += 3;
|
||
|
}
|
||
|
else {
|
||
|
if (locase[(byte)*filter] == locase[(byte)*name])
|
||
|
found = qtrue;
|
||
|
filter++;
|
||
|
}
|
||
|
}
|
||
|
if (!found) return qfalse;
|
||
|
while(*filter) {
|
||
|
if (*filter == ']' && *(filter+1) != ']') break;
|
||
|
filter++;
|
||
|
}
|
||
|
filter++;
|
||
|
name++;
|
||
|
}
|
||
|
else {
|
||
|
if (locase[(byte)*filter] != locase[(byte)*name])
|
||
|
return qfalse;
|
||
|
filter++;
|
||
|
name++;
|
||
|
}
|
||
|
}
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Com_FilterExt
|
||
|
============
|
||
|
*/
|
||
|
qboolean Com_FilterExt( const char *filter, const char *name )
|
||
|
{
|
||
|
char buf[ MAX_TOKEN_CHARS ];
|
||
|
const char *ptr;
|
||
|
int i;
|
||
|
|
||
|
while ( *filter ) {
|
||
|
if ( *filter == '*' ) {
|
||
|
filter++;
|
||
|
for ( i = 0; *filter != '\0' && i < sizeof(buf)-1; i++ ) {
|
||
|
if ( *filter == '*' || *filter == '?' )
|
||
|
break;
|
||
|
buf[i] = *filter++;
|
||
|
}
|
||
|
buf[ i ] = '\0';
|
||
|
if ( i ) {
|
||
|
ptr = Com_StringContains( name, buf, i );
|
||
|
if ( !ptr )
|
||
|
return qfalse;
|
||
|
name = ptr + i;
|
||
|
} else if ( *filter == '\0' ) {
|
||
|
return qtrue;
|
||
|
}
|
||
|
}
|
||
|
else if ( *filter == '?' ) {
|
||
|
if ( *name == '\0' )
|
||
|
return qfalse;
|
||
|
filter++;
|
||
|
name++;
|
||
|
}
|
||
|
else {
|
||
|
if ( locase[(byte)*filter] != locase[(byte)*name] )
|
||
|
return qfalse;
|
||
|
filter++;
|
||
|
name++;
|
||
|
}
|
||
|
}
|
||
|
if ( *name ) {
|
||
|
return qfalse;
|
||
|
}
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Com_HasPatterns
|
||
|
============
|
||
|
*/
|
||
|
qboolean Com_HasPatterns( const char *str )
|
||
|
{
|
||
|
int c;
|
||
|
|
||
|
while ( (c = *str++) != '\0' )
|
||
|
{
|
||
|
if ( c == '*' || c == '?' )
|
||
|
{
|
||
|
return qtrue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
============
|
||
|
Com_FilterPath
|
||
|
============
|
||
|
*/
|
||
|
int Com_FilterPath( const char *filter, const char *name )
|
||
|
{
|
||
|
int i;
|
||
|
char new_filter[MAX_QPATH];
|
||
|
char new_name[MAX_QPATH];
|
||
|
|
||
|
for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) {
|
||
|
if ( filter[i] == '\\' || filter[i] == ':' ) {
|
||
|
new_filter[i] = '/';
|
||
|
}
|
||
|
else {
|
||
|
new_filter[i] = filter[i];
|
||
|
}
|
||
|
}
|
||
|
new_filter[i] = '\0';
|
||
|
for (i = 0; i < MAX_QPATH-1 && name[i]; i++) {
|
||
|
if ( name[i] == '\\' || name[i] == ':' ) {
|
||
|
new_name[i] = '/';
|
||
|
}
|
||
|
else {
|
||
|
new_name[i] = name[i];
|
||
|
}
|
||
|
}
|
||
|
new_name[i] = '\0';
|
||
|
return Com_Filter( new_filter, new_name );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Com_RealTime
|
||
|
================
|
||
|
*/
|
||
|
int Com_RealTime(qtime_t *qtime) {
|
||
|
time_t t;
|
||
|
struct tm *tms;
|
||
|
|
||
|
t = time(NULL);
|
||
|
if (!qtime)
|
||
|
return t;
|
||
|
tms = localtime(&t);
|
||
|
if (tms) {
|
||
|
qtime->tm_sec = tms->tm_sec;
|
||
|
qtime->tm_min = tms->tm_min;
|
||
|
qtime->tm_hour = tms->tm_hour;
|
||
|
qtime->tm_mday = tms->tm_mday;
|
||
|
qtime->tm_mon = tms->tm_mon;
|
||
|
qtime->tm_year = tms->tm_year;
|
||
|
qtime->tm_wday = tms->tm_wday;
|
||
|
qtime->tm_yday = tms->tm_yday;
|
||
|
qtime->tm_isdst = tms->tm_isdst;
|
||
|
}
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Sys_Microseconds
|
||
|
================
|
||
|
*/
|
||
|
int64_t Sys_Microseconds( void )
|
||
|
{
|
||
|
#ifdef _WIN32
|
||
|
static qboolean inited = qfalse;
|
||
|
static LARGE_INTEGER base;
|
||
|
static LARGE_INTEGER freq;
|
||
|
LARGE_INTEGER curr;
|
||
|
|
||
|
if ( !inited )
|
||
|
{
|
||
|
QueryPerformanceFrequency( &freq );
|
||
|
QueryPerformanceCounter( &base );
|
||
|
if ( !freq.QuadPart )
|
||
|
{
|
||
|
return (int64_t)Sys_Milliseconds() * 1000LL; // fallback
|
||
|
}
|
||
|
inited = qtrue;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
QueryPerformanceCounter( &curr );
|
||
|
|
||
|
return ((curr.QuadPart - base.QuadPart) * 1000000LL) / freq.QuadPart;
|
||
|
#else
|
||
|
struct timeval curr;
|
||
|
gettimeofday( &curr, NULL );
|
||
|
|
||
|
return (int64_t)curr.tv_sec * 1000000LL + (int64_t)curr.tv_usec;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
ZONE MEMORY ALLOCATION
|
||
|
|
||
|
There is never any space between memblocks, and there will never be two
|
||
|
contiguous free memblocks.
|
||
|
|
||
|
The rover can be left pointing at a non-empty block
|
||
|
|
||
|
The zone calls are pretty much only used for small strings and structures,
|
||
|
all big things are allocated on the hunk.
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
#define ZONEID 0x1d4a11
|
||
|
#define MINFRAGMENT 64
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
#if 1 // forward lookup, faster allocation
|
||
|
#define DIRECTION next
|
||
|
// we may have up to 4 lists to group free blocks by size
|
||
|
//#define TINY_SIZE 32
|
||
|
#define SMALL_SIZE 64
|
||
|
#define MEDIUM_SIZE 128
|
||
|
#else // backward lookup, better free space consolidation
|
||
|
#define DIRECTION prev
|
||
|
#define TINY_SIZE 64
|
||
|
#define SMALL_SIZE 128
|
||
|
#define MEDIUM_SIZE 256
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#define USE_STATIC_TAGS
|
||
|
#define USE_TRASH_TEST
|
||
|
|
||
|
#ifdef ZONE_DEBUG
|
||
|
typedef struct zonedebug_s {
|
||
|
const char *label;
|
||
|
const char *file;
|
||
|
int line;
|
||
|
int allocSize;
|
||
|
} zonedebug_t;
|
||
|
#endif
|
||
|
|
||
|
typedef struct memblock_s {
|
||
|
struct memblock_s *next, *prev;
|
||
|
int size; // including the header and possibly tiny fragments
|
||
|
memtag_t tag; // a tag of 0 is a free block
|
||
|
int id; // should be ZONEID
|
||
|
#ifdef ZONE_DEBUG
|
||
|
zonedebug_t d;
|
||
|
#endif
|
||
|
} memblock_t;
|
||
|
|
||
|
typedef struct freeblock_s {
|
||
|
struct freeblock_s *prev;
|
||
|
struct freeblock_s *next;
|
||
|
} freeblock_t;
|
||
|
|
||
|
typedef struct memzone_s {
|
||
|
int size; // total bytes malloced, including header
|
||
|
int used; // total bytes used
|
||
|
memblock_t blocklist; // start / end cap for linked list
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
memblock_t dummy0; // just to allocate some space before freelist
|
||
|
freeblock_t freelist_tiny;
|
||
|
memblock_t dummy1;
|
||
|
freeblock_t freelist_small;
|
||
|
memblock_t dummy2;
|
||
|
freeblock_t freelist_medium;
|
||
|
memblock_t dummy3;
|
||
|
freeblock_t freelist;
|
||
|
#else
|
||
|
memblock_t *rover;
|
||
|
#endif
|
||
|
} memzone_t;
|
||
|
|
||
|
static int minfragment = MINFRAGMENT; // may be adjusted at runtime
|
||
|
|
||
|
// main zone for all "dynamic" memory allocation
|
||
|
static memzone_t *mainzone;
|
||
|
|
||
|
// we also have a small zone for small allocations that would only
|
||
|
// fragment the main zone (think of cvar and cmd strings)
|
||
|
static memzone_t *smallzone;
|
||
|
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
|
||
|
static void InitFree( freeblock_t *fb )
|
||
|
{
|
||
|
memblock_t *block = (memblock_t*)( (byte*)fb - sizeof( memblock_t ) );
|
||
|
Com_Memset( block, 0, sizeof( *block ) );
|
||
|
}
|
||
|
|
||
|
|
||
|
static void RemoveFree( memblock_t *block )
|
||
|
{
|
||
|
freeblock_t *fb = (freeblock_t*)( block + 1 );
|
||
|
freeblock_t *prev;
|
||
|
freeblock_t *next;
|
||
|
|
||
|
#ifdef ZONE_DEBUG
|
||
|
if ( fb->next == NULL || fb->prev == NULL || fb->next == fb || fb->prev == fb ) {
|
||
|
Com_Error( ERR_FATAL, "RemoveFree: bad pointers fb->next: %p, fb->prev: %p\n", fb->next, fb->prev );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
prev = fb->prev;
|
||
|
next = fb->next;
|
||
|
|
||
|
prev->next = next;
|
||
|
next->prev = prev;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void InsertFree( memzone_t *zone, memblock_t *block )
|
||
|
{
|
||
|
freeblock_t *fb = (freeblock_t*)( block + 1 );
|
||
|
freeblock_t *prev, *next;
|
||
|
#ifdef TINY_SIZE
|
||
|
if ( block->size <= TINY_SIZE )
|
||
|
prev = &zone->freelist_tiny;
|
||
|
else
|
||
|
#endif
|
||
|
#ifdef SMALL_SIZE
|
||
|
if ( block->size <= SMALL_SIZE )
|
||
|
prev = &zone->freelist_small;
|
||
|
else
|
||
|
#endif
|
||
|
#ifdef MEDIUM_SIZE
|
||
|
if ( block->size <= MEDIUM_SIZE )
|
||
|
prev = &zone->freelist_medium;
|
||
|
else
|
||
|
#endif
|
||
|
prev = &zone->freelist;
|
||
|
|
||
|
next = prev->next;
|
||
|
|
||
|
#ifdef ZONE_DEBUG
|
||
|
if ( block->size < sizeof( *fb ) + sizeof( *block ) ) {
|
||
|
Com_Error( ERR_FATAL, "InsertFree: bad block size: %i\n", block->size );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
prev->next = fb;
|
||
|
next->prev = fb;
|
||
|
|
||
|
fb->prev = prev;
|
||
|
fb->next = next;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
NewBlock
|
||
|
|
||
|
Allocates new free block within specified memory zone
|
||
|
|
||
|
Separator is needed to avoid additional runtime checks in Z_Free()
|
||
|
to prevent merging it with previous free block
|
||
|
================
|
||
|
*/
|
||
|
static freeblock_t *NewBlock( memzone_t *zone, int size )
|
||
|
{
|
||
|
memblock_t *prev, *next;
|
||
|
memblock_t *block, *sep;
|
||
|
int alloc_size;
|
||
|
|
||
|
// zone->prev is pointing on last block in the list
|
||
|
prev = zone->blocklist.prev;
|
||
|
next = prev->next;
|
||
|
|
||
|
size = PAD( size, 1<<21 ); // round up to 2M blocks
|
||
|
// allocate separator block before new free block
|
||
|
alloc_size = size + sizeof( *sep );
|
||
|
|
||
|
sep = (memblock_t *) calloc( alloc_size, 1 );
|
||
|
if ( sep == NULL ) {
|
||
|
Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",
|
||
|
size, zone == smallzone ? "small" : "main" );
|
||
|
return NULL;
|
||
|
}
|
||
|
block = sep+1;
|
||
|
|
||
|
// link separator with prev
|
||
|
prev->next = sep;
|
||
|
sep->prev = prev;
|
||
|
|
||
|
// link separator with block
|
||
|
sep->next = block;
|
||
|
block->prev = sep;
|
||
|
|
||
|
// link block with next
|
||
|
block->next = next;
|
||
|
next->prev = block;
|
||
|
|
||
|
sep->tag = TAG_GENERAL; // in-use block
|
||
|
sep->id = -ZONEID;
|
||
|
sep->size = 0;
|
||
|
|
||
|
block->tag = TAG_FREE;
|
||
|
block->id = ZONEID;
|
||
|
block->size = size;
|
||
|
|
||
|
// update zone statistics
|
||
|
zone->size += alloc_size;
|
||
|
zone->used += sizeof( *sep );
|
||
|
|
||
|
InsertFree( zone, block );
|
||
|
|
||
|
return (freeblock_t*)( block + 1 );
|
||
|
}
|
||
|
|
||
|
|
||
|
static memblock_t *SearchFree( memzone_t *zone, int size )
|
||
|
{
|
||
|
const freeblock_t *fb;
|
||
|
memblock_t *base;
|
||
|
|
||
|
#ifdef TINY_SIZE
|
||
|
if ( size <= TINY_SIZE )
|
||
|
fb = zone->freelist_tiny.DIRECTION;
|
||
|
else
|
||
|
#endif
|
||
|
#ifdef SMALL_SIZE
|
||
|
if ( size <= SMALL_SIZE )
|
||
|
fb = zone->freelist_small.DIRECTION;
|
||
|
else
|
||
|
#endif
|
||
|
#ifdef MEDIUM_SIZE
|
||
|
if ( size <= MEDIUM_SIZE )
|
||
|
fb = zone->freelist_medium.DIRECTION;
|
||
|
else
|
||
|
#endif
|
||
|
fb = zone->freelist.DIRECTION;
|
||
|
|
||
|
for ( ;; ) {
|
||
|
// not found, allocate new segment?
|
||
|
if ( fb == &zone->freelist ) {
|
||
|
fb = NewBlock( zone, size );
|
||
|
} else {
|
||
|
#ifdef TINY_SIZE
|
||
|
if ( fb == &zone->freelist_tiny ) {
|
||
|
fb = zone->freelist_small.DIRECTION;
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
#ifdef SMALL_SIZE
|
||
|
if ( fb == &zone->freelist_small ) {
|
||
|
fb = zone->freelist_medium.DIRECTION;
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
#ifdef MEDIUM_SIZE
|
||
|
if ( fb == &zone->freelist_medium ) {
|
||
|
fb = zone->freelist.DIRECTION;
|
||
|
continue;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
base = (memblock_t*)( (byte*) fb - sizeof( *base ) );
|
||
|
fb = fb->DIRECTION;
|
||
|
if ( base->size >= size ) {
|
||
|
return base;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
#endif // USE_MULTI_SEGMENT
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_ClearZone
|
||
|
========================
|
||
|
*/
|
||
|
static void Z_ClearZone( memzone_t *zone, memzone_t *head, int size, int segnum ) {
|
||
|
memblock_t *block;
|
||
|
int min_fragment;
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
min_fragment = sizeof( memblock_t ) + sizeof( freeblock_t );
|
||
|
#else
|
||
|
min_fragment = sizeof( memblock_t );
|
||
|
#endif
|
||
|
|
||
|
if ( minfragment < min_fragment ) {
|
||
|
// in debug mode size of memblock_t may exceed MINFRAGMENT
|
||
|
minfragment = PAD( min_fragment, sizeof( intptr_t ) );
|
||
|
Com_DPrintf( "zone.minfragment adjusted to %i bytes\n", minfragment );
|
||
|
}
|
||
|
|
||
|
// set the entire zone to one free block
|
||
|
zone->blocklist.next = zone->blocklist.prev = block = (memblock_t *)( zone + 1 );
|
||
|
zone->blocklist.tag = TAG_GENERAL; // in use block
|
||
|
zone->blocklist.id = -ZONEID;
|
||
|
zone->blocklist.size = 0;
|
||
|
#ifndef USE_MULTI_SEGMENT
|
||
|
zone->rover = block;
|
||
|
#endif
|
||
|
zone->size = size;
|
||
|
zone->used = 0;
|
||
|
|
||
|
block->prev = block->next = &zone->blocklist;
|
||
|
block->tag = TAG_FREE; // free block
|
||
|
block->id = ZONEID;
|
||
|
|
||
|
block->size = size - sizeof(memzone_t);
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
InitFree( &zone->freelist );
|
||
|
zone->freelist.next = zone->freelist.prev = &zone->freelist;
|
||
|
|
||
|
InitFree( &zone->freelist_medium );
|
||
|
zone->freelist_medium.next = zone->freelist_medium.prev = &zone->freelist_medium;
|
||
|
|
||
|
InitFree( &zone->freelist_small );
|
||
|
zone->freelist_small.next = zone->freelist_small.prev = &zone->freelist_small;
|
||
|
|
||
|
InitFree( &zone->freelist_tiny );
|
||
|
zone->freelist_tiny.next = zone->freelist_tiny.prev = &zone->freelist_tiny;
|
||
|
|
||
|
InsertFree( zone, block );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_AvailableZoneMemory
|
||
|
========================
|
||
|
*/
|
||
|
static int Z_AvailableZoneMemory( const memzone_t *zone ) {
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
return (1024*1024*1024); // unlimited
|
||
|
#else
|
||
|
return zone->size - zone->used;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_AvailableMemory
|
||
|
========================
|
||
|
*/
|
||
|
int Z_AvailableMemory( void ) {
|
||
|
return Z_AvailableZoneMemory( mainzone );
|
||
|
}
|
||
|
|
||
|
|
||
|
static void MergeBlock( memblock_t *curr_free, const memblock_t *next )
|
||
|
{
|
||
|
curr_free->size += next->size;
|
||
|
curr_free->next = next->next;
|
||
|
curr_free->next->prev = curr_free;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_Free
|
||
|
========================
|
||
|
*/
|
||
|
void Z_Free( void *ptr ) {
|
||
|
memblock_t *block, *other;
|
||
|
memzone_t *zone;
|
||
|
|
||
|
if (!ptr) {
|
||
|
Com_Error( ERR_DROP, "Z_Free: NULL pointer" );
|
||
|
}
|
||
|
|
||
|
block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
|
||
|
if (block->id != ZONEID) {
|
||
|
Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" );
|
||
|
}
|
||
|
|
||
|
if (block->tag == TAG_FREE) {
|
||
|
Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" );
|
||
|
}
|
||
|
|
||
|
// if static memory
|
||
|
#ifdef USE_STATIC_TAGS
|
||
|
if (block->tag == TAG_STATIC) {
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// check the memory trash tester
|
||
|
#ifdef USE_TRASH_TEST
|
||
|
if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) {
|
||
|
Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( block->tag == TAG_SMALL ) {
|
||
|
zone = smallzone;
|
||
|
} else {
|
||
|
zone = mainzone;
|
||
|
}
|
||
|
|
||
|
zone->used -= block->size;
|
||
|
|
||
|
// set the block to something that should cause problems
|
||
|
// if it is referenced...
|
||
|
Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) );
|
||
|
|
||
|
block->tag = TAG_FREE; // mark as free
|
||
|
block->id = ZONEID;
|
||
|
|
||
|
other = block->prev;
|
||
|
if ( other->tag == TAG_FREE ) {
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
RemoveFree( other );
|
||
|
#endif
|
||
|
// merge with previous free block
|
||
|
MergeBlock( other, block );
|
||
|
#ifndef USE_MULTI_SEGMENT
|
||
|
if ( block == zone->rover ) {
|
||
|
zone->rover = other;
|
||
|
}
|
||
|
#endif
|
||
|
block = other;
|
||
|
}
|
||
|
|
||
|
#ifndef USE_MULTI_SEGMENT
|
||
|
zone->rover = block;
|
||
|
#endif
|
||
|
|
||
|
other = block->next;
|
||
|
if ( other->tag == TAG_FREE ) {
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
RemoveFree( other );
|
||
|
#endif
|
||
|
// merge the next free block onto the end
|
||
|
MergeBlock( block, other );
|
||
|
}
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
InsertFree( zone, block );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Z_FreeTags
|
||
|
================
|
||
|
*/
|
||
|
int Z_FreeTags( memtag_t tag ) {
|
||
|
int count;
|
||
|
memzone_t *zone;
|
||
|
memblock_t *block, *freed;
|
||
|
|
||
|
if ( tag == TAG_STATIC ) {
|
||
|
Com_Error( ERR_FATAL, "Z_FreeTags( TAG_STATIC )" );
|
||
|
return 0;
|
||
|
} else if ( tag == TAG_SMALL ) {
|
||
|
zone = smallzone;
|
||
|
} else {
|
||
|
zone = mainzone;
|
||
|
}
|
||
|
|
||
|
count = 0;
|
||
|
for ( block = zone->blocklist.next ; ; ) {
|
||
|
if ( block->tag == tag && block->id == ZONEID ) {
|
||
|
if ( block->prev->tag == TAG_FREE )
|
||
|
freed = block->prev; // current block will be merged with previous
|
||
|
else
|
||
|
freed = block; // will leave in place
|
||
|
Z_Free( (void*)( block + 1 ) );
|
||
|
block = freed;
|
||
|
count++;
|
||
|
}
|
||
|
if ( block->next == &zone->blocklist ) {
|
||
|
break; // all blocks have been hit
|
||
|
}
|
||
|
block = block->next;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Z_TagMalloc
|
||
|
================
|
||
|
*/
|
||
|
#ifdef ZONE_DEBUG
|
||
|
void *Z_TagMallocDebug( int size, memtag_t tag, char *label, char *file, int line ) {
|
||
|
int allocSize;
|
||
|
#else
|
||
|
void *Z_TagMalloc( int size, memtag_t tag ) {
|
||
|
#endif
|
||
|
int extra;
|
||
|
#ifndef USE_MULTI_SEGMENT
|
||
|
memblock_t *start, *rover;
|
||
|
#endif
|
||
|
memblock_t *base;
|
||
|
memzone_t *zone;
|
||
|
|
||
|
if ( tag == TAG_FREE ) {
|
||
|
Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use with TAG_FREE" );
|
||
|
}
|
||
|
|
||
|
if ( tag == TAG_SMALL ) {
|
||
|
zone = smallzone;
|
||
|
} else {
|
||
|
zone = mainzone;
|
||
|
}
|
||
|
|
||
|
#ifdef ZONE_DEBUG
|
||
|
allocSize = size;
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
if ( size < (sizeof( freeblock_t ) ) ) {
|
||
|
size = (sizeof( freeblock_t ) );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// scan through the block list looking for the first free block
|
||
|
// of sufficient size
|
||
|
//
|
||
|
size += sizeof( *base ); // account for size of block header
|
||
|
#ifdef USE_TRASH_TEST
|
||
|
size += 4; // space for memory trash tester
|
||
|
#endif
|
||
|
|
||
|
size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary
|
||
|
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
base = SearchFree( zone, size );
|
||
|
|
||
|
RemoveFree( base );
|
||
|
#else
|
||
|
|
||
|
base = rover = zone->rover;
|
||
|
start = base->prev;
|
||
|
|
||
|
do {
|
||
|
if ( rover == start ) {
|
||
|
// scanned all the way around the list
|
||
|
#ifdef ZONE_DEBUG
|
||
|
Z_LogHeap();
|
||
|
Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone: %s, line: %d (%s)",
|
||
|
size, zone == smallzone ? "small" : "main", file, line, label );
|
||
|
#else
|
||
|
Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",
|
||
|
size, zone == smallzone ? "small" : "main" );
|
||
|
#endif
|
||
|
return NULL;
|
||
|
}
|
||
|
if ( rover->tag != TAG_FREE ) {
|
||
|
base = rover = rover->next;
|
||
|
} else {
|
||
|
rover = rover->next;
|
||
|
}
|
||
|
} while (base->tag != TAG_FREE || base->size < size);
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// found a block big enough
|
||
|
//
|
||
|
extra = base->size - size;
|
||
|
if ( extra >= minfragment ) {
|
||
|
memblock_t *fragment;
|
||
|
// there will be a free fragment after the allocated block
|
||
|
fragment = (memblock_t *)( (byte *)base + size );
|
||
|
fragment->size = extra;
|
||
|
fragment->tag = TAG_FREE; // free block
|
||
|
fragment->id = ZONEID;
|
||
|
fragment->prev = base;
|
||
|
fragment->next = base->next;
|
||
|
fragment->next->prev = fragment;
|
||
|
base->next = fragment;
|
||
|
base->size = size;
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
InsertFree( zone, fragment );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#ifndef USE_MULTI_SEGMENT
|
||
|
zone->rover = base->next; // next allocation will start looking here
|
||
|
#endif
|
||
|
zone->used += base->size;
|
||
|
|
||
|
base->tag = tag; // no longer a free block
|
||
|
base->id = ZONEID;
|
||
|
|
||
|
#ifdef ZONE_DEBUG
|
||
|
base->d.label = label;
|
||
|
base->d.file = file;
|
||
|
base->d.line = line;
|
||
|
base->d.allocSize = allocSize;
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_TRASH_TEST
|
||
|
// marker for memory trash testing
|
||
|
*(int *)((byte *)base + base->size - 4) = ZONEID;
|
||
|
#endif
|
||
|
|
||
|
return (void *) ( base + 1 );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_Malloc
|
||
|
========================
|
||
|
*/
|
||
|
#ifdef ZONE_DEBUG
|
||
|
void *Z_MallocDebug( int size, char *label, char *file, int line ) {
|
||
|
#else
|
||
|
void *Z_Malloc( int size ) {
|
||
|
#endif
|
||
|
void *buf;
|
||
|
|
||
|
//Z_CheckHeap (); // DEBUG
|
||
|
|
||
|
#ifdef ZONE_DEBUG
|
||
|
buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line );
|
||
|
#else
|
||
|
buf = Z_TagMalloc( size, TAG_GENERAL );
|
||
|
#endif
|
||
|
Com_Memset( buf, 0, size );
|
||
|
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
S_Malloc
|
||
|
========================
|
||
|
*/
|
||
|
#ifdef ZONE_DEBUG
|
||
|
void *S_MallocDebug( int size, char *label, char *file, int line ) {
|
||
|
return Z_TagMallocDebug( size, TAG_SMALL, label, file, line );
|
||
|
}
|
||
|
#else
|
||
|
void *S_Malloc( int size ) {
|
||
|
return Z_TagMalloc( size, TAG_SMALL );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_CheckHeap
|
||
|
========================
|
||
|
*/
|
||
|
static void Z_CheckHeap( void ) {
|
||
|
const memblock_t *block;
|
||
|
const memzone_t *zone;
|
||
|
|
||
|
zone = mainzone;
|
||
|
for ( block = zone->blocklist.next ; ; ) {
|
||
|
if ( block->next == &zone->blocklist ) {
|
||
|
break; // all blocks have been hit
|
||
|
}
|
||
|
if ( (byte *)block + block->size != (byte *)block->next) {
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
const memblock_t *next = block->next;
|
||
|
if ( next->size == 0 && next->id == -ZONEID && next->tag == TAG_GENERAL ) {
|
||
|
block = next; // new zone segment
|
||
|
} else
|
||
|
#endif
|
||
|
Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block" );
|
||
|
}
|
||
|
if ( block->next->prev != block) {
|
||
|
Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link" );
|
||
|
}
|
||
|
if ( block->tag == TAG_FREE && block->next->tag == TAG_FREE ) {
|
||
|
Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks" );
|
||
|
}
|
||
|
block = block->next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_LogZoneHeap
|
||
|
========================
|
||
|
*/
|
||
|
static void Z_LogZoneHeap( memzone_t *zone, const char *name ) {
|
||
|
#ifdef ZONE_DEBUG
|
||
|
char dump[32], *ptr;
|
||
|
int i, j;
|
||
|
#endif
|
||
|
memblock_t *block;
|
||
|
char buf[4096];
|
||
|
int size, allocSize, numBlocks;
|
||
|
int len;
|
||
|
|
||
|
if ( logfile == FS_INVALID_HANDLE || !FS_Initialized() )
|
||
|
return;
|
||
|
|
||
|
size = numBlocks = 0;
|
||
|
#ifdef ZONE_DEBUG
|
||
|
allocSize = 0;
|
||
|
#endif
|
||
|
len = Com_sprintf( buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name );
|
||
|
FS_Write( buf, len, logfile );
|
||
|
for ( block = zone->blocklist.next ; ; ) {
|
||
|
if ( block->tag != TAG_FREE ) {
|
||
|
#ifdef ZONE_DEBUG
|
||
|
ptr = ((char *) block) + sizeof(memblock_t);
|
||
|
j = 0;
|
||
|
for (i = 0; i < 20 && i < block->d.allocSize; i++) {
|
||
|
if (ptr[i] >= 32 && ptr[i] < 127) {
|
||
|
dump[j++] = ptr[i];
|
||
|
}
|
||
|
else {
|
||
|
dump[j++] = '_';
|
||
|
}
|
||
|
}
|
||
|
dump[j] = '\0';
|
||
|
len = Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump);
|
||
|
FS_Write( buf, len, logfile );
|
||
|
allocSize += block->d.allocSize;
|
||
|
#endif
|
||
|
size += block->size;
|
||
|
numBlocks++;
|
||
|
}
|
||
|
if ( block->next == &zone->blocklist ) {
|
||
|
break; // all blocks have been hit
|
||
|
}
|
||
|
block = block->next;
|
||
|
}
|
||
|
#ifdef ZONE_DEBUG
|
||
|
// subtract debug memory
|
||
|
size -= numBlocks * sizeof(zonedebug_t);
|
||
|
#else
|
||
|
allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment
|
||
|
#endif
|
||
|
len = Com_sprintf( buf, sizeof( buf ), "%d %s memory in %d blocks\r\n", size, name, numBlocks );
|
||
|
FS_Write( buf, len, logfile );
|
||
|
len = Com_sprintf( buf, sizeof( buf ), "%d %s memory overhead\r\n", size - allocSize, name );
|
||
|
FS_Write( buf, len, logfile );
|
||
|
FS_Flush( logfile );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
Z_LogHeap
|
||
|
========================
|
||
|
*/
|
||
|
void Z_LogHeap( void ) {
|
||
|
Z_LogZoneHeap( mainzone, "MAIN" );
|
||
|
Z_LogZoneHeap( smallzone, "SMALL" );
|
||
|
}
|
||
|
|
||
|
#ifdef USE_STATIC_TAGS
|
||
|
|
||
|
// static mem blocks to reduce a lot of small zone overhead
|
||
|
typedef struct memstatic_s {
|
||
|
memblock_t b;
|
||
|
byte mem[2];
|
||
|
} memstatic_t;
|
||
|
|
||
|
#define MEM_STATIC(chr) { { NULL, NULL, PAD(sizeof(memstatic_t),4), TAG_STATIC, ZONEID }, {chr,'\0'} }
|
||
|
|
||
|
static const memstatic_t emptystring =
|
||
|
MEM_STATIC( '\0' );
|
||
|
|
||
|
static const memstatic_t numberstring[] = {
|
||
|
MEM_STATIC( '0' ),
|
||
|
MEM_STATIC( '1' ),
|
||
|
MEM_STATIC( '2' ),
|
||
|
MEM_STATIC( '3' ),
|
||
|
MEM_STATIC( '4' ),
|
||
|
MEM_STATIC( '5' ),
|
||
|
MEM_STATIC( '6' ),
|
||
|
MEM_STATIC( '7' ),
|
||
|
MEM_STATIC( '8' ),
|
||
|
MEM_STATIC( '9' )
|
||
|
};
|
||
|
#endif // USE_STATIC_TAGS
|
||
|
|
||
|
/*
|
||
|
========================
|
||
|
CopyString
|
||
|
|
||
|
NOTE: never write over the memory CopyString returns because
|
||
|
memory from a memstatic_t might be returned
|
||
|
========================
|
||
|
*/
|
||
|
char *CopyString( const char *in ) {
|
||
|
char *out;
|
||
|
#ifdef USE_STATIC_TAGS
|
||
|
if (!in[0]) {
|
||
|
return ((char *)&emptystring) + sizeof(memblock_t);
|
||
|
}
|
||
|
else if (!in[1]) {
|
||
|
if (in[0] >= '0' && in[0] <= '9') {
|
||
|
return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
out = S_Malloc (strlen(in)+1);
|
||
|
strcpy (out, in);
|
||
|
return out;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
Goals:
|
||
|
reproducible without history effects -- no out of memory errors on weird map to map changes
|
||
|
allow restarting of the client without fragmentation
|
||
|
minimize total pages in use at run time
|
||
|
minimize total pages needed during load time
|
||
|
|
||
|
Single block of memory with stack allocators coming from both ends towards the middle.
|
||
|
|
||
|
One side is designated the temporary memory allocator.
|
||
|
|
||
|
Temporary memory can be allocated and freed in any order.
|
||
|
|
||
|
A highwater mark is kept of the most in use at any time.
|
||
|
|
||
|
When there is no temporary memory allocated, the permanent and temp sides
|
||
|
can be switched, allowing the already touched temp memory to be used for
|
||
|
permanent storage.
|
||
|
|
||
|
Temp memory must never be allocated on two ends at once, or fragmentation
|
||
|
could occur.
|
||
|
|
||
|
If we have any in-use temp memory, additional temp allocations must come from
|
||
|
that side.
|
||
|
|
||
|
If not, we can choose to make either side the new temp side and push future
|
||
|
permanent allocations to the other side. Permanent allocations should be
|
||
|
kept on the side that has the current greatest wasted highwater mark.
|
||
|
|
||
|
==============================================================================
|
||
|
*/
|
||
|
|
||
|
|
||
|
#define HUNK_MAGIC 0x89537892
|
||
|
#define HUNK_FREE_MAGIC 0x89537893
|
||
|
|
||
|
typedef struct {
|
||
|
unsigned int magic;
|
||
|
unsigned int size;
|
||
|
} hunkHeader_t;
|
||
|
|
||
|
typedef struct {
|
||
|
int mark;
|
||
|
int permanent;
|
||
|
int temp;
|
||
|
int tempHighwater;
|
||
|
} hunkUsed_t;
|
||
|
|
||
|
typedef struct hunkblock_s {
|
||
|
int size;
|
||
|
byte printed;
|
||
|
struct hunkblock_s *next;
|
||
|
const char *label;
|
||
|
const char *file;
|
||
|
int line;
|
||
|
} hunkblock_t;
|
||
|
|
||
|
static hunkblock_t *hunkblocks;
|
||
|
|
||
|
static hunkUsed_t hunk_low, hunk_high;
|
||
|
static hunkUsed_t *hunk_permanent, *hunk_temp;
|
||
|
|
||
|
static byte *s_hunkData = NULL;
|
||
|
static int s_hunkTotal;
|
||
|
|
||
|
static const char *tagName[ TAG_COUNT ] = {
|
||
|
"FREE",
|
||
|
"GENERAL",
|
||
|
"PACK",
|
||
|
"SEARCH-PATH",
|
||
|
"SEARCH-PACK",
|
||
|
"SEARCH-DIR",
|
||
|
"BOTLIB",
|
||
|
"RENDERER",
|
||
|
"CLIENTS",
|
||
|
"SMALL",
|
||
|
"STATIC"
|
||
|
};
|
||
|
|
||
|
typedef struct zone_stats_s {
|
||
|
int zoneSegments;
|
||
|
int zoneBlocks;
|
||
|
int zoneBytes;
|
||
|
int botlibBytes;
|
||
|
int rendererBytes;
|
||
|
int freeBytes;
|
||
|
int freeBlocks;
|
||
|
int freeSmallest;
|
||
|
int freeLargest;
|
||
|
} zone_stats_t;
|
||
|
|
||
|
|
||
|
static void Zone_Stats( const char *name, const memzone_t *z, qboolean printDetails, zone_stats_t *stats )
|
||
|
{
|
||
|
const memblock_t *block;
|
||
|
const memzone_t *zone;
|
||
|
zone_stats_t st;
|
||
|
|
||
|
memset( &st, 0, sizeof( st ) );
|
||
|
zone = z;
|
||
|
st.zoneSegments = 1;
|
||
|
st.freeSmallest = 0x7FFFFFFF;
|
||
|
|
||
|
//if ( printDetails ) {
|
||
|
// Com_Printf( "---------- %s zone segment #%i ----------\n", name, zone->segnum );
|
||
|
//}
|
||
|
|
||
|
for ( block = zone->blocklist.next ; ; ) {
|
||
|
if ( printDetails ) {
|
||
|
int tag = block->tag;
|
||
|
Com_Printf( "block:%p size:%8i tag: %s\n", (void *)block, block->size,
|
||
|
(unsigned)tag < TAG_COUNT ? tagName[ tag ] : va( "%i", tag ) );
|
||
|
}
|
||
|
if ( block->tag != TAG_FREE ) {
|
||
|
st.zoneBytes += block->size;
|
||
|
st.zoneBlocks++;
|
||
|
if ( block->tag == TAG_BOTLIB ) {
|
||
|
st.botlibBytes += block->size;
|
||
|
} else if ( block->tag == TAG_RENDERER ) {
|
||
|
st.rendererBytes += block->size;
|
||
|
}
|
||
|
} else {
|
||
|
st.freeBytes += block->size;
|
||
|
st.freeBlocks++;
|
||
|
if ( block->size > st.freeLargest )
|
||
|
st.freeLargest = block->size;
|
||
|
if ( block->size < st.freeSmallest )
|
||
|
st.freeSmallest = block->size;
|
||
|
}
|
||
|
if ( block->next == &zone->blocklist ) {
|
||
|
break; // all blocks have been hit
|
||
|
}
|
||
|
if ( (byte *)block + block->size != (byte *)block->next) {
|
||
|
#ifdef USE_MULTI_SEGMENT
|
||
|
const memblock_t *next = block->next;
|
||
|
if ( next->size == 0 && next->id == -ZONEID && next->tag == TAG_GENERAL ) {
|
||
|
st.zoneSegments++;
|
||
|
if ( printDetails ) {
|
||
|
Com_Printf( "---------- %s zone segment #%i ----------\n", name, st.zoneSegments );
|
||
|
}
|
||
|
block = next->next;
|
||
|
continue;
|
||
|
} else
|
||
|
#endif
|
||
|
Com_Printf( "ERROR: block size does not touch the next block\n" );
|
||
|
}
|
||
|
if ( block->next->prev != block) {
|
||
|
Com_Printf( "ERROR: next block doesn't have proper back link\n" );
|
||
|
}
|
||
|
if ( block->tag == TAG_FREE && block->next->tag == TAG_FREE ) {
|
||
|
Com_Printf( "ERROR: two consecutive free blocks\n" );
|
||
|
}
|
||
|
block = block->next;
|
||
|
}
|
||
|
|
||
|
// export stats
|
||
|
if ( stats ) {
|
||
|
memcpy( stats, &st, sizeof( *stats ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_Meminfo_f
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_Meminfo_f( void ) {
|
||
|
zone_stats_t st;
|
||
|
int unused;
|
||
|
|
||
|
Com_Printf( "%8i bytes total hunk\n", s_hunkTotal );
|
||
|
Com_Printf( "\n" );
|
||
|
Com_Printf( "%8i low mark\n", hunk_low.mark );
|
||
|
Com_Printf( "%8i low permanent\n", hunk_low.permanent );
|
||
|
if ( hunk_low.temp != hunk_low.permanent ) {
|
||
|
Com_Printf( "%8i low temp\n", hunk_low.temp );
|
||
|
}
|
||
|
Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater );
|
||
|
Com_Printf( "\n" );
|
||
|
Com_Printf( "%8i high mark\n", hunk_high.mark );
|
||
|
Com_Printf( "%8i high permanent\n", hunk_high.permanent );
|
||
|
if ( hunk_high.temp != hunk_high.permanent ) {
|
||
|
Com_Printf( "%8i high temp\n", hunk_high.temp );
|
||
|
}
|
||
|
Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater );
|
||
|
Com_Printf( "\n" );
|
||
|
Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent );
|
||
|
unused = 0;
|
||
|
if ( hunk_low.tempHighwater > hunk_low.permanent ) {
|
||
|
unused += hunk_low.tempHighwater - hunk_low.permanent;
|
||
|
}
|
||
|
if ( hunk_high.tempHighwater > hunk_high.permanent ) {
|
||
|
unused += hunk_high.tempHighwater - hunk_high.permanent;
|
||
|
}
|
||
|
Com_Printf( "%8i unused highwater\n", unused );
|
||
|
Com_Printf( "\n" );
|
||
|
|
||
|
Zone_Stats( "main", mainzone, !Q_stricmp( Cmd_Argv(1), "main" ) || !Q_stricmp( Cmd_Argv(1), "all" ), &st );
|
||
|
Com_Printf( "%8i bytes total main zone\n\n", mainzone->size );
|
||
|
Com_Printf( "%8i bytes in %i main zone blocks%s\n", st.zoneBytes, st.zoneBlocks,
|
||
|
st.zoneSegments > 1 ? va( " and %i segments", st.zoneSegments ) : "" );
|
||
|
Com_Printf( " %8i bytes in botlib\n", st.botlibBytes );
|
||
|
Com_Printf( " %8i bytes in renderer\n", st.rendererBytes );
|
||
|
Com_Printf( " %8i bytes in other\n", st.zoneBytes - ( st.botlibBytes + st.rendererBytes ) );
|
||
|
Com_Printf( " %8i bytes in %i free blocks\n", st.freeBytes, st.freeBlocks );
|
||
|
if ( st.freeBlocks > 1 ) {
|
||
|
Com_Printf( " (largest: %i bytes, smallest: %i bytes)\n\n", st.freeLargest, st.freeSmallest );
|
||
|
}
|
||
|
|
||
|
Zone_Stats( "small", smallzone, !Q_stricmp( Cmd_Argv(1), "small" ) || !Q_stricmp( Cmd_Argv(1), "all" ), &st );
|
||
|
Com_Printf( "%8i bytes total small zone\n\n", smallzone->size );
|
||
|
Com_Printf( "%8i bytes in %i small zone blocks%s\n", st.zoneBytes, st.zoneBlocks,
|
||
|
st.zoneSegments > 1 ? va( " and %i segments", st.zoneSegments ) : "" );
|
||
|
Com_Printf( " %8i bytes in %i free blocks\n", st.freeBytes, st.freeBlocks );
|
||
|
if ( st.freeBlocks > 1 ) {
|
||
|
Com_Printf( " (largest: %i bytes, smallest: %i bytes)\n\n", st.freeLargest, st.freeSmallest );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Com_TouchMemory
|
||
|
|
||
|
Touch all known used data to make sure it is paged in
|
||
|
===============
|
||
|
*/
|
||
|
unsigned int Com_TouchMemory( void ) {
|
||
|
const memblock_t *block;
|
||
|
const memzone_t *zone;
|
||
|
int start, end;
|
||
|
int i, j;
|
||
|
unsigned int sum;
|
||
|
|
||
|
Z_CheckHeap();
|
||
|
|
||
|
start = Sys_Milliseconds();
|
||
|
|
||
|
sum = 0;
|
||
|
|
||
|
j = hunk_low.permanent >> 2;
|
||
|
for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
|
||
|
sum += ((unsigned int *)s_hunkData)[i];
|
||
|
}
|
||
|
|
||
|
i = ( s_hunkTotal - hunk_high.permanent ) >> 2;
|
||
|
j = hunk_high.permanent >> 2;
|
||
|
for ( ; i < j ; i+=64 ) { // only need to touch each page
|
||
|
sum += ((unsigned int *)s_hunkData)[i];
|
||
|
}
|
||
|
|
||
|
zone = mainzone;
|
||
|
for (block = zone->blocklist.next ; ; block = block->next) {
|
||
|
if ( block->tag != TAG_FREE ) {
|
||
|
j = block->size >> 2;
|
||
|
for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
|
||
|
sum += ((unsigned int *)block)[i];
|
||
|
}
|
||
|
}
|
||
|
if ( block->next == &zone->blocklist ) {
|
||
|
break; // all blocks have been hit
|
||
|
}
|
||
|
}
|
||
|
|
||
|
end = Sys_Milliseconds();
|
||
|
|
||
|
Com_Printf( "Com_TouchMemory: %i msec\n", end - start );
|
||
|
|
||
|
return sum; // just to silent compiler warning
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_InitSmallZoneMemory
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_InitSmallZoneMemory( void ) {
|
||
|
static byte s_buf[ 512 * 1024 ];
|
||
|
int smallZoneSize;
|
||
|
|
||
|
smallZoneSize = sizeof( s_buf );
|
||
|
Com_Memset( s_buf, 0, smallZoneSize );
|
||
|
smallzone = (memzone_t *)s_buf;
|
||
|
Z_ClearZone( smallzone, smallzone, smallZoneSize, 1 );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_InitZoneMemory
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_InitZoneMemory( void ) {
|
||
|
int mainZoneSize;
|
||
|
cvar_t *cv;
|
||
|
|
||
|
// Please note: com_zoneMegs can only be set on the command line, and
|
||
|
// not in q3config.cfg or Com_StartupVariable, as they haven't been
|
||
|
// executed by this point. It's a chicken and egg problem. We need the
|
||
|
// memory manager configured to handle those places where you would
|
||
|
// configure the memory manager.
|
||
|
|
||
|
// allocate the random block zone
|
||
|
cv = Cvar_Get( "com_zoneMegs", XSTRING( DEF_COMZONEMEGS ), CVAR_LATCH | CVAR_ARCHIVE );
|
||
|
Cvar_CheckRange( cv, "1", NULL, CV_INTEGER );
|
||
|
Cvar_SetDescription( cv, "Initial amount of memory (RAM) allocated for the main block zone (in MB)." );
|
||
|
|
||
|
#ifndef USE_MULTI_SEGMENT
|
||
|
if ( cv->integer < DEF_COMZONEMEGS )
|
||
|
mainZoneSize = 1024 * 1024 * DEF_COMZONEMEGS;
|
||
|
else
|
||
|
#endif
|
||
|
mainZoneSize = cv->integer * 1024 * 1024;
|
||
|
|
||
|
mainzone = calloc( mainZoneSize, 1 );
|
||
|
if ( !mainzone ) {
|
||
|
Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", mainZoneSize / (1024*1024) );
|
||
|
}
|
||
|
Z_ClearZone( mainzone, mainzone, mainZoneSize, 1 );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_Log
|
||
|
=================
|
||
|
*/
|
||
|
void Hunk_Log( void ) {
|
||
|
hunkblock_t *block;
|
||
|
char buf[4096];
|
||
|
int size, numBlocks;
|
||
|
|
||
|
if ( logfile == FS_INVALID_HANDLE || !FS_Initialized() )
|
||
|
return;
|
||
|
|
||
|
size = 0;
|
||
|
numBlocks = 0;
|
||
|
Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n");
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
for (block = hunkblocks ; block; block = block->next) {
|
||
|
#ifdef HUNK_DEBUG
|
||
|
Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label);
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
#endif
|
||
|
size += block->size;
|
||
|
numBlocks++;
|
||
|
}
|
||
|
Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_SmallLog
|
||
|
=================
|
||
|
*/
|
||
|
#ifdef HUNK_DEBUG
|
||
|
void Hunk_SmallLog( void ) {
|
||
|
hunkblock_t *block, *block2;
|
||
|
char buf[4096];
|
||
|
int size, locsize, numBlocks;
|
||
|
|
||
|
if ( logfile == FS_INVALID_HANDLE || !FS_Initialized() )
|
||
|
return;
|
||
|
|
||
|
for (block = hunkblocks ; block; block = block->next) {
|
||
|
block->printed = qfalse;
|
||
|
}
|
||
|
size = 0;
|
||
|
numBlocks = 0;
|
||
|
Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n");
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
for (block = hunkblocks; block; block = block->next) {
|
||
|
if (block->printed) {
|
||
|
continue;
|
||
|
}
|
||
|
locsize = block->size;
|
||
|
for (block2 = block->next; block2; block2 = block2->next) {
|
||
|
if (block->line != block2->line) {
|
||
|
continue;
|
||
|
}
|
||
|
if (Q_stricmp(block->file, block2->file)) {
|
||
|
continue;
|
||
|
}
|
||
|
size += block2->size;
|
||
|
locsize += block2->size;
|
||
|
block2->printed = qtrue;
|
||
|
}
|
||
|
Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label);
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
size += block->size;
|
||
|
numBlocks++;
|
||
|
}
|
||
|
Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
|
||
|
FS_Write(buf, strlen(buf), logfile);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_InitHunkMemory
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_InitHunkMemory( void ) {
|
||
|
cvar_t *cv;
|
||
|
|
||
|
// make sure the file system has allocated and "not" freed any temp blocks
|
||
|
// this allows the config and product id files ( journal files too ) to be loaded
|
||
|
// by the file system without redundant routines in the file system utilizing different
|
||
|
// memory systems
|
||
|
if ( FS_LoadStack() != 0 ) {
|
||
|
Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero" );
|
||
|
}
|
||
|
|
||
|
// allocate the stack based hunk allocator
|
||
|
cv = Cvar_Get( "com_hunkMegs", XSTRING( DEF_COMHUNKMEGS ), CVAR_LATCH | CVAR_ARCHIVE );
|
||
|
Cvar_CheckRange( cv, XSTRING( MIN_COMHUNKMEGS ), NULL, CV_INTEGER );
|
||
|
Cvar_SetDescription( cv, "The size of the hunk memory segment." );
|
||
|
|
||
|
s_hunkTotal = cv->integer * 1024 * 1024;
|
||
|
|
||
|
s_hunkData = calloc( s_hunkTotal + 63, 1 );
|
||
|
if ( !s_hunkData ) {
|
||
|
Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) );
|
||
|
}
|
||
|
|
||
|
// cacheline align
|
||
|
s_hunkData = PADP( s_hunkData, 64 );
|
||
|
Hunk_Clear();
|
||
|
|
||
|
Cmd_AddCommand( "meminfo", Com_Meminfo_f );
|
||
|
#ifdef ZONE_DEBUG
|
||
|
Cmd_AddCommand( "zonelog", Z_LogHeap );
|
||
|
#endif
|
||
|
#ifdef HUNK_DEBUG
|
||
|
Cmd_AddCommand( "hunklog", Hunk_Log );
|
||
|
Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
====================
|
||
|
Hunk_MemoryRemaining
|
||
|
====================
|
||
|
*/
|
||
|
int Hunk_MemoryRemaining( void ) {
|
||
|
int low, high;
|
||
|
|
||
|
low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp;
|
||
|
high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp;
|
||
|
|
||
|
return s_hunkTotal - ( low + high );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===================
|
||
|
Hunk_SetMark
|
||
|
|
||
|
The server calls this after the level and game VM have been loaded
|
||
|
===================
|
||
|
*/
|
||
|
void Hunk_SetMark( void ) {
|
||
|
hunk_low.mark = hunk_low.permanent;
|
||
|
hunk_high.mark = hunk_high.permanent;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_ClearToMark
|
||
|
|
||
|
The client calls this before starting a vid_restart or snd_restart
|
||
|
=================
|
||
|
*/
|
||
|
void Hunk_ClearToMark( void ) {
|
||
|
hunk_low.permanent = hunk_low.temp = hunk_low.mark;
|
||
|
hunk_high.permanent = hunk_high.temp = hunk_high.mark;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_CheckMark
|
||
|
=================
|
||
|
*/
|
||
|
qboolean Hunk_CheckMark( void ) {
|
||
|
if( hunk_low.mark || hunk_high.mark ) {
|
||
|
return qtrue;
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
void CL_ShutdownCGame( void );
|
||
|
void CL_ShutdownUI( void );
|
||
|
void SV_ShutdownGameProgs( void );
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_Clear
|
||
|
|
||
|
The server calls this before shutting down or loading a new map
|
||
|
=================
|
||
|
*/
|
||
|
void Hunk_Clear( void ) {
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
CL_ShutdownCGame();
|
||
|
CL_ShutdownUI();
|
||
|
#endif
|
||
|
SV_ShutdownGameProgs();
|
||
|
#ifndef DEDICATED
|
||
|
CIN_CloseAllVideos();
|
||
|
#endif
|
||
|
hunk_low.mark = 0;
|
||
|
hunk_low.permanent = 0;
|
||
|
hunk_low.temp = 0;
|
||
|
hunk_low.tempHighwater = 0;
|
||
|
|
||
|
hunk_high.mark = 0;
|
||
|
hunk_high.permanent = 0;
|
||
|
hunk_high.temp = 0;
|
||
|
hunk_high.tempHighwater = 0;
|
||
|
|
||
|
hunk_permanent = &hunk_low;
|
||
|
hunk_temp = &hunk_high;
|
||
|
|
||
|
Com_Printf( "Hunk_Clear: reset the hunk ok\n" );
|
||
|
VM_Clear();
|
||
|
#ifdef HUNK_DEBUG
|
||
|
hunkblocks = NULL;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
static void Hunk_SwapBanks( void ) {
|
||
|
hunkUsed_t *swap;
|
||
|
|
||
|
// can't swap banks if there is any temp already allocated
|
||
|
if ( hunk_temp->temp != hunk_temp->permanent ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// if we have a larger highwater mark on this side, start making
|
||
|
// our permanent allocations here and use the other side for temp
|
||
|
if ( hunk_temp->tempHighwater - hunk_temp->permanent >
|
||
|
hunk_permanent->tempHighwater - hunk_permanent->permanent ) {
|
||
|
swap = hunk_temp;
|
||
|
hunk_temp = hunk_permanent;
|
||
|
hunk_permanent = swap;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_Alloc
|
||
|
|
||
|
Allocate permanent (until the hunk is cleared) memory
|
||
|
=================
|
||
|
*/
|
||
|
#ifdef HUNK_DEBUG
|
||
|
void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) {
|
||
|
#else
|
||
|
void *Hunk_Alloc( int size, ha_pref preference ) {
|
||
|
#endif
|
||
|
void *buf;
|
||
|
|
||
|
if ( s_hunkData == NULL)
|
||
|
{
|
||
|
Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" );
|
||
|
}
|
||
|
|
||
|
// can't do preference if there is any temp allocated
|
||
|
if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) {
|
||
|
Hunk_SwapBanks();
|
||
|
} else {
|
||
|
if (preference == h_low && hunk_permanent != &hunk_low) {
|
||
|
Hunk_SwapBanks();
|
||
|
} else if (preference == h_high && hunk_permanent != &hunk_high) {
|
||
|
Hunk_SwapBanks();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef HUNK_DEBUG
|
||
|
size += sizeof(hunkblock_t);
|
||
|
#endif
|
||
|
|
||
|
// round to cacheline
|
||
|
size = PAD( size, 64 );
|
||
|
|
||
|
if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) {
|
||
|
#ifdef HUNK_DEBUG
|
||
|
Hunk_Log();
|
||
|
Hunk_SmallLog();
|
||
|
|
||
|
Com_Error(ERR_DROP, "Hunk_Alloc failed on %i: %s, line: %d (%s)", size, file, line, label);
|
||
|
#else
|
||
|
Com_Error(ERR_DROP, "Hunk_Alloc failed on %i", size);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if ( hunk_permanent == &hunk_low ) {
|
||
|
buf = (void *)(s_hunkData + hunk_permanent->permanent);
|
||
|
hunk_permanent->permanent += size;
|
||
|
} else {
|
||
|
hunk_permanent->permanent += size;
|
||
|
buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
|
||
|
}
|
||
|
|
||
|
hunk_permanent->temp = hunk_permanent->permanent;
|
||
|
|
||
|
Com_Memset( buf, 0, size );
|
||
|
|
||
|
#ifdef HUNK_DEBUG
|
||
|
{
|
||
|
hunkblock_t *block;
|
||
|
|
||
|
block = (hunkblock_t *) buf;
|
||
|
block->size = size - sizeof(hunkblock_t);
|
||
|
block->file = file;
|
||
|
block->label = label;
|
||
|
block->line = line;
|
||
|
block->next = hunkblocks;
|
||
|
hunkblocks = block;
|
||
|
buf = ((byte *) buf) + sizeof(hunkblock_t);
|
||
|
}
|
||
|
#endif
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_AllocateTempMemory
|
||
|
|
||
|
This is used by the file loading system.
|
||
|
Multiple files can be loaded in temporary memory.
|
||
|
When the files-in-use count reaches zero, all temp memory will be deleted
|
||
|
=================
|
||
|
*/
|
||
|
void *Hunk_AllocateTempMemory( int size ) {
|
||
|
void *buf;
|
||
|
hunkHeader_t *hdr;
|
||
|
|
||
|
// return a Z_Malloc'd block if the hunk has not been initialized
|
||
|
// this allows the config and product id files ( journal files too ) to be loaded
|
||
|
// by the file system without redundant routines in the file system utilizing different
|
||
|
// memory systems
|
||
|
if ( s_hunkData == NULL )
|
||
|
{
|
||
|
return Z_Malloc(size);
|
||
|
}
|
||
|
|
||
|
Hunk_SwapBanks();
|
||
|
|
||
|
size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t );
|
||
|
|
||
|
if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) {
|
||
|
Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size );
|
||
|
}
|
||
|
|
||
|
if ( hunk_temp == &hunk_low ) {
|
||
|
buf = (void *)(s_hunkData + hunk_temp->temp);
|
||
|
hunk_temp->temp += size;
|
||
|
} else {
|
||
|
hunk_temp->temp += size;
|
||
|
buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp );
|
||
|
}
|
||
|
|
||
|
if ( hunk_temp->temp > hunk_temp->tempHighwater ) {
|
||
|
hunk_temp->tempHighwater = hunk_temp->temp;
|
||
|
}
|
||
|
|
||
|
hdr = (hunkHeader_t *)buf;
|
||
|
buf = (void *)(hdr+1);
|
||
|
|
||
|
hdr->magic = HUNK_MAGIC;
|
||
|
hdr->size = size;
|
||
|
|
||
|
// don't bother clearing, because we are going to load a file over it
|
||
|
return buf;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Hunk_FreeTempMemory
|
||
|
==================
|
||
|
*/
|
||
|
void Hunk_FreeTempMemory( void *buf ) {
|
||
|
hunkHeader_t *hdr;
|
||
|
|
||
|
// free with Z_Free if the hunk has not been initialized
|
||
|
// this allows the config and product id files ( journal files too ) to be loaded
|
||
|
// by the file system without redundant routines in the file system utilizing different
|
||
|
// memory systems
|
||
|
if ( s_hunkData == NULL )
|
||
|
{
|
||
|
Z_Free(buf);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
hdr = ( (hunkHeader_t *)buf ) - 1;
|
||
|
if ( hdr->magic != HUNK_MAGIC ) {
|
||
|
Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" );
|
||
|
}
|
||
|
|
||
|
hdr->magic = HUNK_FREE_MAGIC;
|
||
|
|
||
|
// this only works if the files are freed in stack order,
|
||
|
// otherwise the memory will stay around until Hunk_ClearTempMemory
|
||
|
if ( hunk_temp == &hunk_low ) {
|
||
|
if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) {
|
||
|
hunk_temp->temp -= hdr->size;
|
||
|
} else {
|
||
|
Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
|
||
|
}
|
||
|
} else {
|
||
|
if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) {
|
||
|
hunk_temp->temp -= hdr->size;
|
||
|
} else {
|
||
|
Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Hunk_ClearTempMemory
|
||
|
|
||
|
The temp space is no longer needed. If we have left more
|
||
|
touched but unused memory on this side, have future
|
||
|
permanent allocs use this side.
|
||
|
=================
|
||
|
*/
|
||
|
void Hunk_ClearTempMemory( void ) {
|
||
|
if ( s_hunkData != NULL ) {
|
||
|
hunk_temp->temp = hunk_temp->permanent;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
===================================================================
|
||
|
|
||
|
EVENTS AND JOURNALING
|
||
|
|
||
|
In addition to these events, .cfg files are also copied to the
|
||
|
journaled file
|
||
|
===================================================================
|
||
|
*/
|
||
|
|
||
|
#define MAX_PUSHED_EVENTS 256
|
||
|
static int com_pushedEventsHead = 0;
|
||
|
static int com_pushedEventsTail = 0;
|
||
|
static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS];
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_InitJournaling
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_InitJournaling( void ) {
|
||
|
if ( !com_journal->integer ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( com_journal->integer == 1 ) {
|
||
|
Com_Printf( "Journaling events\n" );
|
||
|
com_journalFile = FS_FOpenFileWrite( "journal.dat" );
|
||
|
com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" );
|
||
|
} else if ( com_journal->integer == 2 ) {
|
||
|
Com_Printf( "Replaying journaled events\n" );
|
||
|
FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue );
|
||
|
FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue );
|
||
|
}
|
||
|
|
||
|
if ( com_journalFile == FS_INVALID_HANDLE || com_journalDataFile == FS_INVALID_HANDLE ) {
|
||
|
Cvar_Set( "com_journal", "0" );
|
||
|
if ( com_journalFile != FS_INVALID_HANDLE ) {
|
||
|
FS_FCloseFile( com_journalFile );
|
||
|
com_journalFile = FS_INVALID_HANDLE;
|
||
|
}
|
||
|
if ( com_journalDataFile != FS_INVALID_HANDLE ) {
|
||
|
FS_FCloseFile( com_journalDataFile );
|
||
|
com_journalDataFile = FS_INVALID_HANDLE;
|
||
|
}
|
||
|
Com_Printf( "Couldn't open journal files\n" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
========================================================================
|
||
|
|
||
|
EVENT LOOP
|
||
|
|
||
|
========================================================================
|
||
|
*/
|
||
|
|
||
|
#define MAX_QUED_EVENTS 128
|
||
|
#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 )
|
||
|
|
||
|
static sysEvent_t eventQue[ MAX_QUED_EVENTS ];
|
||
|
static sysEvent_t *lastEvent = eventQue + MAX_QUED_EVENTS - 1;
|
||
|
static unsigned int eventHead = 0;
|
||
|
static unsigned int eventTail = 0;
|
||
|
|
||
|
static const char *Sys_EventName( sysEventType_t evType ) {
|
||
|
|
||
|
static const char *evNames[ SE_MAX ] = {
|
||
|
"SE_NONE",
|
||
|
"SE_KEY",
|
||
|
"SE_CHAR",
|
||
|
"SE_MOUSE",
|
||
|
"SE_JOYSTICK_AXIS",
|
||
|
"SE_CONSOLE"
|
||
|
};
|
||
|
|
||
|
if ( (unsigned)evType >= ARRAY_LEN( evNames ) ) {
|
||
|
return "SE_UNKNOWN";
|
||
|
} else {
|
||
|
return evNames[ evType ];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Sys_QueEvent
|
||
|
|
||
|
A time of 0 will get the current time
|
||
|
Ptr should either be null, or point to a block of data that can
|
||
|
be freed by the game later.
|
||
|
================
|
||
|
*/
|
||
|
void Sys_QueEvent( int evTime, sysEventType_t evType, int value, int value2, int ptrLength, void *ptr ) {
|
||
|
sysEvent_t *ev;
|
||
|
|
||
|
#if 0
|
||
|
Com_Printf( "%-10s: evTime=%i, evTail=%i, evHead=%i\n",
|
||
|
Sys_EventName( evType ), evTime, eventTail, eventHead );
|
||
|
#endif
|
||
|
|
||
|
if ( evTime == 0 ) {
|
||
|
evTime = Sys_Milliseconds();
|
||
|
}
|
||
|
|
||
|
// try to combine all sequential mouse moves in one event
|
||
|
if ( evType == SE_MOUSE && lastEvent->evType == SE_MOUSE && eventHead != eventTail ) {
|
||
|
lastEvent->evValue += value;
|
||
|
lastEvent->evValue2 += value2;
|
||
|
lastEvent->evTime = evTime;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ev = &eventQue[ eventHead & MASK_QUED_EVENTS ];
|
||
|
|
||
|
//Com_Printf( "%s(type=%s,keys=(%i,%i),time=%i): overflow\n", __func__, Sys_EventName( evType ), value, value2, evTime );
|
||
|
|
||
|
if ( eventHead - eventTail >= MAX_QUED_EVENTS ) {
|
||
|
Com_Printf( "%s(type=%s,keys=(%i,%i),time=%i): overflow\n", __func__, Sys_EventName( evType ), value, value2, evTime );
|
||
|
// we are discarding an event, but don't leak memory
|
||
|
if ( ev->evPtr ) {
|
||
|
Z_Free( ev->evPtr );
|
||
|
}
|
||
|
eventTail++;
|
||
|
}
|
||
|
|
||
|
eventHead++;
|
||
|
|
||
|
ev->evTime = evTime;
|
||
|
ev->evType = evType;
|
||
|
ev->evValue = value;
|
||
|
ev->evValue2 = value2;
|
||
|
ev->evPtrLength = ptrLength;
|
||
|
ev->evPtr = ptr;
|
||
|
|
||
|
lastEvent = ev;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Com_GetSystemEvent
|
||
|
================
|
||
|
*/
|
||
|
static sysEvent_t Com_GetSystemEvent( void )
|
||
|
{
|
||
|
sysEvent_t ev;
|
||
|
const char *s;
|
||
|
int evTime;
|
||
|
|
||
|
// return if we have data
|
||
|
if ( eventHead - eventTail > 0 )
|
||
|
return eventQue[ ( eventTail++ ) & MASK_QUED_EVENTS ];
|
||
|
|
||
|
Sys_SendKeyEvents();
|
||
|
|
||
|
evTime = Sys_Milliseconds();
|
||
|
|
||
|
// check for console commands
|
||
|
s = Sys_ConsoleInput();
|
||
|
if ( s )
|
||
|
{
|
||
|
char *b;
|
||
|
int len;
|
||
|
|
||
|
len = strlen( s ) + 1;
|
||
|
b = Z_Malloc( len );
|
||
|
strcpy( b, s );
|
||
|
Sys_QueEvent( evTime, SE_CONSOLE, 0, 0, len, b );
|
||
|
}
|
||
|
|
||
|
// return if we have data
|
||
|
if ( eventHead - eventTail > 0 )
|
||
|
return eventQue[ ( eventTail++ ) & MASK_QUED_EVENTS ];
|
||
|
|
||
|
// create an empty event to return
|
||
|
memset( &ev, 0, sizeof( ev ) );
|
||
|
ev.evTime = evTime;
|
||
|
|
||
|
return ev;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_GetRealEvent
|
||
|
=================
|
||
|
*/
|
||
|
static sysEvent_t Com_GetRealEvent( void ) {
|
||
|
|
||
|
// get or save an event from/to the journal file
|
||
|
if ( com_journalFile != FS_INVALID_HANDLE ) {
|
||
|
int r;
|
||
|
sysEvent_t ev;
|
||
|
|
||
|
if ( com_journal->integer == 2 ) {
|
||
|
Sys_SendKeyEvents();
|
||
|
r = FS_Read( &ev, sizeof(ev), com_journalFile );
|
||
|
if ( r != sizeof(ev) ) {
|
||
|
Com_Error( ERR_FATAL, "Error reading from journal file" );
|
||
|
}
|
||
|
if ( ev.evPtrLength ) {
|
||
|
ev.evPtr = Z_Malloc( ev.evPtrLength );
|
||
|
r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile );
|
||
|
if ( r != ev.evPtrLength ) {
|
||
|
Com_Error( ERR_FATAL, "Error reading from journal file" );
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
ev = Com_GetSystemEvent();
|
||
|
|
||
|
// write the journal value out if needed
|
||
|
if ( com_journal->integer == 1 ) {
|
||
|
r = FS_Write( &ev, sizeof(ev), com_journalFile );
|
||
|
if ( r != sizeof(ev) ) {
|
||
|
Com_Error( ERR_FATAL, "Error writing to journal file" );
|
||
|
}
|
||
|
if ( ev.evPtrLength ) {
|
||
|
r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile );
|
||
|
if ( r != ev.evPtrLength ) {
|
||
|
Com_Error( ERR_FATAL, "Error writing to journal file" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ev;
|
||
|
}
|
||
|
|
||
|
return Com_GetSystemEvent();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_InitPushEvent
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_InitPushEvent( void ) {
|
||
|
// clear the static buffer array
|
||
|
// this requires SE_NONE to be accepted as a valid but NOP event
|
||
|
memset( com_pushedEvents, 0, sizeof(com_pushedEvents) );
|
||
|
// reset counters while we are at it
|
||
|
// beware: GetEvent might still return an SE_NONE from the buffer
|
||
|
com_pushedEventsHead = 0;
|
||
|
com_pushedEventsTail = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_PushEvent
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_PushEvent( const sysEvent_t *event ) {
|
||
|
sysEvent_t *ev;
|
||
|
static int printedWarning = 0;
|
||
|
|
||
|
ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ];
|
||
|
|
||
|
if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) {
|
||
|
|
||
|
// don't print the warning constantly, or it can give time for more...
|
||
|
if ( !printedWarning ) {
|
||
|
printedWarning = qtrue;
|
||
|
Com_Printf( "WARNING: Com_PushEvent overflow\n" );
|
||
|
}
|
||
|
|
||
|
if ( ev->evPtr ) {
|
||
|
Z_Free( ev->evPtr );
|
||
|
}
|
||
|
com_pushedEventsTail++;
|
||
|
} else {
|
||
|
printedWarning = qfalse;
|
||
|
}
|
||
|
|
||
|
*ev = *event;
|
||
|
com_pushedEventsHead++;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_GetEvent
|
||
|
=================
|
||
|
*/
|
||
|
static sysEvent_t Com_GetEvent( void ) {
|
||
|
if ( com_pushedEventsHead - com_pushedEventsTail > 0 ) {
|
||
|
return com_pushedEvents[ (com_pushedEventsTail++) & (MAX_PUSHED_EVENTS-1) ];
|
||
|
}
|
||
|
return Com_GetRealEvent();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_RunAndTimeServerPacket
|
||
|
=================
|
||
|
*/
|
||
|
void Com_RunAndTimeServerPacket( const netadr_t *evFrom, msg_t *buf ) {
|
||
|
int t1, t2, msec;
|
||
|
|
||
|
t1 = 0;
|
||
|
|
||
|
if ( com_speeds->integer ) {
|
||
|
t1 = Sys_Milliseconds ();
|
||
|
}
|
||
|
|
||
|
SV_PacketEvent( evFrom, buf );
|
||
|
|
||
|
if ( com_speeds->integer ) {
|
||
|
t2 = Sys_Milliseconds ();
|
||
|
msec = t2 - t1;
|
||
|
if ( com_speeds->integer == 3 ) {
|
||
|
Com_Printf( "SV_PacketEvent time: %i\n", msec );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_EventLoop
|
||
|
|
||
|
Returns last event time
|
||
|
=================
|
||
|
*/
|
||
|
int Com_EventLoop( void ) {
|
||
|
sysEvent_t ev;
|
||
|
netadr_t evFrom;
|
||
|
byte bufData[ MAX_MSGLEN_BUF ];
|
||
|
msg_t buf;
|
||
|
|
||
|
MSG_Init( &buf, bufData, MAX_MSGLEN );
|
||
|
|
||
|
while ( 1 ) {
|
||
|
ev = Com_GetEvent();
|
||
|
|
||
|
// if no more events are available
|
||
|
if ( ev.evType == SE_NONE ) {
|
||
|
// manually send packet events for the loopback channel
|
||
|
#ifndef DEDICATED
|
||
|
while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) {
|
||
|
CL_PacketEvent( &evFrom, &buf );
|
||
|
}
|
||
|
#endif
|
||
|
while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) {
|
||
|
// if the server just shut down, flush the events
|
||
|
if ( com_sv_running->integer ) {
|
||
|
Com_RunAndTimeServerPacket( &evFrom, &buf );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ev.evTime;
|
||
|
}
|
||
|
|
||
|
|
||
|
switch ( ev.evType ) {
|
||
|
#ifndef DEDICATED
|
||
|
case SE_KEY:
|
||
|
CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime );
|
||
|
break;
|
||
|
case SE_CHAR:
|
||
|
CL_CharEvent( ev.evValue );
|
||
|
break;
|
||
|
case SE_MOUSE:
|
||
|
CL_MouseEvent( ev.evValue, ev.evValue2 /*, ev.evTime*/ );
|
||
|
break;
|
||
|
case SE_JOYSTICK_AXIS:
|
||
|
CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime );
|
||
|
break;
|
||
|
#endif
|
||
|
case SE_CONSOLE:
|
||
|
Cbuf_AddText( (char *)ev.evPtr );
|
||
|
Cbuf_AddText( "\n" );
|
||
|
break;
|
||
|
default:
|
||
|
Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// free any block data
|
||
|
if ( ev.evPtr ) {
|
||
|
Z_Free( ev.evPtr );
|
||
|
ev.evPtr = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0; // never reached
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Com_Milliseconds
|
||
|
|
||
|
Can be used for profiling, but will be journaled accurately
|
||
|
================
|
||
|
*/
|
||
|
int Com_Milliseconds( void ) {
|
||
|
|
||
|
sysEvent_t ev;
|
||
|
|
||
|
// get events and push them until we get a null event with the current time
|
||
|
do {
|
||
|
ev = Com_GetRealEvent();
|
||
|
if ( ev.evType != SE_NONE ) {
|
||
|
Com_PushEvent( &ev );
|
||
|
}
|
||
|
} while ( ev.evType != SE_NONE );
|
||
|
|
||
|
return ev.evTime;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
Com_Error_f
|
||
|
|
||
|
Just throw a fatal error to
|
||
|
test error shutdown procedures
|
||
|
=============
|
||
|
*/
|
||
|
static void __attribute__((__noreturn__)) Com_Error_f (void) {
|
||
|
if ( Cmd_Argc() > 1 ) {
|
||
|
Com_Error( ERR_DROP, "Testing drop error" );
|
||
|
} else {
|
||
|
Com_Error( ERR_FATAL, "Testing fatal error" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=============
|
||
|
Com_Freeze_f
|
||
|
|
||
|
Just freeze in place for a given number of seconds to test
|
||
|
error recovery
|
||
|
=============
|
||
|
*/
|
||
|
static void Com_Freeze_f( void ) {
|
||
|
int s;
|
||
|
int start, now;
|
||
|
|
||
|
if ( Cmd_Argc() != 2 ) {
|
||
|
Com_Printf( "freeze <seconds>\n" );
|
||
|
return;
|
||
|
}
|
||
|
s = atoi( Cmd_Argv(1) ) * 1000;
|
||
|
|
||
|
start = Com_Milliseconds();
|
||
|
|
||
|
while ( 1 ) {
|
||
|
now = Com_Milliseconds();
|
||
|
if ( now - start > s ) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_Crash_f
|
||
|
|
||
|
A way to force a bus error for development reasons
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_Crash_f( void ) {
|
||
|
* ( volatile int * ) 0 = 0x12345678;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_ExecuteCfg
|
||
|
|
||
|
For controlling environment variables
|
||
|
==================
|
||
|
*/
|
||
|
static void Com_ExecuteCfg( void )
|
||
|
{
|
||
|
Cbuf_ExecuteText(EXEC_NOW, "exec default.cfg\n");
|
||
|
Cbuf_Execute(); // Always execute after exec to prevent text buffer overflowing
|
||
|
|
||
|
if(!Com_SafeMode())
|
||
|
{
|
||
|
// skip the q3config.cfg and autoexec.cfg if "safe" is on the command line
|
||
|
Cbuf_ExecuteText(EXEC_NOW, "exec " Q3CONFIG_CFG "\n");
|
||
|
Cbuf_Execute();
|
||
|
Cbuf_ExecuteText(EXEC_NOW, "exec autoexec.cfg\n");
|
||
|
Cbuf_Execute();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_GameRestart
|
||
|
|
||
|
Change to a new mod properly with cleaning up cvars before switching.
|
||
|
==================
|
||
|
*/
|
||
|
void Com_GameRestart( int checksumFeed, qboolean clientRestart )
|
||
|
{
|
||
|
static qboolean com_gameRestarting = qfalse;
|
||
|
|
||
|
// make sure no recursion can be triggered
|
||
|
if ( !com_gameRestarting && com_fullyInitialized )
|
||
|
{
|
||
|
com_gameRestarting = qtrue;
|
||
|
#ifndef DEDICATED
|
||
|
if ( clientRestart )
|
||
|
{
|
||
|
CL_Disconnect( qfalse );
|
||
|
CL_ShutdownAll();
|
||
|
CL_ClearMemory(); // Hunk_Clear(); // -EC-
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Kill server if we have one
|
||
|
if ( com_sv_running->integer )
|
||
|
SV_Shutdown( "Game directory changed" );
|
||
|
|
||
|
// Reset console command history
|
||
|
Con_ResetHistory();
|
||
|
|
||
|
// Shutdown FS early so Cvar_Restart will not reset old game cvars
|
||
|
FS_Shutdown( qtrue );
|
||
|
|
||
|
// Clean out any user and VM created cvars
|
||
|
Cvar_Restart( qtrue );
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
// Reparse pure paks and update cvars before FS startup
|
||
|
if ( CL_GameSwitch() )
|
||
|
CL_SystemInfoChanged( qfalse );
|
||
|
#endif
|
||
|
|
||
|
FS_Restart( checksumFeed );
|
||
|
|
||
|
// Load new configuration
|
||
|
Com_ExecuteCfg();
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
if ( clientRestart )
|
||
|
CL_StartHunkUsers();
|
||
|
#endif
|
||
|
|
||
|
com_gameRestarting = qfalse;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_GameRestart_f
|
||
|
|
||
|
Expose possibility to change current running mod to the user
|
||
|
==================
|
||
|
*/
|
||
|
static void Com_GameRestart_f( void )
|
||
|
{
|
||
|
Cvar_Set( "fs_game", Cmd_Argv( 1 ) );
|
||
|
|
||
|
Com_GameRestart( 0, qtrue );
|
||
|
}
|
||
|
|
||
|
|
||
|
// TTimo: centralizing the cl_cdkey stuff after I discovered a buffer overflow problem with the dedicated server version
|
||
|
// not sure it's necessary to have different defaults for regular and dedicated, but I don't want to risk it
|
||
|
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=470
|
||
|
#ifndef DEDICATED
|
||
|
char cl_cdkey[34] = " ";
|
||
|
#else
|
||
|
char cl_cdkey[34] = "123456789";
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
bool CL_CDKeyValidate
|
||
|
=================
|
||
|
*/
|
||
|
qboolean Com_CDKeyValidate( const char *key, const char *checksum ) {
|
||
|
#ifdef STANDALONE
|
||
|
return qtrue;
|
||
|
#else
|
||
|
char ch;
|
||
|
byte sum;
|
||
|
char chs[10];
|
||
|
int i, len;
|
||
|
|
||
|
len = strlen(key);
|
||
|
if( len != CDKEY_LEN ) {
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) {
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
sum = 0;
|
||
|
// for loop gets rid of conditional assignment warning
|
||
|
for (i = 0; i < len; i++) {
|
||
|
ch = *key++;
|
||
|
if (ch>='a' && ch<='z') {
|
||
|
ch -= 32;
|
||
|
}
|
||
|
switch( ch ) {
|
||
|
case '2':
|
||
|
case '3':
|
||
|
case '7':
|
||
|
case 'A':
|
||
|
case 'B':
|
||
|
case 'C':
|
||
|
case 'D':
|
||
|
case 'G':
|
||
|
case 'H':
|
||
|
case 'J':
|
||
|
case 'L':
|
||
|
case 'P':
|
||
|
case 'R':
|
||
|
case 'S':
|
||
|
case 'T':
|
||
|
case 'W':
|
||
|
sum += ch;
|
||
|
continue;
|
||
|
default:
|
||
|
return qfalse;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sprintf(chs, "%02x", sum);
|
||
|
|
||
|
if (checksum && !Q_stricmp(chs, checksum)) {
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
if (!checksum) {
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
return qfalse;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_ReadCDKey
|
||
|
=================
|
||
|
*/
|
||
|
void Com_ReadCDKey( const char *filename ) {
|
||
|
fileHandle_t f;
|
||
|
char buffer[33];
|
||
|
char fbuffer[MAX_OSPATH];
|
||
|
|
||
|
Com_sprintf( fbuffer, sizeof( fbuffer ), "%s/q3key", filename );
|
||
|
|
||
|
FS_SV_FOpenFileRead( fbuffer, &f );
|
||
|
if ( f == FS_INVALID_HANDLE ) {
|
||
|
Q_strncpyz( cl_cdkey, " ", 17 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Com_Memset( buffer, 0, sizeof( buffer ) );
|
||
|
|
||
|
FS_Read( buffer, 16, f );
|
||
|
FS_FCloseFile( f );
|
||
|
|
||
|
if ( Com_CDKeyValidate(buffer, NULL) ) {
|
||
|
Q_strncpyz( cl_cdkey, buffer, 17 );
|
||
|
} else {
|
||
|
Q_strncpyz( cl_cdkey, " ", 17 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_AppendCDKey
|
||
|
=================
|
||
|
*/
|
||
|
void Com_AppendCDKey( const char *filename ) {
|
||
|
fileHandle_t f;
|
||
|
char buffer[33];
|
||
|
char fbuffer[MAX_OSPATH];
|
||
|
|
||
|
Com_sprintf(fbuffer, sizeof(fbuffer), "%s/q3key", filename);
|
||
|
|
||
|
FS_SV_FOpenFileRead( fbuffer, &f );
|
||
|
if ( f == FS_INVALID_HANDLE ) {
|
||
|
Q_strncpyz( &cl_cdkey[16], " ", 17 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Com_Memset( buffer, 0, sizeof(buffer) );
|
||
|
|
||
|
FS_Read( buffer, 16, f );
|
||
|
FS_FCloseFile( f );
|
||
|
|
||
|
if ( Com_CDKeyValidate(buffer, NULL)) {
|
||
|
strcat( &cl_cdkey[16], buffer );
|
||
|
} else {
|
||
|
Q_strncpyz( &cl_cdkey[16], " ", 17 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
#ifndef DEDICATED // bk001204
|
||
|
/*
|
||
|
=================
|
||
|
Com_WriteCDKey
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_WriteCDKey( const char *filename, const char *ikey ) {
|
||
|
fileHandle_t f;
|
||
|
char fbuffer[MAX_OSPATH];
|
||
|
char key[17];
|
||
|
#ifndef _WIN32
|
||
|
mode_t savedumask;
|
||
|
#endif
|
||
|
|
||
|
Com_sprintf( fbuffer, sizeof(fbuffer), "%s/q3key", filename );
|
||
|
|
||
|
Q_strncpyz( key, ikey, 17 );
|
||
|
|
||
|
if( !Com_CDKeyValidate(key, NULL) ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
#ifndef _WIN32
|
||
|
savedumask = umask(0077);
|
||
|
#endif
|
||
|
f = FS_SV_FOpenFileWrite( fbuffer );
|
||
|
if ( f == FS_INVALID_HANDLE ) {
|
||
|
Com_Printf( "Couldn't write CD key to %s.\n", fbuffer );
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
FS_Write( key, 16, f );
|
||
|
|
||
|
FS_Printf( f, Q_NEWLINE "// generated by quake, do not modify" Q_NEWLINE );
|
||
|
FS_Printf( f, "// Do not give this file to ANYONE." Q_NEWLINE );
|
||
|
FS_Printf( f, "// id Software and Activision will NOT ask you to send this file to them." Q_NEWLINE );
|
||
|
|
||
|
FS_FCloseFile( f );
|
||
|
out:
|
||
|
#ifndef _WIN32
|
||
|
umask(savedumask);
|
||
|
#else
|
||
|
;
|
||
|
#endif
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
** --------------------------------------------------------------------------------
|
||
|
**
|
||
|
** PROCESSOR STUFF
|
||
|
**
|
||
|
** --------------------------------------------------------------------------------
|
||
|
*/
|
||
|
|
||
|
#if (idx64 || id386)
|
||
|
|
||
|
#if defined _MSC_VER
|
||
|
#include <intrin.h>
|
||
|
static void CPUID( int func, unsigned int *regs )
|
||
|
{
|
||
|
__cpuid( (int*)regs, func );
|
||
|
}
|
||
|
|
||
|
#else // clang/gcc/mingw
|
||
|
|
||
|
static void CPUID( int func, unsigned int *regs )
|
||
|
{
|
||
|
__asm__ __volatile__( "cpuid" :
|
||
|
"=a"(regs[0]),
|
||
|
"=b"(regs[1]),
|
||
|
"=c"(regs[2]),
|
||
|
"=d"(regs[3]) :
|
||
|
"a"(func) );
|
||
|
}
|
||
|
|
||
|
#endif // clang/gcc/mingw
|
||
|
|
||
|
static void Sys_GetProcessorId( char *vendor )
|
||
|
{
|
||
|
uint32_t regs[4]; // EAX, EBX, ECX, EDX
|
||
|
uint32_t cpuid_level_ex;
|
||
|
char vendor_str[12 + 1]; // short CPU vendor string
|
||
|
|
||
|
// setup initial features
|
||
|
#if idx64
|
||
|
CPU_Flags |= CPU_SSE | CPU_SSE2 | CPU_FCOM;
|
||
|
#else
|
||
|
CPU_Flags = 0;
|
||
|
#endif
|
||
|
vendor[0] = '\0';
|
||
|
|
||
|
CPUID( 0x80000000, regs );
|
||
|
cpuid_level_ex = regs[0];
|
||
|
|
||
|
// get CPUID level & short CPU vendor string
|
||
|
CPUID( 0x0, regs );
|
||
|
memcpy(vendor_str + 0, (char*)®s[1], 4);
|
||
|
memcpy(vendor_str + 4, (char*)®s[3], 4);
|
||
|
memcpy(vendor_str + 8, (char*)®s[2], 4);
|
||
|
vendor_str[12] = '\0';
|
||
|
|
||
|
// get CPU feature bits
|
||
|
CPUID( 0x1, regs );
|
||
|
|
||
|
// bit 15 of EDX denotes CMOV/FCMOV/FCOMI existence
|
||
|
if ( regs[3] & ( 1 << 15 ) )
|
||
|
CPU_Flags |= CPU_FCOM;
|
||
|
|
||
|
// bit 23 of EDX denotes MMX existence
|
||
|
if ( regs[3] & ( 1 << 23 ) )
|
||
|
CPU_Flags |= CPU_MMX;
|
||
|
|
||
|
// bit 25 of EDX denotes SSE existence
|
||
|
if ( regs[3] & ( 1 << 25 ) )
|
||
|
CPU_Flags |= CPU_SSE;
|
||
|
|
||
|
// bit 26 of EDX denotes SSE2 existence
|
||
|
if ( regs[3] & ( 1 << 26 ) )
|
||
|
CPU_Flags |= CPU_SSE2;
|
||
|
|
||
|
// bit 0 of ECX denotes SSE3 existence
|
||
|
//if ( regs[2] & ( 1 << 0 ) )
|
||
|
// CPU_Flags |= CPU_SSE3;
|
||
|
|
||
|
// bit 19 of ECX denotes SSE41 existence
|
||
|
if ( regs[ 2 ] & ( 1 << 19 ) )
|
||
|
CPU_Flags |= CPU_SSE41;
|
||
|
|
||
|
if ( vendor ) {
|
||
|
if ( cpuid_level_ex >= 0x80000004 ) {
|
||
|
// read CPU Brand string
|
||
|
uint32_t i;
|
||
|
for ( i = 0x80000002; i <= 0x80000004; i++) {
|
||
|
CPUID( i, regs );
|
||
|
memcpy( vendor+0, (char*)®s[0], 4 );
|
||
|
memcpy( vendor+4, (char*)®s[1], 4 );
|
||
|
memcpy( vendor+8, (char*)®s[2], 4 );
|
||
|
memcpy( vendor+12, (char*)®s[3], 4 );
|
||
|
vendor[16] = '\0';
|
||
|
vendor += strlen( vendor );
|
||
|
}
|
||
|
} else {
|
||
|
const int print_flags = CPU_Flags;
|
||
|
vendor = Q_stradd( vendor, vendor_str );
|
||
|
if (print_flags) {
|
||
|
// print features
|
||
|
strcat(vendor, " w/");
|
||
|
if (print_flags & CPU_FCOM)
|
||
|
strcat(vendor, " CMOV");
|
||
|
if (print_flags & CPU_MMX)
|
||
|
strcat(vendor, " MMX");
|
||
|
if (print_flags & CPU_SSE)
|
||
|
strcat(vendor, " SSE");
|
||
|
if (print_flags & CPU_SSE2)
|
||
|
strcat(vendor, " SSE2");
|
||
|
//if ( CPU_Flags & CPU_SSE3 )
|
||
|
// strcat( vendor, " SSE3" );
|
||
|
if (print_flags & CPU_SSE41)
|
||
|
strcat(vendor, " SSE4.1");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#else // non-x86
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
|
||
|
static void Sys_GetProcessorId( char *vendor )
|
||
|
{
|
||
|
Com_sprintf( vendor, 100, "%s", ARCH_STRING );
|
||
|
}
|
||
|
|
||
|
#else // not _WIN32
|
||
|
|
||
|
#include <sys/auxv.h>
|
||
|
|
||
|
#if arm32
|
||
|
#include <asm/hwcap.h>
|
||
|
#endif
|
||
|
|
||
|
static void Sys_GetProcessorId( char *vendor )
|
||
|
{
|
||
|
#if arm32
|
||
|
const char *platform;
|
||
|
long hwcaps;
|
||
|
CPU_Flags = 0;
|
||
|
|
||
|
platform = (const char*)getauxval( AT_PLATFORM );
|
||
|
|
||
|
if ( !platform || *platform == '\0' ) {
|
||
|
platform = "(unknown)";
|
||
|
}
|
||
|
|
||
|
if ( platform[0] == 'v' || platform[0] == 'V' ) {
|
||
|
if ( atoi( platform + 1 ) >= 7 ) {
|
||
|
CPU_Flags |= CPU_ARMv7;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Com_sprintf( vendor, 100, "ARM %s", platform );
|
||
|
hwcaps = getauxval( AT_HWCAP );
|
||
|
if ( hwcaps & ( HWCAP_IDIVA | HWCAP_VFPv3 ) ) {
|
||
|
strcat( vendor, " /w" );
|
||
|
|
||
|
if ( hwcaps & HWCAP_IDIVA ) {
|
||
|
CPU_Flags |= CPU_IDIVA;
|
||
|
strcat( vendor, " IDIVA" );
|
||
|
}
|
||
|
|
||
|
if ( hwcaps & HWCAP_VFPv3 ) {
|
||
|
CPU_Flags |= CPU_VFPv3;
|
||
|
strcat( vendor, " VFPv3" );
|
||
|
}
|
||
|
|
||
|
if ( ( CPU_Flags & ( CPU_ARMv7 | CPU_VFPv3 ) ) == ( CPU_ARMv7 | CPU_VFPv3 ) ) {
|
||
|
strcat( vendor, " QVM-bytecode" );
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
CPU_Flags = 0;
|
||
|
#if arm64
|
||
|
Com_sprintf( vendor, 100, "ARM %s", ARCH_STRING );
|
||
|
#else
|
||
|
Com_sprintf( vendor, 128, "%s %s", ARCH_STRING, (const char*)getauxval( AT_PLATFORM ) );
|
||
|
#endif
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#endif // !_WIN32
|
||
|
|
||
|
#endif // non-x86
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Sys_SnapVector
|
||
|
================
|
||
|
*/
|
||
|
#ifdef _MSC_VER
|
||
|
#if idx64
|
||
|
void Sys_SnapVector( float *vector )
|
||
|
{
|
||
|
__m128 vf0, vf1, vf2;
|
||
|
__m128i vi;
|
||
|
DWORD mxcsr;
|
||
|
|
||
|
mxcsr = _mm_getcsr();
|
||
|
vf0 = _mm_setr_ps( vector[0], vector[1], vector[2], 0.0f );
|
||
|
|
||
|
_mm_setcsr( mxcsr & ~0x6000 ); // enforce rounding mode to "round to nearest"
|
||
|
|
||
|
vi = _mm_cvtps_epi32( vf0 );
|
||
|
vf0 = _mm_cvtepi32_ps( vi );
|
||
|
|
||
|
vf1 = _mm_shuffle_ps(vf0, vf0, _MM_SHUFFLE(1,1,1,1));
|
||
|
vf2 = _mm_shuffle_ps(vf0, vf0, _MM_SHUFFLE(2,2,2,2));
|
||
|
|
||
|
_mm_setcsr( mxcsr ); // restore rounding mode
|
||
|
|
||
|
_mm_store_ss( &vector[0], vf0 );
|
||
|
_mm_store_ss( &vector[1], vf1 );
|
||
|
_mm_store_ss( &vector[2], vf2 );
|
||
|
}
|
||
|
#endif // idx64
|
||
|
|
||
|
#if id386
|
||
|
void Sys_SnapVector( float *vector )
|
||
|
{
|
||
|
static const DWORD cw037F = 0x037F;
|
||
|
DWORD cwCurr;
|
||
|
__asm {
|
||
|
fnstcw word ptr [cwCurr]
|
||
|
mov ecx, vector
|
||
|
fldcw word ptr [cw037F]
|
||
|
|
||
|
fld dword ptr[ecx+8]
|
||
|
fistp dword ptr[ecx+8]
|
||
|
fild dword ptr[ecx+8]
|
||
|
fstp dword ptr[ecx+8]
|
||
|
|
||
|
fld dword ptr[ecx+4]
|
||
|
fistp dword ptr[ecx+4]
|
||
|
fild dword ptr[ecx+4]
|
||
|
fstp dword ptr[ecx+4]
|
||
|
|
||
|
fld dword ptr[ecx+0]
|
||
|
fistp dword ptr[ecx+0]
|
||
|
fild dword ptr[ecx+0]
|
||
|
fstp dword ptr[ecx+0]
|
||
|
|
||
|
fldcw word ptr cwCurr
|
||
|
}; // __asm
|
||
|
}
|
||
|
#endif // id386
|
||
|
|
||
|
#if arm64
|
||
|
void Sys_SnapVector( float *vector )
|
||
|
{
|
||
|
vector[0] = rint( vector[0] );
|
||
|
vector[1] = rint( vector[1] );
|
||
|
vector[2] = rint( vector[2] );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#else // clang/gcc/mingw
|
||
|
|
||
|
#if id386
|
||
|
|
||
|
#define QROUNDX87(src) \
|
||
|
"flds " src "\n" \
|
||
|
"fistpl " src "\n" \
|
||
|
"fildl " src "\n" \
|
||
|
"fstps " src "\n"
|
||
|
|
||
|
void Sys_SnapVector( float *vector )
|
||
|
{
|
||
|
static const unsigned short cw037F = 0x037F;
|
||
|
unsigned short cwCurr;
|
||
|
|
||
|
__asm__ volatile
|
||
|
(
|
||
|
"fnstcw %1\n" \
|
||
|
"fldcw %2\n" \
|
||
|
QROUNDX87("0(%0)")
|
||
|
QROUNDX87("4(%0)")
|
||
|
QROUNDX87("8(%0)")
|
||
|
"fldcw %1\n" \
|
||
|
:
|
||
|
: "r" (vector), "m"(cwCurr), "m"(cw037F)
|
||
|
: "memory", "st"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#else // idx64, non-x86
|
||
|
|
||
|
void Sys_SnapVector( float *vector )
|
||
|
{
|
||
|
vector[0] = rint( vector[0] );
|
||
|
vector[1] = rint( vector[1] );
|
||
|
vector[2] = rint( vector[2] );
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
#endif // clang/gcc/mingw
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_Init
|
||
|
=================
|
||
|
*/
|
||
|
void Com_Init( char *commandLine ) {
|
||
|
const char *s;
|
||
|
int qport;
|
||
|
|
||
|
Com_Printf( "%s %s %s\n", SVN_VERSION, PLATFORM_STRING, __DATE__ );
|
||
|
|
||
|
if ( Q_setjmp( abortframe ) ) {
|
||
|
Sys_Error ("Error during initialization");
|
||
|
}
|
||
|
|
||
|
// bk001129 - do this before anything else decides to push events
|
||
|
Com_InitPushEvent();
|
||
|
|
||
|
Com_InitSmallZoneMemory();
|
||
|
Cvar_Init();
|
||
|
|
||
|
#if defined(_WIN32) && defined(_DEBUG)
|
||
|
com_noErrorInterrupt = Cvar_Get( "com_noErrorInterrupt", "0", 0 );
|
||
|
#endif
|
||
|
|
||
|
#ifdef DEFAULT_GAME
|
||
|
Cvar_Set( "fs_game", DEFAULT_GAME );
|
||
|
#endif
|
||
|
|
||
|
// prepare enough of the subsystems to handle
|
||
|
// cvar and command buffer management
|
||
|
Com_ParseCommandLine( commandLine );
|
||
|
|
||
|
// Swap_Init ();
|
||
|
Cbuf_Init();
|
||
|
|
||
|
// override anything from the config files with command line args
|
||
|
Com_StartupVariable( NULL );
|
||
|
|
||
|
Com_InitZoneMemory();
|
||
|
Cmd_Init();
|
||
|
|
||
|
// get the developer cvar set as early as possible
|
||
|
Com_StartupVariable( "developer" );
|
||
|
com_developer = Cvar_Get( "developer", "0", CVAR_TEMP );
|
||
|
Cvar_CheckRange( com_developer, NULL, NULL, CV_INTEGER );
|
||
|
|
||
|
Com_StartupVariable( "vm_rtChecks" );
|
||
|
vm_rtChecks = Cvar_Get( "vm_rtChecks", "15", CVAR_INIT | CVAR_PROTECTED );
|
||
|
Cvar_CheckRange( vm_rtChecks, "0", "15", CV_INTEGER );
|
||
|
Cvar_SetDescription( vm_rtChecks,
|
||
|
"Runtime checks in compiled vm code, bitmask:\n 1 - program stack overflow\n" \
|
||
|
" 2 - opcode stack overflow\n 4 - jump target range\n 8 - data read/write range" );
|
||
|
|
||
|
Com_StartupVariable( "journal" );
|
||
|
com_journal = Cvar_Get( "journal", "0", CVAR_INIT | CVAR_PROTECTED );
|
||
|
Cvar_CheckRange( com_journal, "0", "2", CV_INTEGER );
|
||
|
Cvar_SetDescription( com_journal, "When enabled, writes events and its data to 'journal.dat' and 'journaldata.dat'.");
|
||
|
|
||
|
Com_StartupVariable( "sv_master1" );
|
||
|
Com_StartupVariable( "sv_master2" );
|
||
|
Com_StartupVariable( "sv_master3" );
|
||
|
Cvar_Get( "sv_master1", MASTER_SERVER_NAME, CVAR_INIT );
|
||
|
Cvar_Get( "sv_master2", "master.ioquake3.org", CVAR_INIT );
|
||
|
Cvar_Get( "sv_master3", "master.maverickservers.com", CVAR_INIT );
|
||
|
|
||
|
com_protocol = Cvar_Get( "protocol", XSTRING( DEFAULT_PROTOCOL_VERSION ), 0 );
|
||
|
Cvar_SetDescription( com_protocol, "Specify network protocol version number, use -compat suffix for OpenArena compatibility.");
|
||
|
if ( Q_stristr( com_protocol->string, "-compat" ) > com_protocol->string ) {
|
||
|
// strip -compat suffix
|
||
|
Cvar_Set2( "protocol", va( "%i", com_protocol->integer ), qtrue );
|
||
|
// enforce legacy stream encoding but with new challenge format
|
||
|
com_protocolCompat = qtrue;
|
||
|
} else {
|
||
|
com_protocolCompat = qfalse;
|
||
|
}
|
||
|
|
||
|
Cvar_CheckRange( com_protocol, "0", NULL, CV_INTEGER );
|
||
|
com_protocol->flags &= ~CVAR_USER_CREATED;
|
||
|
com_protocol->flags |= CVAR_SERVERINFO | CVAR_ROM;
|
||
|
|
||
|
// done early so bind command exists
|
||
|
Com_InitKeyCommands();
|
||
|
|
||
|
cl_selectedmod = Cvar_Get("cl_selectedmod", "default", CVAR_ARCHIVE | CVAR_SERVERINFO);
|
||
|
cl_arenascriptDebug = Cvar_Get("cl_arenascriptDebug", "0", CVAR_ARCHIVE | CVAR_SERVERINFO);
|
||
|
|
||
|
FS_InitFilesystem();
|
||
|
|
||
|
com_logfile = Cvar_Get( "logfile", "0", CVAR_TEMP );
|
||
|
Cvar_CheckRange( com_logfile, "0", "4", CV_INTEGER );
|
||
|
Cvar_SetDescription( com_logfile, "System console logging:\n"
|
||
|
" 0 - disabled\n"
|
||
|
" 1 - overwrite mode, buffered\n"
|
||
|
" 2 - overwrite mode, synced\n"
|
||
|
" 3 - append mode, buffered\n"
|
||
|
" 4 - append mode, synced\n" );
|
||
|
|
||
|
Com_InitJournaling();
|
||
|
|
||
|
Com_ExecuteCfg();
|
||
|
|
||
|
// override anything from the config files with command line args
|
||
|
Com_StartupVariable( NULL );
|
||
|
|
||
|
// get dedicated here for proper hunk megs initialization
|
||
|
#ifdef DEDICATED
|
||
|
com_dedicated = Cvar_Get( "dedicated", "1", CVAR_INIT );
|
||
|
Cvar_CheckRange( com_dedicated, "1", "2", CV_INTEGER );
|
||
|
#else
|
||
|
com_dedicated = Cvar_Get( "dedicated", "0", CVAR_LATCH );
|
||
|
Cvar_CheckRange( com_dedicated, "0", "2", CV_INTEGER );
|
||
|
#endif
|
||
|
Cvar_SetDescription( com_dedicated, "Enables dedicated server mode.\n 0: Listen server\n 1: Unlisted dedicated server \n 2: Listed dedicated server" );
|
||
|
// allocate the stack based hunk allocator
|
||
|
Com_InitHunkMemory();
|
||
|
|
||
|
// if any archived cvars are modified after this, we will trigger a writing
|
||
|
// of the config file
|
||
|
cvar_modifiedFlags &= ~CVAR_ARCHIVE;
|
||
|
|
||
|
//
|
||
|
// init commands and vars
|
||
|
//
|
||
|
#ifndef DEDICATED
|
||
|
com_maxfps = Cvar_Get( "com_maxfps", "125", 0 ); // try to force that in some light way
|
||
|
Cvar_CheckRange( com_maxfps, "0", "1000", CV_INTEGER );
|
||
|
Cvar_SetDescription( com_maxfps, "Sets maximum frames per second." );
|
||
|
com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "60", CVAR_ARCHIVE_ND );
|
||
|
Cvar_CheckRange( com_maxfpsUnfocused, "0", "1000", CV_INTEGER );
|
||
|
Cvar_SetDescription( com_maxfpsUnfocused, "Sets maximum frames per second in unfocused game window." );
|
||
|
com_yieldCPU = Cvar_Get( "com_yieldCPU", "1", CVAR_ARCHIVE_ND );
|
||
|
Cvar_CheckRange( com_yieldCPU, "0", "16", CV_INTEGER );
|
||
|
Cvar_SetDescription( com_yieldCPU, "Attempt to sleep specified amount of time between rendered frames when game is active, this will greatly reduce CPU load. Use 0 only if you're experiencing some lag." );
|
||
|
#endif
|
||
|
|
||
|
#ifdef USE_AFFINITY_MASK
|
||
|
com_affinityMask = Cvar_Get( "com_affinityMask", "0", CVAR_ARCHIVE_ND );
|
||
|
Cvar_SetDescription( com_affinityMask, "Bind Quake3e process to bitmask-specified CPU core(s)." );
|
||
|
com_affinityMask->modified = qfalse;
|
||
|
#endif
|
||
|
|
||
|
// com_blood = Cvar_Get( "com_blood", "1", CVAR_ARCHIVE_ND );
|
||
|
|
||
|
com_timescale = Cvar_Get( "timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO );
|
||
|
Cvar_CheckRange( com_timescale, "0", NULL, CV_FLOAT );
|
||
|
Cvar_SetDescription( com_timescale, "System timing factor:\n < 1: Slows the game down\n = 1: Regular speed\n > 1: Speeds the game up" );
|
||
|
com_fixedtime = Cvar_Get( "fixedtime", "0", CVAR_CHEAT );
|
||
|
Cvar_SetDescription( com_fixedtime, "Toggle the rendering of every frame the game will wait until each frame is completely rendered before sending the next frame." );
|
||
|
com_showtrace = Cvar_Get( "com_showtrace", "0", CVAR_CHEAT );
|
||
|
Cvar_SetDescription( com_showtrace, "Debugging tool that prints out trace information." );
|
||
|
com_viewlog = Cvar_Get( "viewlog", "0", 0 );
|
||
|
Cvar_SetDescription( com_viewlog, "Toggle the display of the startup console window over the game screen." );
|
||
|
com_speeds = Cvar_Get( "com_speeds", "0", 0 );
|
||
|
Cvar_SetDescription( com_speeds, "Prints speed information per frame to the console. Used for debugging." );
|
||
|
com_cameraMode = Cvar_Get( "com_cameraMode", "0", CVAR_CHEAT );
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
com_timedemo = Cvar_Get( "timedemo", "0", 0 );
|
||
|
Cvar_CheckRange( com_timedemo, "0", "1", CV_INTEGER );
|
||
|
Cvar_SetDescription( com_timedemo, "When set to '1' times a demo and returns frames per second like a benchmark." );
|
||
|
cl_paused = Cvar_Get( "cl_paused", "0", CVAR_ROM );
|
||
|
Cvar_SetDescription( cl_paused, "Read-only CVAR to toggle functionality of paused games (the variable holds the status of the paused flag on the client side)." );
|
||
|
cl_packetdelay = Cvar_Get( "cl_packetdelay", "0", CVAR_CHEAT );
|
||
|
Cvar_SetDescription( cl_packetdelay, "Artificially set the client's latency. Simulates packet delay, which can lead to packet loss." );
|
||
|
com_cl_running = Cvar_Get( "cl_running", "0", CVAR_ROM | CVAR_NOTABCOMPLETE );
|
||
|
Cvar_SetDescription( com_cl_running, "Can be used to check the status of the client game." );
|
||
|
#endif
|
||
|
|
||
|
sv_paused = Cvar_Get( "sv_paused", "0", CVAR_ROM );
|
||
|
sv_packetdelay = Cvar_Get( "sv_packetdelay", "0", CVAR_CHEAT );
|
||
|
Cvar_SetDescription( sv_packetdelay, "Simulates packet delay, which can lead to packet loss. Server side." );
|
||
|
com_sv_running = Cvar_Get( "sv_running", "0", CVAR_ROM | CVAR_NOTABCOMPLETE );
|
||
|
Cvar_SetDescription( com_sv_running, "Communicates to game modules if there is a server currently running." );
|
||
|
|
||
|
com_buildScript = Cvar_Get( "com_buildScript", "0", 0 );
|
||
|
Cvar_SetDescription( com_buildScript, "Loads all game assets, regardless whether they are required or not." );
|
||
|
|
||
|
Cvar_Get( "com_errorMessage", "", CVAR_ROM | CVAR_NORESTART );
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
com_introPlayed = Cvar_Get( "com_introplayed", "0", CVAR_ARCHIVE );
|
||
|
Cvar_SetDescription( com_introPlayed, "Skips the introduction cinematic." );
|
||
|
com_skipIdLogo = Cvar_Get( "com_skipIdLogo", "0", CVAR_ARCHIVE );
|
||
|
Cvar_SetDescription( com_skipIdLogo, "Skip playing Id Software logo cinematic at startup." );
|
||
|
#endif
|
||
|
|
||
|
if ( com_dedicated->integer ) {
|
||
|
if ( !com_viewlog->integer ) {
|
||
|
Cvar_Set( "viewlog", "1" );
|
||
|
}
|
||
|
gw_minimized = qtrue;
|
||
|
} else {
|
||
|
gw_minimized = qfalse;
|
||
|
}
|
||
|
|
||
|
if ( com_developer->integer ) {
|
||
|
Cmd_AddCommand( "error", Com_Error_f );
|
||
|
Cmd_AddCommand( "crash", Com_Crash_f );
|
||
|
Cmd_AddCommand( "freeze", Com_Freeze_f );
|
||
|
}
|
||
|
|
||
|
Cmd_AddCommand( "quit", Com_Quit_f );
|
||
|
Cmd_AddCommand( "changeVectors", MSG_ReportChangeVectors_f );
|
||
|
Cmd_AddCommand( "writeconfig", Com_WriteConfig_f );
|
||
|
Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteWriteCfgName );
|
||
|
Cmd_AddCommand( "game_restart", Com_GameRestart_f );
|
||
|
|
||
|
s = va( "%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ );
|
||
|
com_version = Cvar_Get( "version", s, CVAR_PROTECTED | CVAR_ROM | CVAR_SERVERINFO );
|
||
|
Cvar_SetDescription( com_version, "Read-only CVAR to see the version of the game." );
|
||
|
|
||
|
// this cvar is the single entry point of the entire extension system
|
||
|
Cvar_Get( "//trap_GetValue", va( "%i", COM_TRAP_GETVALUE ), CVAR_PROTECTED | CVAR_ROM | CVAR_NOTABCOMPLETE );
|
||
|
|
||
|
Sys_Init();
|
||
|
|
||
|
// CPU detection
|
||
|
Cvar_Get( "sys_cpustring", "detect", CVAR_PROTECTED | CVAR_ROM | CVAR_NORESTART );
|
||
|
if ( !Q_stricmp( Cvar_VariableString( "sys_cpustring" ), "detect" ) )
|
||
|
{
|
||
|
char vendor[128];
|
||
|
Com_Printf( "...detecting CPU, found " );
|
||
|
Sys_GetProcessorId( vendor );
|
||
|
Cvar_Set( "sys_cpustring", vendor );
|
||
|
}
|
||
|
Com_Printf( "%s\n", Cvar_VariableString( "sys_cpustring" ) );
|
||
|
|
||
|
#ifdef USE_AFFINITY_MASK
|
||
|
if ( com_affinityMask->integer )
|
||
|
Sys_SetAffinityMask( com_affinityMask->integer );
|
||
|
#endif
|
||
|
|
||
|
// Pick a random port value
|
||
|
Com_RandomBytes( (byte*)&qport, sizeof( qport ) );
|
||
|
Netchan_Init( qport & 0xffff );
|
||
|
|
||
|
VM_Init();
|
||
|
SV_Init();
|
||
|
|
||
|
com_dedicated->modified = qfalse;
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
if ( !com_dedicated->integer ) {
|
||
|
CL_Init();
|
||
|
// Sys_ShowConsole( com_viewlog->integer, qfalse ); // moved down
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// add + commands from command line
|
||
|
if ( !Com_AddStartupCommands() ) {
|
||
|
// if the user didn't give any commands, run default action
|
||
|
if ( !com_dedicated->integer ) {
|
||
|
#ifndef DEDICATED
|
||
|
if ( !com_skipIdLogo || !com_skipIdLogo->integer )
|
||
|
Cbuf_AddText( "cinematic idlogo.RoQ\n" );
|
||
|
if( !com_introPlayed->integer ) {
|
||
|
Cvar_Set( com_introPlayed->name, "1" );
|
||
|
Cvar_Set( "nextmap", "cinematic intro.RoQ" );
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
CL_StartHunkUsers();
|
||
|
#endif
|
||
|
|
||
|
// set com_frameTime so that if a map is started on the
|
||
|
// command line it will still be able to count on com_frameTime
|
||
|
// being random enough for a serverid
|
||
|
lastTime = com_frameTime = Com_Milliseconds();
|
||
|
|
||
|
if ( !com_errorEntered )
|
||
|
Sys_ShowConsole( com_viewlog->integer, qfalse );
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
// make sure single player is off by default
|
||
|
Cvar_Set( "ui_singlePlayerActive", "0" );
|
||
|
#endif
|
||
|
|
||
|
com_fullyInitialized = qtrue;
|
||
|
|
||
|
Com_Printf( "--- Common Initialization Complete ---\n" );
|
||
|
}
|
||
|
|
||
|
|
||
|
//==================================================================
|
||
|
|
||
|
static void Com_WriteConfigToFile( const char *filename ) {
|
||
|
fileHandle_t f;
|
||
|
|
||
|
f = FS_FOpenFileWrite( filename );
|
||
|
if ( f == FS_INVALID_HANDLE ) {
|
||
|
if ( !FS_ResetReadOnlyAttribute( filename ) || ( f = FS_FOpenFileWrite( filename ) ) == FS_INVALID_HANDLE ) {
|
||
|
Com_Printf( "Couldn't write %s.\n", filename );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FS_Printf( f, "// generated by quake, do not modify" Q_NEWLINE );
|
||
|
#ifndef DEDICATED
|
||
|
Key_WriteBindings( f );
|
||
|
#endif
|
||
|
Cvar_WriteVariables( f );
|
||
|
FS_FCloseFile( f );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Com_WriteConfiguration
|
||
|
|
||
|
Writes key bindings and archived cvars to config file if modified
|
||
|
===============
|
||
|
*/
|
||
|
void Com_WriteConfiguration( void ) {
|
||
|
#ifndef DEDICATED
|
||
|
const char *basegame;
|
||
|
const char *gamedir;
|
||
|
#endif
|
||
|
// if we are quitting without fully initializing, make sure
|
||
|
// we don't write out anything
|
||
|
if ( !com_fullyInitialized ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) {
|
||
|
return;
|
||
|
}
|
||
|
cvar_modifiedFlags &= ~CVAR_ARCHIVE;
|
||
|
|
||
|
Com_WriteConfigToFile( Q3CONFIG_CFG );
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
gamedir = Cvar_VariableString( "fs_game" );
|
||
|
basegame = Cvar_VariableString( "fs_basegame" );
|
||
|
if ( UI_usesUniqueCDKey() && gamedir[0] && Q_stricmp( basegame, gamedir ) ) {
|
||
|
Com_WriteCDKey( gamedir, &cl_cdkey[16] );
|
||
|
} else {
|
||
|
Com_WriteCDKey( basegame, cl_cdkey );
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Com_WriteConfig_f
|
||
|
|
||
|
Write the config file to a specific name
|
||
|
===============
|
||
|
*/
|
||
|
static void Com_WriteConfig_f( void ) {
|
||
|
char filename[MAX_QPATH];
|
||
|
const char *ext;
|
||
|
|
||
|
if ( Cmd_Argc() != 2 ) {
|
||
|
Com_Printf( "Usage: writeconfig <filename>\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
|
||
|
COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
|
||
|
|
||
|
if ( !FS_AllowedExtension( filename, qfalse, &ext ) ) {
|
||
|
Com_Printf( "%s: Invalid filename extension: '%s'.\n", __func__, ext );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Com_Printf( "Writing %s.\n", filename );
|
||
|
Com_WriteConfigToFile( filename );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
================
|
||
|
Com_ModifyMsec
|
||
|
================
|
||
|
*/
|
||
|
static int Com_ModifyMsec( int msec ) {
|
||
|
int clampTime;
|
||
|
|
||
|
//
|
||
|
// modify time for debugging values
|
||
|
//
|
||
|
if ( com_fixedtime->integer ) {
|
||
|
msec = com_fixedtime->integer;
|
||
|
} else if ( com_timescale->value ) {
|
||
|
msec *= com_timescale->value;
|
||
|
} else if (com_cameraMode->integer) {
|
||
|
msec *= com_timescale->value;
|
||
|
}
|
||
|
|
||
|
// don't let it scale below 1 msec
|
||
|
if ( msec < 1 && com_timescale->value) {
|
||
|
msec = 1;
|
||
|
}
|
||
|
|
||
|
if ( com_dedicated->integer ) {
|
||
|
// dedicated servers don't want to clamp for a much longer
|
||
|
// period, because it would mess up all the client's views
|
||
|
// of time.
|
||
|
if (com_sv_running->integer && msec > 500)
|
||
|
Com_Printf( "Hitch warning: %i msec frame time\n", msec );
|
||
|
|
||
|
clampTime = 5000;
|
||
|
} else
|
||
|
if ( !com_sv_running->integer ) {
|
||
|
// clients of remote servers do not want to clamp time, because
|
||
|
// it would skew their view of the server's time temporarily
|
||
|
clampTime = 5000;
|
||
|
} else {
|
||
|
// for local single player gaming
|
||
|
// we may want to clamp the time to prevent players from
|
||
|
// flying off edges when something hitches.
|
||
|
clampTime = 200;
|
||
|
}
|
||
|
|
||
|
if ( msec > clampTime ) {
|
||
|
msec = clampTime;
|
||
|
}
|
||
|
|
||
|
return msec;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_TimeVal
|
||
|
=================
|
||
|
*/
|
||
|
static int Com_TimeVal( int minMsec )
|
||
|
{
|
||
|
int timeVal;
|
||
|
|
||
|
timeVal = Com_Milliseconds() - com_frameTime;
|
||
|
|
||
|
if ( timeVal >= minMsec )
|
||
|
timeVal = 0;
|
||
|
else
|
||
|
timeVal = minMsec - timeVal;
|
||
|
|
||
|
return timeVal;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_Frame
|
||
|
=================
|
||
|
*/
|
||
|
void Com_Frame( qboolean noDelay ) {
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
static int bias = 0;
|
||
|
#endif
|
||
|
int msec, realMsec, minMsec;
|
||
|
int sleepMsec;
|
||
|
int timeVal;
|
||
|
int timeValSV;
|
||
|
|
||
|
int timeBeforeFirstEvents;
|
||
|
int timeBeforeServer;
|
||
|
int timeBeforeEvents;
|
||
|
int timeBeforeClient;
|
||
|
int timeAfter;
|
||
|
|
||
|
if ( Q_setjmp( abortframe ) ) {
|
||
|
return; // an ERR_DROP was thrown
|
||
|
}
|
||
|
|
||
|
minMsec = 0; // silent compiler warning
|
||
|
|
||
|
// bk001204 - init to zero.
|
||
|
// also: might be clobbered by `longjmp' or `vfork'
|
||
|
timeBeforeFirstEvents = 0;
|
||
|
timeBeforeServer = 0;
|
||
|
timeBeforeEvents = 0;
|
||
|
timeBeforeClient = 0;
|
||
|
timeAfter = 0;
|
||
|
|
||
|
// write config file if anything changed
|
||
|
#ifndef DELAY_WRITECONFIG
|
||
|
Com_WriteConfiguration();
|
||
|
#endif
|
||
|
|
||
|
// if "viewlog" has been modified, show or hide the log console
|
||
|
if ( com_viewlog->modified ) {
|
||
|
if ( !com_dedicated->integer ) {
|
||
|
Sys_ShowConsole( com_viewlog->integer, qfalse );
|
||
|
}
|
||
|
com_viewlog->modified = qfalse;
|
||
|
}
|
||
|
|
||
|
#ifdef USE_AFFINITY_MASK
|
||
|
if ( com_affinityMask->modified ) {
|
||
|
Cvar_Get( "com_affinityMask", "0", CVAR_ARCHIVE );
|
||
|
com_affinityMask->modified = qfalse;
|
||
|
Sys_SetAffinityMask( com_affinityMask->integer );
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// main event loop
|
||
|
//
|
||
|
if ( com_speeds->integer ) {
|
||
|
timeBeforeFirstEvents = Sys_Milliseconds();
|
||
|
}
|
||
|
|
||
|
// we may want to spin here if things are going too fast
|
||
|
if ( com_dedicated->integer ) {
|
||
|
minMsec = SV_FrameMsec();
|
||
|
#ifndef DEDICATED
|
||
|
bias = 0;
|
||
|
#endif
|
||
|
} else {
|
||
|
#ifndef DEDICATED
|
||
|
if ( noDelay ) {
|
||
|
minMsec = 0;
|
||
|
bias = 0;
|
||
|
} else {
|
||
|
if ( !gw_active && com_maxfpsUnfocused->integer > 0 )
|
||
|
minMsec = 1000 / com_maxfpsUnfocused->integer;
|
||
|
else
|
||
|
if ( com_maxfps->integer > 0 )
|
||
|
minMsec = 1000 / com_maxfps->integer;
|
||
|
else
|
||
|
minMsec = 1;
|
||
|
|
||
|
timeVal = com_frameTime - lastTime;
|
||
|
bias += timeVal - minMsec;
|
||
|
|
||
|
if ( bias > minMsec )
|
||
|
bias = minMsec;
|
||
|
|
||
|
// Adjust minMsec if previous frame took too long to render so
|
||
|
// that framerate is stable at the requested value.
|
||
|
minMsec -= bias;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// waiting for incoming packets
|
||
|
if ( noDelay == qfalse )
|
||
|
do {
|
||
|
if ( com_sv_running->integer ) {
|
||
|
timeValSV = SV_SendQueuedPackets();
|
||
|
timeVal = Com_TimeVal( minMsec );
|
||
|
if ( timeValSV < timeVal )
|
||
|
timeVal = timeValSV;
|
||
|
} else {
|
||
|
timeVal = Com_TimeVal( minMsec );
|
||
|
}
|
||
|
sleepMsec = timeVal;
|
||
|
#ifndef DEDICATED
|
||
|
if ( !gw_minimized && timeVal > com_yieldCPU->integer )
|
||
|
sleepMsec = com_yieldCPU->integer;
|
||
|
if ( timeVal > sleepMsec )
|
||
|
Com_EventLoop();
|
||
|
#endif
|
||
|
NET_Sleep( sleepMsec * 1000 - 500 );
|
||
|
} while( Com_TimeVal( minMsec ) );
|
||
|
|
||
|
lastTime = com_frameTime;
|
||
|
com_frameTime = Com_EventLoop();
|
||
|
realMsec = com_frameTime - lastTime;
|
||
|
|
||
|
Cbuf_Execute();
|
||
|
|
||
|
// mess with msec if needed
|
||
|
msec = Com_ModifyMsec( realMsec );
|
||
|
|
||
|
//
|
||
|
// server side
|
||
|
//
|
||
|
if ( com_speeds->integer ) {
|
||
|
timeBeforeServer = Sys_Milliseconds();
|
||
|
}
|
||
|
|
||
|
SV_Frame( msec );
|
||
|
|
||
|
// if "dedicated" has been modified, start up
|
||
|
// or shut down the client system.
|
||
|
// Do this after the server may have started,
|
||
|
// but before the client tries to auto-connect
|
||
|
if ( com_dedicated->modified ) {
|
||
|
// get the latched value
|
||
|
Cvar_Get( "dedicated", "0", 0 );
|
||
|
com_dedicated->modified = qfalse;
|
||
|
if ( !com_dedicated->integer ) {
|
||
|
SV_Shutdown( "dedicated set to 0" );
|
||
|
SV_RemoveDedicatedCommands();
|
||
|
#ifndef DEDICATED
|
||
|
CL_Init();
|
||
|
#endif
|
||
|
Sys_ShowConsole( com_viewlog->integer, qfalse );
|
||
|
#ifndef DEDICATED
|
||
|
gw_minimized = qfalse;
|
||
|
CL_StartHunkUsers();
|
||
|
#endif
|
||
|
} else {
|
||
|
#ifndef DEDICATED
|
||
|
CL_Shutdown( "", qfalse );
|
||
|
CL_ClearMemory();
|
||
|
#endif
|
||
|
Sys_ShowConsole( 1, qtrue );
|
||
|
SV_AddDedicatedCommands();
|
||
|
gw_minimized = qtrue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef DEDICATED
|
||
|
if ( com_speeds->integer ) {
|
||
|
timeAfter = Sys_Milliseconds ();
|
||
|
timeBeforeEvents = timeAfter;
|
||
|
timeBeforeClient = timeAfter;
|
||
|
}
|
||
|
#else
|
||
|
//
|
||
|
// client system
|
||
|
//
|
||
|
if ( !com_dedicated->integer ) {
|
||
|
//
|
||
|
// run event loop a second time to get server to client packets
|
||
|
// without a frame of latency
|
||
|
//
|
||
|
if ( com_speeds->integer ) {
|
||
|
timeBeforeEvents = Sys_Milliseconds();
|
||
|
}
|
||
|
Com_EventLoop();
|
||
|
Cbuf_Execute();
|
||
|
|
||
|
//
|
||
|
// client side
|
||
|
//
|
||
|
if ( com_speeds->integer ) {
|
||
|
timeBeforeClient = Sys_Milliseconds();
|
||
|
}
|
||
|
|
||
|
CL_Frame( msec, realMsec );
|
||
|
|
||
|
if ( com_speeds->integer ) {
|
||
|
timeAfter = Sys_Milliseconds();
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
NET_FlushPacketQueue();
|
||
|
|
||
|
//
|
||
|
// report timing information
|
||
|
//
|
||
|
if ( com_speeds->integer ) {
|
||
|
int all, sv, ev, cl;
|
||
|
|
||
|
all = timeAfter - timeBeforeServer;
|
||
|
sv = timeBeforeEvents - timeBeforeServer;
|
||
|
ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents;
|
||
|
cl = timeAfter - timeBeforeClient;
|
||
|
sv -= time_game;
|
||
|
cl -= time_frontend + time_backend;
|
||
|
|
||
|
Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n",
|
||
|
com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// trace optimization tracking
|
||
|
//
|
||
|
if ( com_showtrace->integer ) {
|
||
|
|
||
|
extern int c_traces, c_brush_traces, c_patch_traces;
|
||
|
extern int c_pointcontents;
|
||
|
|
||
|
Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces,
|
||
|
c_brush_traces, c_patch_traces, c_pointcontents);
|
||
|
c_traces = 0;
|
||
|
c_brush_traces = 0;
|
||
|
c_patch_traces = 0;
|
||
|
c_pointcontents = 0;
|
||
|
}
|
||
|
|
||
|
com_frameNumber++;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
=================
|
||
|
Com_Shutdown
|
||
|
=================
|
||
|
*/
|
||
|
static void Com_Shutdown( void ) {
|
||
|
if ( logfile != FS_INVALID_HANDLE ) {
|
||
|
FS_FCloseFile( logfile );
|
||
|
logfile = FS_INVALID_HANDLE;
|
||
|
}
|
||
|
|
||
|
if ( com_journalFile != FS_INVALID_HANDLE ) {
|
||
|
FS_FCloseFile( com_journalFile );
|
||
|
com_journalFile = FS_INVALID_HANDLE;
|
||
|
}
|
||
|
|
||
|
if ( com_journalDataFile != FS_INVALID_HANDLE ) {
|
||
|
FS_FCloseFile( com_journalDataFile );
|
||
|
com_journalDataFile = FS_INVALID_HANDLE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
/*
|
||
|
===========================================
|
||
|
command line completion
|
||
|
===========================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Field_Clear
|
||
|
==================
|
||
|
*/
|
||
|
void Field_Clear( field_t *edit ) {
|
||
|
memset( edit->buffer, 0, sizeof( edit->buffer ) );
|
||
|
edit->cursor = 0;
|
||
|
edit->scroll = 0;
|
||
|
}
|
||
|
|
||
|
static const char *completionString;
|
||
|
static char shortestMatch[MAX_TOKEN_CHARS];
|
||
|
static int matchCount;
|
||
|
// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance)
|
||
|
static field_t *completionField;
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
FindMatches
|
||
|
===============
|
||
|
*/
|
||
|
static void FindMatches( const char *s ) {
|
||
|
int i, n;
|
||
|
|
||
|
if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) {
|
||
|
return;
|
||
|
}
|
||
|
matchCount++;
|
||
|
if ( matchCount == 1 ) {
|
||
|
Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
n = (int)strlen(s);
|
||
|
// cut shortestMatch to the amount common with s
|
||
|
for ( i = 0 ; shortestMatch[i] ; i++ ) {
|
||
|
if ( i >= n ) {
|
||
|
shortestMatch[i] = '\0';
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( tolower(shortestMatch[i]) != tolower(s[i]) ) {
|
||
|
shortestMatch[i] = '\0';
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
PrintMatches
|
||
|
===============
|
||
|
*/
|
||
|
static void PrintMatches( const char *s ) {
|
||
|
if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
|
||
|
Com_Printf( " %s\n", s );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
PrintCvarMatches
|
||
|
===============
|
||
|
*/
|
||
|
static void PrintCvarMatches( const char *s ) {
|
||
|
char value[ TRUNCATE_LENGTH ];
|
||
|
|
||
|
if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
|
||
|
Com_TruncateLongString( value, Cvar_VariableString( s ) );
|
||
|
Com_Printf( " %s = \"%s\"\n", s, value );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_FindFirstSeparator
|
||
|
===============
|
||
|
*/
|
||
|
static const char *Field_FindFirstSeparator( const char *s )
|
||
|
{
|
||
|
char c;
|
||
|
while ( (c = *s) != '\0' ) {
|
||
|
if ( c == ';' )
|
||
|
return s;
|
||
|
s++;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_AddSpace
|
||
|
===============
|
||
|
*/
|
||
|
static void Field_AddSpace( void )
|
||
|
{
|
||
|
size_t len = strlen( completionField->buffer );
|
||
|
if ( len && len < sizeof( completionField->buffer ) - 1 && completionField->buffer[ len - 1 ] != ' ' )
|
||
|
{
|
||
|
memcpy( completionField->buffer + len, " ", 2 );
|
||
|
completionField->cursor = (int)(len + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_Complete
|
||
|
===============
|
||
|
*/
|
||
|
static qboolean Field_Complete( void )
|
||
|
{
|
||
|
int completionOffset;
|
||
|
|
||
|
if( matchCount == 0 )
|
||
|
return qtrue;
|
||
|
|
||
|
completionOffset = strlen( completionField->buffer ) - strlen( completionString );
|
||
|
|
||
|
Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch,
|
||
|
sizeof( completionField->buffer ) - completionOffset );
|
||
|
|
||
|
completionField->cursor = strlen( completionField->buffer );
|
||
|
|
||
|
if( matchCount == 1 )
|
||
|
{
|
||
|
Field_AddSpace();
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
Com_Printf( "]%s\n", completionField->buffer );
|
||
|
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_CompleteKeyname
|
||
|
===============
|
||
|
*/
|
||
|
void Field_CompleteKeyname( void )
|
||
|
{
|
||
|
matchCount = 0;
|
||
|
shortestMatch[ 0 ] = '\0';
|
||
|
|
||
|
Key_KeynameCompletion( FindMatches );
|
||
|
|
||
|
if ( !Field_Complete() )
|
||
|
Key_KeynameCompletion( PrintMatches );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_CompleteKeyBind
|
||
|
===============
|
||
|
*/
|
||
|
void Field_CompleteKeyBind( int key )
|
||
|
{
|
||
|
const char *value;
|
||
|
int vlen;
|
||
|
int blen;
|
||
|
|
||
|
value = Key_GetBinding( key );
|
||
|
if ( value == NULL || *value == '\0' )
|
||
|
return;
|
||
|
|
||
|
blen = (int)strlen( completionField->buffer );
|
||
|
vlen = (int)strlen( value );
|
||
|
|
||
|
if ( Field_FindFirstSeparator( (char*)value ) )
|
||
|
{
|
||
|
value = va( "\"%s\"", value );
|
||
|
vlen += 2;
|
||
|
}
|
||
|
|
||
|
if ( vlen + blen > sizeof( completionField->buffer ) - 1 )
|
||
|
{
|
||
|
//vlen = sizeof( completionField->buffer ) - 1 - blen;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
memcpy( completionField->buffer + blen, value, vlen + 1 );
|
||
|
completionField->cursor = blen + vlen;
|
||
|
|
||
|
Field_AddSpace();
|
||
|
}
|
||
|
|
||
|
|
||
|
static void Field_CompleteCvarValue( const char *value, const char *current )
|
||
|
{
|
||
|
int vlen;
|
||
|
int blen;
|
||
|
|
||
|
if ( *value == '\0' )
|
||
|
return;
|
||
|
|
||
|
blen = (int)strlen( completionField->buffer );
|
||
|
vlen = (int)strlen( value );
|
||
|
|
||
|
if ( *current != '\0' )
|
||
|
{
|
||
|
#if 0
|
||
|
int clen = (int) strlen( current );
|
||
|
if ( strncmp( value, current, clen ) == 0 ) // current value is a substring of new value
|
||
|
{
|
||
|
value += clen;
|
||
|
vlen -= clen;
|
||
|
}
|
||
|
else // modification, nothing to complete
|
||
|
#endif
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( Field_FindFirstSeparator( (char*)value ) )
|
||
|
{
|
||
|
value = va( "\"%s\"", value );
|
||
|
vlen += 2;
|
||
|
}
|
||
|
|
||
|
if ( vlen + blen > sizeof( completionField->buffer ) - 1 )
|
||
|
{
|
||
|
//vlen = sizeof( completionField->buffer ) - 1 - blen;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( blen > 1 )
|
||
|
{
|
||
|
if ( completionField->buffer[ blen-1 ] == '"' && completionField->buffer[ blen-2 ] == ' ' )
|
||
|
{
|
||
|
completionField->buffer[ blen-- ] = '\0'; // strip starting quote
|
||
|
}
|
||
|
}
|
||
|
|
||
|
memcpy( completionField->buffer + blen, value, vlen + 1 );
|
||
|
completionField->cursor = vlen + blen;
|
||
|
|
||
|
Field_AddSpace();
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_CompleteFilename
|
||
|
===============
|
||
|
*/
|
||
|
void Field_CompleteFilename( const char *dir, const char *ext, qboolean stripExt, int flags )
|
||
|
{
|
||
|
matchCount = 0;
|
||
|
shortestMatch[ 0 ] = '\0';
|
||
|
|
||
|
FS_FilenameCompletion( dir, ext, stripExt, FindMatches, flags );
|
||
|
|
||
|
if ( !Field_Complete() )
|
||
|
FS_FilenameCompletion( dir, ext, stripExt, PrintMatches, flags );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_CompleteCommand
|
||
|
===============
|
||
|
*/
|
||
|
void Field_CompleteCommand( const char *cmd, qboolean doCommands, qboolean doCvars )
|
||
|
{
|
||
|
int completionArgument;
|
||
|
|
||
|
// Skip leading whitespace and quotes
|
||
|
cmd = Com_SkipCharset( cmd, " \"" );
|
||
|
|
||
|
Cmd_TokenizeStringIgnoreQuotes( cmd );
|
||
|
completionArgument = Cmd_Argc();
|
||
|
|
||
|
// If there is trailing whitespace on the cmd
|
||
|
if( *( cmd + strlen( cmd ) - 1 ) == ' ' )
|
||
|
{
|
||
|
completionString = "";
|
||
|
completionArgument++;
|
||
|
}
|
||
|
else
|
||
|
completionString = Cmd_Argv( completionArgument - 1 );
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
// Unconditionally add a '\' to the start of the buffer
|
||
|
if ( completionField->buffer[ 0 ] && completionField->buffer[ 0 ] != '\\' )
|
||
|
{
|
||
|
if( completionField->buffer[ 0 ] != '/' )
|
||
|
{
|
||
|
// Buffer is full, refuse to complete
|
||
|
if ( strlen( completionField->buffer ) + 1 >= sizeof( completionField->buffer ) )
|
||
|
return;
|
||
|
|
||
|
memmove( &completionField->buffer[ 1 ],
|
||
|
&completionField->buffer[ 0 ],
|
||
|
strlen( completionField->buffer ) + 1 );
|
||
|
completionField->cursor++;
|
||
|
}
|
||
|
|
||
|
completionField->buffer[ 0 ] = '\\';
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( completionArgument > 1 )
|
||
|
{
|
||
|
const char *baseCmd = Cmd_Argv( 0 );
|
||
|
const char *p;
|
||
|
|
||
|
#ifndef DEDICATED
|
||
|
// This should always be true
|
||
|
if ( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' )
|
||
|
baseCmd++;
|
||
|
#endif
|
||
|
|
||
|
if( ( p = Field_FindFirstSeparator( cmd ) ) != NULL )
|
||
|
{
|
||
|
Field_CompleteCommand( p + 1, qtrue, qtrue ); // Compound command
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
qboolean argumentCompleted = Cmd_CompleteArgument( baseCmd, cmd, completionArgument );
|
||
|
if ( ( matchCount == 1 || argumentCompleted ) && doCvars )
|
||
|
{
|
||
|
if ( cmd[0] == '/' || cmd[0] == '\\' )
|
||
|
cmd++;
|
||
|
Cmd_TokenizeString( cmd );
|
||
|
Field_CompleteCvarValue( Cvar_VariableString( Cmd_Argv( 0 ) ), Cmd_Argv( 1 ) );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( completionString[0] == '\\' || completionString[0] == '/' )
|
||
|
completionString++;
|
||
|
|
||
|
matchCount = 0;
|
||
|
shortestMatch[ 0 ] = '\0';
|
||
|
|
||
|
if ( completionString[0] == '\0' ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( doCommands )
|
||
|
Cmd_CommandCompletion( FindMatches );
|
||
|
|
||
|
if ( doCvars )
|
||
|
Cvar_CommandCompletion( FindMatches );
|
||
|
|
||
|
if ( !Field_Complete() )
|
||
|
{
|
||
|
// run through again, printing matches
|
||
|
if ( doCommands )
|
||
|
Cmd_CommandCompletion( PrintMatches );
|
||
|
|
||
|
if ( doCvars )
|
||
|
Cvar_CommandCompletion( PrintCvarMatches );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
===============
|
||
|
Field_AutoComplete
|
||
|
|
||
|
Perform Tab expansion
|
||
|
===============
|
||
|
*/
|
||
|
void Field_AutoComplete( field_t *field )
|
||
|
{
|
||
|
completionField = field;
|
||
|
|
||
|
Field_CompleteCommand( completionField->buffer, qtrue, qtrue );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_RandomBytes
|
||
|
|
||
|
fills string array with len random bytes, preferably from the OS randomizer
|
||
|
==================
|
||
|
*/
|
||
|
void Com_RandomBytes( byte *string, int len )
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if ( Sys_RandomBytes( string, len ) )
|
||
|
return;
|
||
|
|
||
|
Com_Printf( S_COLOR_YELLOW "Com_RandomBytes: using weak randomization\n" );
|
||
|
srand( time( NULL ) );
|
||
|
for( i = 0; i < len; i++ )
|
||
|
string[i] = (unsigned char)( rand() % 256 );
|
||
|
}
|
||
|
|
||
|
|
||
|
static qboolean strgtr(const char *s0, const char *s1) {
|
||
|
int l0, l1, i;
|
||
|
|
||
|
l0 = strlen( s0 );
|
||
|
l1 = strlen( s1 );
|
||
|
|
||
|
if ( l1 < l0 ) {
|
||
|
l0 = l1;
|
||
|
}
|
||
|
|
||
|
for( i = 0; i < l0; i++ ) {
|
||
|
if ( s1[i] > s0[i] ) {
|
||
|
return qtrue;
|
||
|
}
|
||
|
if ( s1[i] < s0[i] ) {
|
||
|
return qfalse;
|
||
|
}
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_SortList
|
||
|
==================
|
||
|
*/
|
||
|
static void Com_SortList( char **list, int n )
|
||
|
{
|
||
|
const char *m;
|
||
|
char *temp;
|
||
|
int i, j;
|
||
|
i = 0;
|
||
|
j = n;
|
||
|
m = list[ n >> 1 ];
|
||
|
do
|
||
|
{
|
||
|
while ( strcmp( list[i], m ) < 0 ) i++;
|
||
|
while ( strcmp( list[j], m ) > 0 ) j--;
|
||
|
if ( i <= j )
|
||
|
{
|
||
|
temp = list[i];
|
||
|
list[i] = list[j];
|
||
|
list[j] = temp;
|
||
|
i++;
|
||
|
j--;
|
||
|
}
|
||
|
}
|
||
|
while ( i <= j );
|
||
|
if ( j > 0 ) Com_SortList( list, j );
|
||
|
if ( n > i ) Com_SortList( list+i, n-i );
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
==================
|
||
|
Com_SortFileList
|
||
|
==================
|
||
|
*/
|
||
|
void Com_SortFileList( char **list, int nfiles, int fastSort )
|
||
|
{
|
||
|
if ( nfiles > 1 && fastSort )
|
||
|
{
|
||
|
Com_SortList( list, nfiles-1 );
|
||
|
}
|
||
|
else // defrag mod demo UI can't handle _properly_ sorted directories
|
||
|
{
|
||
|
int i, flag;
|
||
|
do {
|
||
|
flag = 0;
|
||
|
for( i = 1; i < nfiles; i++ ) {
|
||
|
if ( strgtr( list[i-1], list[i] ) ) {
|
||
|
char *temp = list[i];
|
||
|
list[i] = list[i-1];
|
||
|
list[i-1] = temp;
|
||
|
flag = 1;
|
||
|
}
|
||
|
}
|
||
|
} while( flag );
|
||
|
}
|
||
|
}
|