2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
7
Quake III Arena source code is free software; you can redistribute it
8
and/or modify it under the terms of the GNU General Public License as
9
published by the Free Software Foundation; either version 2 of the License,
10
or (at your option) any later version.
12
Quake III Arena source code is distributed in the hope that it will be
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with Quake III Arena source code; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20
===========================================================================
22
// common.c -- misc functions used in client and server
28
#include <netinet/in.h>
29
#include <sys/stat.h> // umask
34
int demo_protocols[] =
37
#define MAX_NUM_ARGVS 50
39
#define MIN_DEDICATED_COMHUNKMEGS 1
40
#define MIN_COMHUNKMEGS 56
41
#define DEF_COMHUNKMEGS 64
42
#define DEF_COMZONEMEGS 24
43
#define XSTRING(x) STRING(x)
45
#define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS)
46
#define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS)
49
char *com_argv[MAX_NUM_ARGVS+1];
51
jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame
55
static fileHandle_t logfile;
56
fileHandle_t com_journalFile; // events are written here
57
fileHandle_t com_journalDataFile; // config files are written here
61
cvar_t *com_developer;
62
cvar_t *com_dedicated;
63
cvar_t *com_timescale;
64
cvar_t *com_fixedtime;
65
cvar_t *com_dropsim; // 0.0 to 1.0, simulated packet drops
70
cvar_t *com_sv_running;
71
cvar_t *com_cl_running;
72
cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print
73
cvar_t *com_showtrace;
76
cvar_t *com_buildScript; // for automated data building scripts
77
cvar_t *com_introPlayed;
80
cvar_t *cl_packetdelay;
81
cvar_t *sv_packetdelay;
82
cvar_t *com_cameraMode;
83
#if defined(_WIN32) && defined(_DEBUG)
84
cvar_t *com_noErrorInterrupt;
89
int time_frontend; // renderer frontend time
90
int time_backend; // renderer backend time
96
qboolean com_errorEntered;
97
qboolean com_fullyInitialized;
99
char com_errorMessage[MAXPRINTMSG];
101
void Com_WriteConfig_f( void );
102
void CIN_CloseAllVideos( void );
104
//============================================================================
106
static char *rd_buffer;
107
static int rd_buffersize;
108
static void (*rd_flush)( char *buffer );
110
void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) )
112
if (!buffer || !buffersize || !flush)
115
rd_buffersize = buffersize;
121
void Com_EndRedirect (void)
136
Both client and server can use this, and it will output
137
to the apropriate place.
139
A raw string should NEVER be passed as fmt, because of "%f" type crashers.
142
void QDECL Com_Printf( const char *fmt, ... ) {
144
char msg[MAXPRINTMSG];
145
static qboolean opening_qconsole = qfalse;
148
va_start (argptr,fmt);
149
Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
153
if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) {
157
Q_strcat(rd_buffer, rd_buffersize, msg);
158
// TTimo nooo .. that would defeat the purpose
159
//rd_flush(rd_buffer);
164
// echo to console if we're not a dedicated server
165
if ( com_dedicated && !com_dedicated->integer ) {
166
CL_ConsolePrint( msg );
169
// echo to dedicated console and early console
173
if ( com_logfile && com_logfile->integer ) {
174
// TTimo: only open the qconsole.log if the filesystem is in an initialized state
175
// also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on)
176
if ( !logfile && FS_Initialized() && !opening_qconsole) {
180
opening_qconsole = qtrue;
183
newtime = localtime( &aclock );
185
logfile = FS_FOpenFileWrite( "qconsole.log" );
189
Com_Printf( "logfile opened on %s\n", asctime( newtime ) );
191
if ( com_logfile->integer > 1 )
193
// force it to not buffer so we get valid
194
// data even if we are crashing
195
FS_ForceFlush(logfile);
200
Com_Printf("Opening qconsole.log failed!\n");
201
Cvar_SetValue("logfile", 0);
204
opening_qconsole = qfalse;
206
if ( logfile && FS_Initialized()) {
207
FS_Write(msg, strlen(msg), logfile);
217
A Com_Printf that only shows up if the "developer" cvar is set
220
void QDECL Com_DPrintf( const char *fmt, ...) {
222
char msg[MAXPRINTMSG];
224
if ( !com_developer || !com_developer->integer ) {
225
return; // don't confuse non-developers with techie stuff...
228
va_start (argptr,fmt);
229
Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
232
Com_Printf ("%s", msg);
239
Both client and server can use this, and it will
240
do the apropriate things.
243
void QDECL Com_Error( int code, const char *fmt, ... ) {
245
static int lastErrorTime;
246
static int errorCount;
249
#if defined(_WIN32) && defined(_DEBUG)
250
if ( code != ERR_DISCONNECT && code != ERR_NEED_CD ) {
251
if (!com_noErrorInterrupt->integer) {
259
// when we are running automated scripts, make sure we
260
// know if anything failed
261
if ( com_buildScript && com_buildScript->integer ) {
265
// if we are getting a solid stream of ERR_DROP, do an ERR_FATAL
266
currentTime = Sys_Milliseconds();
267
if ( currentTime - lastErrorTime < 100 ) {
268
if ( ++errorCount > 3 ) {
274
lastErrorTime = currentTime;
276
if ( com_errorEntered ) {
277
Sys_Error( "recursive error after: %s", com_errorMessage );
279
com_errorEntered = qtrue;
281
va_start (argptr,fmt);
282
vsprintf (com_errorMessage,fmt,argptr);
285
if (code != ERR_DISCONNECT && code != ERR_NEED_CD)
286
Cvar_Set("com_errorMessage", com_errorMessage);
288
if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) {
289
CL_Disconnect( qtrue );
291
// make sure we can get at our local stuff
292
FS_PureServerSetLoadedPaks("", "");
293
com_errorEntered = qfalse;
294
longjmp (abortframe, -1);
295
} else if (code == ERR_DROP) {
296
Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage);
297
SV_Shutdown (va("Server crashed: %s", com_errorMessage));
298
CL_Disconnect( qtrue );
300
FS_PureServerSetLoadedPaks("", "");
301
com_errorEntered = qfalse;
302
longjmp (abortframe, -1);
303
} else if ( code == ERR_NEED_CD ) {
304
SV_Shutdown( "Server didn't have CD" );
305
if ( com_cl_running && com_cl_running->integer ) {
306
CL_Disconnect( qtrue );
308
com_errorEntered = qfalse;
311
Com_Printf("Server didn't have CD\n" );
313
FS_PureServerSetLoadedPaks("", "");
314
longjmp (abortframe, -1);
317
SV_Shutdown (va("Server fatal crashed: %s", com_errorMessage));
322
Sys_Error ("%s", com_errorMessage);
330
Both client and server can use this, and it will
331
do the apropriate things.
334
void Com_Quit_f( void ) {
335
// don't try to shutdown if we are in a recursive error
336
if ( !com_errorEntered ) {
337
SV_Shutdown ("Server quit");
348
============================================================================
350
COMMAND LINE FUNCTIONS
352
+ characters seperate the commandLine string into multiple console
355
All of these are valid:
357
quake3 +set test blah +map test
358
quake3 set test blah+map test
359
quake3 set test blah + map test
361
============================================================================
364
#define MAX_CONSOLE_LINES 32
365
int com_numConsoleLines;
366
char *com_consoleLines[MAX_CONSOLE_LINES];
372
Break it up into multiple console lines
375
void Com_ParseCommandLine( char *commandLine ) {
377
com_consoleLines[0] = commandLine;
378
com_numConsoleLines = 1;
380
while ( *commandLine ) {
381
if (*commandLine == '"') {
384
// look for a + seperating character
385
// if commandLine came from a file, we might have real line seperators
386
if ( (*commandLine == '+' && !inq) || *commandLine == '\n' || *commandLine == '\r' ) {
387
if ( com_numConsoleLines == MAX_CONSOLE_LINES ) {
390
com_consoleLines[com_numConsoleLines] = commandLine + 1;
391
com_numConsoleLines++;
403
Check for "safe" on the command line, which will
404
skip loading of q3config.cfg
407
qboolean Com_SafeMode( void ) {
410
for ( i = 0 ; i < com_numConsoleLines ; i++ ) {
411
Cmd_TokenizeString( com_consoleLines[i] );
412
if ( !Q_stricmp( Cmd_Argv(0), "safe" )
413
|| !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) {
414
com_consoleLines[i][0] = 0;
426
Searches for command line parameters that are set commands.
427
If match is not NULL, only that cvar will be looked for.
428
That is necessary because cddir and basedir need to be set
429
before the filesystem is started, but all other sets should
430
be after execing the config and default.
433
void Com_StartupVariable( const char *match ) {
438
for (i=0 ; i < com_numConsoleLines ; i++) {
439
Cmd_TokenizeString( com_consoleLines[i] );
440
if ( strcmp( Cmd_Argv(0), "set" ) ) {
445
if ( !match || !strcmp( s, match ) ) {
446
Cvar_Set( s, Cmd_Argv(2) );
447
cv = Cvar_Get( s, "", 0 );
448
cv->flags |= CVAR_USER_CREATED;
449
// com_consoleLines[i] = 0;
457
Com_AddStartupCommands
459
Adds command line parameters as script statements
460
Commands are seperated by + signs
462
Returns qtrue if any late commands were added, which
463
will keep the demoloop from immediately starting
466
qboolean Com_AddStartupCommands( void ) {
471
// quote every token, so args with semicolons can work
472
for (i=0 ; i < com_numConsoleLines ; i++) {
473
if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) {
477
// set commands won't override menu startup
478
if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) {
481
Cbuf_AddText( com_consoleLines[i] );
482
Cbuf_AddText( "\n" );
489
//============================================================================
491
void Info_Print( const char *s ) {
502
while (*s && *s != '\\')
508
Com_Memset (o, ' ', 20-l);
513
Com_Printf ("%s", key);
517
Com_Printf ("MISSING VALUE\n");
523
while (*s && *s != '\\')
529
Com_Printf ("%s\n", value);
538
char *Com_StringContains(char *str1, char *str2, int casesensitive) {
541
len = strlen(str1) - strlen(str2);
542
for (i = 0; i <= len; i++, str1++) {
543
for (j = 0; str2[j]; j++) {
545
if (str1[j] != str2[j]) {
550
if (toupper(str1[j]) != toupper(str2[j])) {
567
int Com_Filter(char *filter, char *name, int casesensitive)
569
char buf[MAX_TOKEN_CHARS];
574
if (*filter == '*') {
576
for (i = 0; *filter; i++) {
577
if (*filter == '*' || *filter == '?') break;
583
ptr = Com_StringContains(name, buf, casesensitive);
584
if (!ptr) return qfalse;
585
name = ptr + strlen(buf);
588
else if (*filter == '?') {
592
else if (*filter == '[' && *(filter+1) == '[') {
595
else if (*filter == '[') {
598
while(*filter && !found) {
599
if (*filter == ']' && *(filter+1) != ']') break;
600
if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
602
if (*name >= *filter && *name <= *(filter+2)) found = qtrue;
605
if (toupper(*name) >= toupper(*filter) &&
606
toupper(*name) <= toupper(*(filter+2))) found = qtrue;
612
if (*filter == *name) found = qtrue;
615
if (toupper(*filter) == toupper(*name)) found = qtrue;
620
if (!found) return qfalse;
622
if (*filter == ']' && *(filter+1) != ']') break;
630
if (*filter != *name) return qfalse;
633
if (toupper(*filter) != toupper(*name)) return qfalse;
647
int Com_FilterPath(char *filter, char *name, int casesensitive)
650
char new_filter[MAX_QPATH];
651
char new_name[MAX_QPATH];
653
for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) {
654
if ( filter[i] == '\\' || filter[i] == ':' ) {
658
new_filter[i] = filter[i];
661
new_filter[i] = '\0';
662
for (i = 0; i < MAX_QPATH-1 && name[i]; i++) {
663
if ( name[i] == '\\' || name[i] == ':' ) {
667
new_name[i] = name[i];
671
return Com_Filter(new_filter, new_name, casesensitive);
679
int Com_HashKey(char *string, int maxlen) {
680
int register hash, i;
683
for (i = 0; i < maxlen && string[i] != '\0'; i++) {
684
hash += string[i] * (119 + i);
686
hash = (hash ^ (hash >> 10) ^ (hash >> 20));
695
int Com_RealTime(qtime_t *qtime) {
704
qtime->tm_sec = tms->tm_sec;
705
qtime->tm_min = tms->tm_min;
706
qtime->tm_hour = tms->tm_hour;
707
qtime->tm_mday = tms->tm_mday;
708
qtime->tm_mon = tms->tm_mon;
709
qtime->tm_year = tms->tm_year;
710
qtime->tm_wday = tms->tm_wday;
711
qtime->tm_yday = tms->tm_yday;
712
qtime->tm_isdst = tms->tm_isdst;
719
==============================================================================
721
ZONE MEMORY ALLOCATION
723
There is never any space between memblocks, and there will never be two
724
contiguous free memblocks.
726
The rover can be left pointing at a non-empty block
728
The zone calls are pretty much only used for small strings and structures,
729
all big things are allocated on the hunk.
730
==============================================================================
733
#define ZONEID 0x1d4a11
734
#define MINFRAGMENT 64
736
typedef struct zonedebug_s {
743
typedef struct memblock_s {
744
int size; // including the header and possibly tiny fragments
745
int tag; // a tag of 0 is a free block
746
struct memblock_s *next, *prev;
747
int id; // should be ZONEID
754
int size; // total bytes malloced, including header
755
int used; // total bytes used
756
memblock_t blocklist; // start / end cap for linked list
760
// main zone for all "dynamic" memory allocation
762
// we also have a small zone for small allocations that would only
763
// fragment the main zone (think of cvar and cmd strings)
764
memzone_t *smallzone;
766
void Z_CheckHeap( void );
769
========================
771
========================
773
void Z_ClearZone( memzone_t *zone, int size ) {
776
// set the entire zone to one free block
778
zone->blocklist.next = zone->blocklist.prev = block =
779
(memblock_t *)( (byte *)zone + sizeof(memzone_t) );
780
zone->blocklist.tag = 1; // in use block
781
zone->blocklist.id = 0;
782
zone->blocklist.size = 0;
787
block->prev = block->next = &zone->blocklist;
788
block->tag = 0; // free block
790
block->size = size - sizeof(memzone_t);
794
========================
795
Z_AvailableZoneMemory
796
========================
798
int Z_AvailableZoneMemory( memzone_t *zone ) {
799
return zone->size - zone->used;
803
========================
805
========================
807
int Z_AvailableMemory( void ) {
808
return Z_AvailableZoneMemory( mainzone );
812
========================
814
========================
816
void Z_Free( void *ptr ) {
817
memblock_t *block, *other;
821
Com_Error( ERR_DROP, "Z_Free: NULL pointer" );
824
block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
825
if (block->id != ZONEID) {
826
Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" );
828
if (block->tag == 0) {
829
Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" );
832
if (block->tag == TAG_STATIC) {
836
// check the memory trash tester
837
if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) {
838
Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" );
841
if (block->tag == TAG_SMALL) {
848
zone->used -= block->size;
849
// set the block to something that should cause problems
850
// if it is referenced...
851
Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) );
853
block->tag = 0; // mark as free
857
// merge with previous free block
858
other->size += block->size;
859
other->next = block->next;
860
other->next->prev = other;
861
if (block == zone->rover) {
871
// merge the next free block onto the end
872
block->size += other->size;
873
block->next = other->next;
874
block->next->prev = block;
875
if (other == zone->rover) {
887
void Z_FreeTags( int tag ) {
891
if ( tag == TAG_SMALL ) {
898
// use the rover as our pointer, because
899
// Z_Free automatically adjusts it
900
zone->rover = zone->blocklist.next;
902
if ( zone->rover->tag == tag ) {
904
Z_Free( (void *)(zone->rover + 1) );
907
zone->rover = zone->rover->next;
908
} while ( zone->rover != &zone->blocklist );
918
void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ) {
920
void *Z_TagMalloc( int size, int tag ) {
922
int extra, allocSize;
923
memblock_t *start, *rover, *new, *base;
927
Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" );
930
if ( tag == TAG_SMALL ) {
939
// scan through the block list looking for the first free block
940
// of sufficient size
942
size += sizeof(memblock_t); // account for size of block header
943
size += 4; // space for memory trash tester
944
size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary
946
base = rover = zone->rover;
950
if (rover == start) {
954
// scaned all the way around the list
955
Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",
956
size, zone == smallzone ? "small" : "main");
960
base = rover = rover->next;
964
} while (base->tag || base->size < size);
967
// found a block big enough
969
extra = base->size - size;
970
if (extra > MINFRAGMENT) {
971
// there will be a free fragment after the allocated block
972
new = (memblock_t *) ((byte *)base + size );
974
new->tag = 0; // free block
977
new->next = base->next;
978
new->next->prev = new;
983
base->tag = tag; // no longer a free block
985
zone->rover = base->next; // next allocation will start looking here
986
zone->used += base->size; //
991
base->d.label = label;
994
base->d.allocSize = allocSize;
997
// marker for memory trash testing
998
*(int *)((byte *)base + base->size - 4) = ZONEID;
1000
return (void *) ((byte *)base + sizeof(memblock_t));
1004
========================
1006
========================
1009
void *Z_MallocDebug( int size, char *label, char *file, int line ) {
1011
void *Z_Malloc( int size ) {
1015
//Z_CheckHeap (); // DEBUG
1018
buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line );
1020
buf = Z_TagMalloc( size, TAG_GENERAL );
1022
Com_Memset( buf, 0, size );
1028
void *S_MallocDebug( int size, char *label, char *file, int line ) {
1029
return Z_TagMallocDebug( size, TAG_SMALL, label, file, line );
1032
void *S_Malloc( int size ) {
1033
return Z_TagMalloc( size, TAG_SMALL );
1038
========================
1040
========================
1042
void Z_CheckHeap( void ) {
1045
for (block = mainzone->blocklist.next ; ; block = block->next) {
1046
if (block->next == &mainzone->blocklist) {
1047
break; // all blocks have been hit
1049
if ( (byte *)block + block->size != (byte *)block->next)
1050
Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block\n" );
1051
if ( block->next->prev != block) {
1052
Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link\n" );
1054
if ( !block->tag && !block->next->tag ) {
1055
Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks\n" );
1061
========================
1063
========================
1065
void Z_LogZoneHeap( memzone_t *zone, char *name ) {
1067
char dump[32], *ptr;
1072
int size, allocSize, numBlocks;
1074
if (!logfile || !FS_Initialized())
1076
size = allocSize = numBlocks = 0;
1077
Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name);
1078
FS_Write(buf, strlen(buf), logfile);
1079
for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) {
1082
ptr = ((char *) block) + sizeof(memblock_t);
1084
for (i = 0; i < 20 && i < block->d.allocSize; i++) {
1085
if (ptr[i] >= 32 && ptr[i] < 127) {
1093
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);
1094
FS_Write(buf, strlen(buf), logfile);
1095
allocSize += block->d.allocSize;
1097
size += block->size;
1102
// subtract debug memory
1103
size -= numBlocks * sizeof(zonedebug_t);
1105
allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment
1107
Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks);
1108
FS_Write(buf, strlen(buf), logfile);
1109
Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name);
1110
FS_Write(buf, strlen(buf), logfile);
1114
========================
1116
========================
1118
void Z_LogHeap( void ) {
1119
Z_LogZoneHeap( mainzone, "MAIN" );
1120
Z_LogZoneHeap( smallzone, "SMALL" );
1123
// static mem blocks to reduce a lot of small zone overhead
1124
typedef struct memstatic_s {
1129
// bk001204 - initializer brackets
1130
memstatic_t emptystring =
1131
{ {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} };
1132
memstatic_t numberstring[] = {
1133
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} },
1134
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} },
1135
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} },
1136
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} },
1137
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} },
1138
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} },
1139
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} },
1140
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} },
1141
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} },
1142
{ {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} }
1146
========================
1149
NOTE: never write over the memory CopyString returns because
1150
memory from a memstatic_t might be returned
1151
========================
1153
char *CopyString( const char *in ) {
1157
return ((char *)&emptystring) + sizeof(memblock_t);
1160
if (in[0] >= '0' && in[0] <= '9') {
1161
return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t);
1164
out = S_Malloc (strlen(in)+1);
1170
==============================================================================
1173
reproducable without history effects -- no out of memory errors on weird map to map changes
1174
allow restarting of the client without fragmentation
1175
minimize total pages in use at run time
1176
minimize total pages needed during load time
1178
Single block of memory with stack allocators coming from both ends towards the middle.
1180
One side is designated the temporary memory allocator.
1182
Temporary memory can be allocated and freed in any order.
1184
A highwater mark is kept of the most in use at any time.
1186
When there is no temporary memory allocated, the permanent and temp sides
1187
can be switched, allowing the already touched temp memory to be used for
1190
Temp memory must never be allocated on two ends at once, or fragmentation
1193
If we have any in-use temp memory, additional temp allocations must come from
1196
If not, we can choose to make either side the new temp side and push future
1197
permanent allocations to the other side. Permanent allocations should be
1198
kept on the side that has the current greatest wasted highwater mark.
1200
==============================================================================
1204
#define HUNK_MAGIC 0x89537892
1205
#define HUNK_FREE_MAGIC 0x89537893
1219
typedef struct hunkblock_s {
1222
struct hunkblock_s *next;
1228
static hunkblock_t *hunkblocks;
1230
static hunkUsed_t hunk_low, hunk_high;
1231
static hunkUsed_t *hunk_permanent, *hunk_temp;
1233
static byte *s_hunkData = NULL;
1234
static int s_hunkTotal;
1236
static int s_zoneTotal;
1237
static int s_smallZoneTotal;
1245
void Com_Meminfo_f( void ) {
1247
int zoneBytes, zoneBlocks;
1248
int smallZoneBytes, smallZoneBlocks;
1249
int botlibBytes, rendererBytes;
1256
for (block = mainzone->blocklist.next ; ; block = block->next) {
1257
if ( Cmd_Argc() != 1 ) {
1258
Com_Printf ("block:%p size:%7i tag:%3i\n",
1259
block, block->size, block->tag);
1262
zoneBytes += block->size;
1264
if ( block->tag == TAG_BOTLIB ) {
1265
botlibBytes += block->size;
1266
} else if ( block->tag == TAG_RENDERER ) {
1267
rendererBytes += block->size;
1271
if (block->next == &mainzone->blocklist) {
1272
break; // all blocks have been hit
1274
if ( (byte *)block + block->size != (byte *)block->next) {
1275
Com_Printf ("ERROR: block size does not touch the next block\n");
1277
if ( block->next->prev != block) {
1278
Com_Printf ("ERROR: next block doesn't have proper back link\n");
1280
if ( !block->tag && !block->next->tag ) {
1281
Com_Printf ("ERROR: two consecutive free blocks\n");
1286
smallZoneBlocks = 0;
1287
for (block = smallzone->blocklist.next ; ; block = block->next) {
1289
smallZoneBytes += block->size;
1293
if (block->next == &smallzone->blocklist) {
1294
break; // all blocks have been hit
1298
Com_Printf( "%8i bytes total hunk\n", s_hunkTotal );
1299
Com_Printf( "%8i bytes total zone\n", s_zoneTotal );
1301
Com_Printf( "%8i low mark\n", hunk_low.mark );
1302
Com_Printf( "%8i low permanent\n", hunk_low.permanent );
1303
if ( hunk_low.temp != hunk_low.permanent ) {
1304
Com_Printf( "%8i low temp\n", hunk_low.temp );
1306
Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater );
1308
Com_Printf( "%8i high mark\n", hunk_high.mark );
1309
Com_Printf( "%8i high permanent\n", hunk_high.permanent );
1310
if ( hunk_high.temp != hunk_high.permanent ) {
1311
Com_Printf( "%8i high temp\n", hunk_high.temp );
1313
Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater );
1315
Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent );
1317
if ( hunk_low.tempHighwater > hunk_low.permanent ) {
1318
unused += hunk_low.tempHighwater - hunk_low.permanent;
1320
if ( hunk_high.tempHighwater > hunk_high.permanent ) {
1321
unused += hunk_high.tempHighwater - hunk_high.permanent;
1323
Com_Printf( "%8i unused highwater\n", unused );
1325
Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks );
1326
Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes );
1327
Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes );
1328
Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) );
1329
Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes );
1336
Touch all known used data to make sure it is paged in
1339
void Com_TouchMemory( void ) {
1347
start = Sys_Milliseconds();
1351
j = hunk_low.permanent >> 2;
1352
for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
1353
sum += ((int *)s_hunkData)[i];
1356
i = ( s_hunkTotal - hunk_high.permanent ) >> 2;
1357
j = hunk_high.permanent >> 2;
1358
for ( ; i < j ; i+=64 ) { // only need to touch each page
1359
sum += ((int *)s_hunkData)[i];
1362
for (block = mainzone->blocklist.next ; ; block = block->next) {
1364
j = block->size >> 2;
1365
for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
1366
sum += ((int *)block)[i];
1369
if ( block->next == &mainzone->blocklist ) {
1370
break; // all blocks have been hit
1374
end = Sys_Milliseconds();
1376
Com_Printf( "Com_TouchMemory: %i msec\n", end - start );
1386
void Com_InitSmallZoneMemory( void ) {
1387
s_smallZoneTotal = 512 * 1024;
1388
// bk001205 - was malloc
1389
smallzone = calloc( s_smallZoneTotal, 1 );
1391
Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) );
1393
Z_ClearZone( smallzone, s_smallZoneTotal );
1398
void Com_InitZoneMemory( void ) {
1401
//FIXME: 05/01/06 com_zoneMegs is useless right now as neither q3config.cfg nor
1402
// Com_StartupVariable have been executed by this point. The net result is that
1403
// s_zoneTotal will always be set to the default value.
1405
// allocate the random block zone
1406
cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
1408
if ( cv->integer < DEF_COMZONEMEGS ) {
1409
s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS;
1411
s_zoneTotal = cv->integer * 1024 * 1024;
1414
// bk001205 - was malloc
1415
mainzone = calloc( s_zoneTotal, 1 );
1417
Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) );
1419
Z_ClearZone( mainzone, s_zoneTotal );
1428
void Hunk_Log( void) {
1431
int size, numBlocks;
1433
if (!logfile || !FS_Initialized())
1437
Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n");
1438
FS_Write(buf, strlen(buf), logfile);
1439
for (block = hunkblocks ; block; block = block->next) {
1441
Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label);
1442
FS_Write(buf, strlen(buf), logfile);
1444
size += block->size;
1447
Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
1448
FS_Write(buf, strlen(buf), logfile);
1449
Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
1450
FS_Write(buf, strlen(buf), logfile);
1458
void Hunk_SmallLog( void) {
1459
hunkblock_t *block, *block2;
1461
int size, locsize, numBlocks;
1463
if (!logfile || !FS_Initialized())
1465
for (block = hunkblocks ; block; block = block->next) {
1466
block->printed = qfalse;
1470
Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n");
1471
FS_Write(buf, strlen(buf), logfile);
1472
for (block = hunkblocks; block; block = block->next) {
1473
if (block->printed) {
1476
locsize = block->size;
1477
for (block2 = block->next; block2; block2 = block2->next) {
1478
if (block->line != block2->line) {
1481
if (Q_stricmp(block->file, block2->file)) {
1484
size += block2->size;
1485
locsize += block2->size;
1486
block2->printed = qtrue;
1489
Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label);
1490
FS_Write(buf, strlen(buf), logfile);
1492
size += block->size;
1495
Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
1496
FS_Write(buf, strlen(buf), logfile);
1497
Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
1498
FS_Write(buf, strlen(buf), logfile);
1506
void Com_InitHunkMemory( void ) {
1511
// make sure the file system has allocated and "not" freed any temp blocks
1512
// this allows the config and product id files ( journal files too ) to be loaded
1513
// by the file system without redunant routines in the file system utilizing different
1515
if (FS_LoadStack() != 0) {
1516
Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero");
1519
// allocate the stack based hunk allocator
1520
cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
1522
// if we are not dedicated min allocation is 56, otherwise min is 1
1523
if (com_dedicated && com_dedicated->integer) {
1524
nMinAlloc = MIN_DEDICATED_COMHUNKMEGS;
1525
pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n";
1528
nMinAlloc = MIN_COMHUNKMEGS;
1529
pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n";
1532
if ( cv->integer < nMinAlloc ) {
1533
s_hunkTotal = 1024 * 1024 * nMinAlloc;
1534
Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024));
1536
s_hunkTotal = cv->integer * 1024 * 1024;
1540
// bk001205 - was malloc
1541
s_hunkData = calloc( s_hunkTotal + 31, 1 );
1542
if ( !s_hunkData ) {
1543
Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) );
1546
s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 );
1549
Cmd_AddCommand( "meminfo", Com_Meminfo_f );
1551
Cmd_AddCommand( "zonelog", Z_LogHeap );
1554
Cmd_AddCommand( "hunklog", Hunk_Log );
1555
Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog );
1560
====================
1561
Hunk_MemoryRemaining
1562
====================
1564
int Hunk_MemoryRemaining( void ) {
1567
low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp;
1568
high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp;
1570
return s_hunkTotal - ( low + high );
1577
The server calls this after the level and game VM have been loaded
1580
void Hunk_SetMark( void ) {
1581
hunk_low.mark = hunk_low.permanent;
1582
hunk_high.mark = hunk_high.permanent;
1589
The client calls this before starting a vid_restart or snd_restart
1592
void Hunk_ClearToMark( void ) {
1593
hunk_low.permanent = hunk_low.temp = hunk_low.mark;
1594
hunk_high.permanent = hunk_high.temp = hunk_high.mark;
1602
qboolean Hunk_CheckMark( void ) {
1603
if( hunk_low.mark || hunk_high.mark ) {
1609
void CL_ShutdownCGame( void );
1610
void CL_ShutdownUI( void );
1611
void SV_ShutdownGameProgs( void );
1617
The server calls this before shutting down or loading a new map
1620
void Hunk_Clear( void ) {
1626
SV_ShutdownGameProgs();
1628
CIN_CloseAllVideos();
1631
hunk_low.permanent = 0;
1633
hunk_low.tempHighwater = 0;
1636
hunk_high.permanent = 0;
1638
hunk_high.tempHighwater = 0;
1640
hunk_permanent = &hunk_low;
1641
hunk_temp = &hunk_high;
1643
Com_Printf( "Hunk_Clear: reset the hunk ok\n" );
1650
static void Hunk_SwapBanks( void ) {
1653
// can't swap banks if there is any temp already allocated
1654
if ( hunk_temp->temp != hunk_temp->permanent ) {
1658
// if we have a larger highwater mark on this side, start making
1659
// our permanent allocations here and use the other side for temp
1660
if ( hunk_temp->tempHighwater - hunk_temp->permanent >
1661
hunk_permanent->tempHighwater - hunk_permanent->permanent ) {
1663
hunk_temp = hunk_permanent;
1664
hunk_permanent = swap;
1672
Allocate permanent (until the hunk is cleared) memory
1676
void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) {
1678
void *Hunk_Alloc( int size, ha_pref preference ) {
1682
if ( s_hunkData == NULL)
1684
Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" );
1687
// can't do preference if there is any temp allocated
1688
if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) {
1691
if (preference == h_low && hunk_permanent != &hunk_low) {
1693
} else if (preference == h_high && hunk_permanent != &hunk_high) {
1699
size += sizeof(hunkblock_t);
1702
// round to cacheline
1703
size = (size+31)&~31;
1705
if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) {
1710
Com_Error( ERR_DROP, "Hunk_Alloc failed on %i", size );
1713
if ( hunk_permanent == &hunk_low ) {
1714
buf = (void *)(s_hunkData + hunk_permanent->permanent);
1715
hunk_permanent->permanent += size;
1717
hunk_permanent->permanent += size;
1718
buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
1721
hunk_permanent->temp = hunk_permanent->permanent;
1723
Com_Memset( buf, 0, size );
1729
block = (hunkblock_t *) buf;
1730
block->size = size - sizeof(hunkblock_t);
1732
block->label = label;
1734
block->next = hunkblocks;
1736
buf = ((byte *) buf) + sizeof(hunkblock_t);
1744
Hunk_AllocateTempMemory
1746
This is used by the file loading system.
1747
Multiple files can be loaded in temporary memory.
1748
When the files-in-use count reaches zero, all temp memory will be deleted
1751
void *Hunk_AllocateTempMemory( int size ) {
1755
// return a Z_Malloc'd block if the hunk has not been initialized
1756
// this allows the config and product id files ( journal files too ) to be loaded
1757
// by the file system without redunant routines in the file system utilizing different
1759
if ( s_hunkData == NULL )
1761
return Z_Malloc(size);
1766
size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t );
1768
if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) {
1769
Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size );
1772
if ( hunk_temp == &hunk_low ) {
1773
buf = (void *)(s_hunkData + hunk_temp->temp);
1774
hunk_temp->temp += size;
1776
hunk_temp->temp += size;
1777
buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp );
1780
if ( hunk_temp->temp > hunk_temp->tempHighwater ) {
1781
hunk_temp->tempHighwater = hunk_temp->temp;
1784
hdr = (hunkHeader_t *)buf;
1785
buf = (void *)(hdr+1);
1787
hdr->magic = HUNK_MAGIC;
1790
// don't bother clearing, because we are going to load a file over it
1800
void Hunk_FreeTempMemory( void *buf ) {
1803
// free with Z_Free if the hunk has not been initialized
1804
// this allows the config and product id files ( journal files too ) to be loaded
1805
// by the file system without redunant routines in the file system utilizing different
1807
if ( s_hunkData == NULL )
1814
hdr = ( (hunkHeader_t *)buf ) - 1;
1815
if ( hdr->magic != HUNK_MAGIC ) {
1816
Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" );
1819
hdr->magic = HUNK_FREE_MAGIC;
1821
// this only works if the files are freed in stack order,
1822
// otherwise the memory will stay around until Hunk_ClearTempMemory
1823
if ( hunk_temp == &hunk_low ) {
1824
if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) {
1825
hunk_temp->temp -= hdr->size;
1827
Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
1830
if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) {
1831
hunk_temp->temp -= hdr->size;
1833
Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
1841
Hunk_ClearTempMemory
1843
The temp space is no longer needed. If we have left more
1844
touched but unused memory on this side, have future
1845
permanent allocs use this side.
1848
void Hunk_ClearTempMemory( void ) {
1849
if ( s_hunkData != NULL ) {
1850
hunk_temp->temp = hunk_temp->permanent;
1859
void Hunk_Trash( void ) {
1865
if ( s_hunkData == NULL )
1869
Com_Error(ERR_DROP, "hunk trashed\n");
1873
Cvar_Set("com_jp", "1");
1876
if ( hunk_permanent == &hunk_low ) {
1877
buf = (void *)(s_hunkData + hunk_permanent->permanent);
1879
buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
1881
length = hunk_permanent->permanent;
1883
if (length > 0x7FFFF) {
1884
//randomly trash data within buf
1885
rnd = random() * (length - 0x7FFFF);
1887
for (i = 0; i < 0x7FFFF; i++) {
1889
buf[rnd+i] ^= value;
1895
===================================================================
1897
EVENTS AND JOURNALING
1899
In addition to these events, .cfg files are also copied to the
1901
===================================================================
1904
// bk001129 - here we go again: upped from 64
1905
// FIXME TTimo blunt upping from 256 to 1024
1906
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=5
1907
#define MAX_PUSHED_EVENTS 1024
1908
// bk001129 - init, also static
1909
static int com_pushedEventsHead = 0;
1910
static int com_pushedEventsTail = 0;
1911
// bk001129 - static
1912
static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS];
1919
void Com_InitJournaling( void ) {
1920
Com_StartupVariable( "journal" );
1921
com_journal = Cvar_Get ("journal", "0", CVAR_INIT);
1922
if ( !com_journal->integer ) {
1926
if ( com_journal->integer == 1 ) {
1927
Com_Printf( "Journaling events\n");
1928
com_journalFile = FS_FOpenFileWrite( "journal.dat" );
1929
com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" );
1930
} else if ( com_journal->integer == 2 ) {
1931
Com_Printf( "Replaying journaled events\n");
1932
FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue );
1933
FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue );
1936
if ( !com_journalFile || !com_journalDataFile ) {
1937
Cvar_Set( "com_journal", "0" );
1938
com_journalFile = 0;
1939
com_journalDataFile = 0;
1940
Com_Printf( "Couldn't open journal files\n" );
1949
sysEvent_t Com_GetRealEvent( void ) {
1953
// either get an event from the system or the journal file
1954
if ( com_journal->integer == 2 ) {
1955
r = FS_Read( &ev, sizeof(ev), com_journalFile );
1956
if ( r != sizeof(ev) ) {
1957
Com_Error( ERR_FATAL, "Error reading from journal file" );
1959
if ( ev.evPtrLength ) {
1960
ev.evPtr = Z_Malloc( ev.evPtrLength );
1961
r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile );
1962
if ( r != ev.evPtrLength ) {
1963
Com_Error( ERR_FATAL, "Error reading from journal file" );
1967
ev = Sys_GetEvent();
1969
// write the journal value out if needed
1970
if ( com_journal->integer == 1 ) {
1971
r = FS_Write( &ev, sizeof(ev), com_journalFile );
1972
if ( r != sizeof(ev) ) {
1973
Com_Error( ERR_FATAL, "Error writing to journal file" );
1975
if ( ev.evPtrLength ) {
1976
r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile );
1977
if ( r != ev.evPtrLength ) {
1978
Com_Error( ERR_FATAL, "Error writing to journal file" );
1994
void Com_InitPushEvent( void ) {
1995
// clear the static buffer array
1996
// this requires SE_NONE to be accepted as a valid but NOP event
1997
memset( com_pushedEvents, 0, sizeof(com_pushedEvents) );
1998
// reset counters while we are at it
1999
// beware: GetEvent might still return an SE_NONE from the buffer
2000
com_pushedEventsHead = 0;
2001
com_pushedEventsTail = 0;
2010
void Com_PushEvent( sysEvent_t *event ) {
2012
static int printedWarning = 0; // bk001129 - init, bk001204 - explicit int
2014
ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ];
2016
if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) {
2018
// don't print the warning constantly, or it can give time for more...
2019
if ( !printedWarning ) {
2020
printedWarning = qtrue;
2021
Com_Printf( "WARNING: Com_PushEvent overflow\n" );
2025
Z_Free( ev->evPtr );
2027
com_pushedEventsTail++;
2029
printedWarning = qfalse;
2033
com_pushedEventsHead++;
2041
sysEvent_t Com_GetEvent( void ) {
2042
if ( com_pushedEventsHead > com_pushedEventsTail ) {
2043
com_pushedEventsTail++;
2044
return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ];
2046
return Com_GetRealEvent();
2051
Com_RunAndTimeServerPacket
2054
void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) {
2059
if ( com_speeds->integer ) {
2060
t1 = Sys_Milliseconds ();
2063
SV_PacketEvent( *evFrom, buf );
2065
if ( com_speeds->integer ) {
2066
t2 = Sys_Milliseconds ();
2068
if ( com_speeds->integer == 3 ) {
2069
Com_Printf( "SV_PacketEvent time: %i\n", msec );
2078
Returns last event time
2081
int Com_EventLoop( void ) {
2084
byte bufData[MAX_MSGLEN];
2087
MSG_Init( &buf, bufData, sizeof( bufData ) );
2090
NET_FlushPacketQueue();
2091
ev = Com_GetEvent();
2093
// if no more events are available
2094
if ( ev.evType == SE_NONE ) {
2095
// manually send packet events for the loopback channel
2096
while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) {
2097
CL_PacketEvent( evFrom, &buf );
2100
while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) {
2101
// if the server just shut down, flush the events
2102
if ( com_sv_running->integer ) {
2103
Com_RunAndTimeServerPacket( &evFrom, &buf );
2111
switch ( ev.evType ) {
2113
// bk001129 - was ev.evTime
2114
Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType );
2119
CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime );
2122
CL_CharEvent( ev.evValue );
2125
CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime );
2127
case SE_JOYSTICK_AXIS:
2128
CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime );
2131
Cbuf_AddText( (char *)ev.evPtr );
2132
Cbuf_AddText( "\n" );
2135
// this cvar allows simulation of connections that
2136
// drop a lot of packets. Note that loopback connections
2137
// don't go through here at all.
2138
if ( com_dropsim->value > 0 ) {
2141
if ( Q_random( &seed ) < com_dropsim->value ) {
2142
break; // drop this packet
2146
evFrom = *(netadr_t *)ev.evPtr;
2147
buf.cursize = ev.evPtrLength - sizeof( evFrom );
2149
// we must copy the contents of the message out, because
2150
// the event buffers are only large enough to hold the
2151
// exact payload, but channel messages need to be large
2152
// enough to hold fragment reassembly
2153
if ( (unsigned)buf.cursize > buf.maxsize ) {
2154
Com_Printf("Com_EventLoop: oversize packet\n");
2157
Com_Memcpy( buf.data, (byte *)((netadr_t *)ev.evPtr + 1), buf.cursize );
2158
if ( com_sv_running->integer ) {
2159
Com_RunAndTimeServerPacket( &evFrom, &buf );
2161
CL_PacketEvent( evFrom, &buf );
2166
// free any block data
2172
return 0; // never reached
2179
Can be used for profiling, but will be journaled accurately
2182
int Com_Milliseconds (void) {
2185
// get events and push them until we get a null event with the current time
2188
ev = Com_GetRealEvent();
2189
if ( ev.evType != SE_NONE ) {
2190
Com_PushEvent( &ev );
2192
} while ( ev.evType != SE_NONE );
2197
//============================================================================
2203
Just throw a fatal error to
2204
test error shutdown procedures
2207
static void Com_Error_f (void) {
2208
if ( Cmd_Argc() > 1 ) {
2209
Com_Error( ERR_DROP, "Testing drop error" );
2211
Com_Error( ERR_FATAL, "Testing fatal error" );
2220
Just freeze in place for a given number of seconds to test
2224
static void Com_Freeze_f (void) {
2228
if ( Cmd_Argc() != 2 ) {
2229
Com_Printf( "freeze <seconds>\n" );
2232
s = atof( Cmd_Argv(1) );
2234
start = Com_Milliseconds();
2237
now = Com_Milliseconds();
2238
if ( ( now - start ) * 0.001 > s ) {
2248
A way to force a bus error for development reasons
2251
static void Com_Crash_f( void ) {
2252
* ( int * ) 0 = 0x12345678;
2255
// TTimo: centralizing the cl_cdkey stuff after I discovered a buffer overflow problem with the dedicated server version
2256
// not sure it's necessary to have different defaults for regular and dedicated, but I don't want to risk it
2257
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=470
2259
char cl_cdkey[34] = " ";
2261
char cl_cdkey[34] = "123456789";
2269
qboolean CL_CDKeyValidate( const char *key, const char *checksum );
2270
void Com_ReadCDKey( const char *filename ) {
2273
char fbuffer[MAX_OSPATH];
2275
sprintf(fbuffer, "%s/q3key", filename);
2277
FS_SV_FOpenFileRead( fbuffer, &f );
2279
Q_strncpyz( cl_cdkey, " ", 17 );
2283
Com_Memset( buffer, 0, sizeof(buffer) );
2285
FS_Read( buffer, 16, f );
2288
if (CL_CDKeyValidate(buffer, NULL)) {
2289
Q_strncpyz( cl_cdkey, buffer, 17 );
2291
Q_strncpyz( cl_cdkey, " ", 17 );
2300
void Com_AppendCDKey( const char *filename ) {
2303
char fbuffer[MAX_OSPATH];
2305
sprintf(fbuffer, "%s/q3key", filename);
2307
FS_SV_FOpenFileRead( fbuffer, &f );
2309
Q_strncpyz( &cl_cdkey[16], " ", 17 );
2313
Com_Memset( buffer, 0, sizeof(buffer) );
2315
FS_Read( buffer, 16, f );
2318
if (CL_CDKeyValidate(buffer, NULL)) {
2319
strcat( &cl_cdkey[16], buffer );
2321
Q_strncpyz( &cl_cdkey[16], " ", 17 );
2325
#ifndef DEDICATED // bk001204
2331
static void Com_WriteCDKey( const char *filename, const char *ikey ) {
2333
char fbuffer[MAX_OSPATH];
2340
sprintf(fbuffer, "%s/q3key", filename);
2343
Q_strncpyz( key, ikey, 17 );
2345
if(!CL_CDKeyValidate(key, NULL) ) {
2350
savedumask = umask(0077);
2352
f = FS_SV_FOpenFileWrite( fbuffer );
2354
Com_Printf ("Couldn't write CD key to %s.\n", fbuffer );
2358
FS_Write( key, 16, f );
2360
FS_Printf( f, "\n// generated by quake, do not modify\r\n" );
2361
FS_Printf( f, "// Do not give this file to ANYONE.\r\n" );
2362
FS_Printf( f, "// id Software and Activision will NOT ask you to send this file to them.\r\n");
2374
static void Com_DetectAltivec(void)
2376
// Only detect if user hasn't forcibly disabled it.
2377
if (com_altivec->integer) {
2378
static qboolean altivec = qfalse;
2379
static qboolean detected = qfalse;
2381
altivec = Sys_DetectAltivec();
2386
Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support!
2397
void Com_Init( char *commandLine ) {
2400
Com_Printf( "%s %s %s\n", SVN_VERSION, PLATFORM_STRING, __DATE__ );
2402
if ( setjmp (abortframe) ) {
2403
Sys_Error ("Error during initialization");
2406
// bk001129 - do this before anything else decides to push events
2407
Com_InitPushEvent();
2409
Com_InitSmallZoneMemory();
2412
// prepare enough of the subsystems to handle
2413
// cvar and command buffer management
2414
Com_ParseCommandLine( commandLine );
2419
Com_InitZoneMemory();
2422
// override anything from the config files with command line args
2423
Com_StartupVariable( NULL );
2425
// get the developer cvar set as early as possible
2426
Com_StartupVariable( "developer" );
2428
// done early so bind command exists
2429
CL_InitKeyCommands();
2431
FS_InitFilesystem ();
2433
Com_InitJournaling();
2435
Cbuf_AddText ("exec default.cfg\n");
2437
// skip the q3config.cfg if "safe" is on the command line
2438
if ( !Com_SafeMode() ) {
2439
Cbuf_AddText ("exec q3config.cfg\n");
2442
Cbuf_AddText ("exec autoexec.cfg\n");
2446
// override anything from the config files with command line args
2447
Com_StartupVariable( NULL );
2449
// get dedicated here for proper hunk megs initialization
2451
com_dedicated = Cvar_Get ("dedicated", "1", CVAR_ROM);
2453
com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH);
2455
// allocate the stack based hunk allocator
2456
Com_InitHunkMemory();
2458
// if any archived cvars are modified after this, we will trigger a writing
2459
// of the config file
2460
cvar_modifiedFlags &= ~CVAR_ARCHIVE;
2463
// init commands and vars
2465
com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE);
2466
com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE);
2467
com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE);
2469
com_developer = Cvar_Get ("developer", "0", CVAR_TEMP );
2470
com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP );
2472
com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO );
2473
com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT);
2474
com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT);
2475
com_dropsim = Cvar_Get ("com_dropsim", "0", CVAR_CHEAT);
2476
com_viewlog = Cvar_Get( "viewlog", "0", CVAR_CHEAT );
2477
com_speeds = Cvar_Get ("com_speeds", "0", 0);
2478
com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT);
2479
com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT);
2481
cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM);
2482
sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM);
2483
cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT);
2484
sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT);
2485
com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM);
2486
com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM);
2487
com_buildScript = Cvar_Get( "com_buildScript", "0", 0 );
2489
com_introPlayed = Cvar_Get( "com_introplayed", "0", CVAR_ARCHIVE);
2491
#if defined(_WIN32) && defined(_DEBUG)
2492
com_noErrorInterrupt = Cvar_Get( "com_noErrorInterrupt", "0", 0 );
2495
if ( com_dedicated->integer ) {
2496
if ( !com_viewlog->integer ) {
2497
Cvar_Set( "viewlog", "1" );
2501
if ( com_developer && com_developer->integer ) {
2502
Cmd_AddCommand ("error", Com_Error_f);
2503
Cmd_AddCommand ("crash", Com_Crash_f );
2504
Cmd_AddCommand ("freeze", Com_Freeze_f);
2506
Cmd_AddCommand ("quit", Com_Quit_f);
2507
Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f );
2508
Cmd_AddCommand ("writeconfig", Com_WriteConfig_f );
2510
s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ );
2511
com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO );
2514
Netchan_Init( Com_Milliseconds() & 0xffff ); // pick a port value that should be nice and random
2518
com_dedicated->modified = qfalse;
2519
if ( !com_dedicated->integer ) {
2521
Sys_ShowConsole( com_viewlog->integer, qfalse );
2524
// set com_frameTime so that if a map is started on the
2525
// command line it will still be able to count on com_frameTime
2526
// being random enough for a serverid
2527
com_frameTime = Com_Milliseconds();
2529
// add + commands from command line
2530
if ( !Com_AddStartupCommands() ) {
2531
// if the user didn't give any commands, run default action
2532
if ( !com_dedicated->integer ) {
2533
Cbuf_AddText ("cinematic idlogo.RoQ\n");
2534
if( !com_introPlayed->integer ) {
2535
Cvar_Set( com_introPlayed->name, "1" );
2536
Cvar_Set( "nextmap", "cinematic intro.RoQ" );
2541
// start in full screen ui mode
2542
Cvar_Set("r_uiFullScreen", "1");
2544
CL_StartHunkUsers();
2546
// make sure single player is off by default
2547
Cvar_Set("ui_singlePlayerActive", "0");
2549
com_fullyInitialized = qtrue;
2551
// always set the cvar, but only print the info if it makes sense.
2552
Com_DetectAltivec();
2554
Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled");
2557
Com_Printf ("--- Common Initialization Complete ---\n");
2560
//==================================================================
2562
void Com_WriteConfigToFile( const char *filename ) {
2565
f = FS_FOpenFileWrite( filename );
2567
Com_Printf ("Couldn't write %s.\n", filename );
2571
FS_Printf (f, "// generated by quake, do not modify\n");
2572
Key_WriteBindings (f);
2573
Cvar_WriteVariables (f);
2580
Com_WriteConfiguration
2582
Writes key bindings and archived cvars to config file if modified
2585
void Com_WriteConfiguration( void ) {
2586
#ifndef DEDICATED // bk001204
2589
// if we are quiting without fully initializing, make sure
2590
// we don't write out anything
2591
if ( !com_fullyInitialized ) {
2595
if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) {
2598
cvar_modifiedFlags &= ~CVAR_ARCHIVE;
2600
Com_WriteConfigToFile( "q3config.cfg" );
2602
// bk001119 - tentative "not needed for dedicated"
2604
fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
2605
if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) {
2606
Com_WriteCDKey( fs->string, &cl_cdkey[16] );
2608
Com_WriteCDKey( BASEGAME, cl_cdkey );
2618
Write the config file to a specific name
2621
void Com_WriteConfig_f( void ) {
2622
char filename[MAX_QPATH];
2624
if ( Cmd_Argc() != 2 ) {
2625
Com_Printf( "Usage: writeconfig <filename>\n" );
2629
Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
2630
COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
2631
Com_Printf( "Writing %s.\n", filename );
2632
Com_WriteConfigToFile( filename );
2640
int Com_ModifyMsec( int msec ) {
2644
// modify time for debugging values
2646
if ( com_fixedtime->integer ) {
2647
msec = com_fixedtime->integer;
2648
} else if ( com_timescale->value ) {
2649
msec *= com_timescale->value;
2650
} else if (com_cameraMode->integer) {
2651
msec *= com_timescale->value;
2654
// don't let it scale below 1 msec
2655
if ( msec < 1 && com_timescale->value) {
2659
if ( com_dedicated->integer ) {
2660
// dedicated servers don't want to clamp for a much longer
2661
// period, because it would mess up all the client's views
2663
if (com_sv_running->integer && msec > 500)
2664
Com_Printf( "Hitch warning: %i msec frame time\n", msec );
2668
if ( !com_sv_running->integer ) {
2669
// clients of remote servers do not want to clamp time, because
2670
// it would skew their view of the server's time temporarily
2673
// for local single player gaming
2674
// we may want to clamp the time to prevent players from
2675
// flying off edges when something hitches.
2679
if ( msec > clampTime ) {
2691
void Com_Frame( void ) {
2694
static int lastTime;
2697
int timeBeforeFirstEvents;
2698
int timeBeforeServer;
2699
int timeBeforeEvents;
2700
int timeBeforeClient;
2707
if ( setjmp (abortframe) ) {
2708
return; // an ERR_DROP was thrown
2711
// bk001204 - init to zero.
2712
// also: might be clobbered by `longjmp' or `vfork'
2713
timeBeforeFirstEvents =0;
2714
timeBeforeServer =0;
2715
timeBeforeEvents =0;
2716
timeBeforeClient = 0;
2720
// old net chan encryption key
2723
// write config file if anything changed
2724
Com_WriteConfiguration();
2726
// if "viewlog" has been modified, show or hide the log console
2727
if ( com_viewlog->modified ) {
2728
if ( !com_dedicated->value ) {
2729
Sys_ShowConsole( com_viewlog->integer, qfalse );
2731
com_viewlog->modified = qfalse;
2737
if ( com_speeds->integer ) {
2738
timeBeforeFirstEvents = Sys_Milliseconds ();
2741
// we may want to spin here if things are going too fast
2742
if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer ) {
2743
minMsec = 1000 / com_maxfps->integer;
2748
com_frameTime = Com_EventLoop();
2749
if ( lastTime > com_frameTime ) {
2750
lastTime = com_frameTime; // possible on first frame
2752
msec = com_frameTime - lastTime;
2753
} while ( msec < minMsec );
2756
if (com_altivec->modified)
2758
Com_DetectAltivec();
2759
com_altivec->modified = qfalse;
2762
lastTime = com_frameTime;
2764
// mess with msec if needed
2765
com_frameMsec = msec;
2766
msec = Com_ModifyMsec( msec );
2771
if ( com_speeds->integer ) {
2772
timeBeforeServer = Sys_Milliseconds ();
2777
// if "dedicated" has been modified, start up
2778
// or shut down the client system.
2779
// Do this after the server may have started,
2780
// but before the client tries to auto-connect
2781
if ( com_dedicated->modified ) {
2782
// get the latched value
2783
Cvar_Get( "dedicated", "0", 0 );
2784
com_dedicated->modified = qfalse;
2785
if ( !com_dedicated->integer ) {
2787
Sys_ShowConsole( com_viewlog->integer, qfalse );
2790
Sys_ShowConsole( 1, qtrue );
2797
if ( !com_dedicated->integer ) {
2799
// run event loop a second time to get server to client packets
2800
// without a frame of latency
2802
if ( com_speeds->integer ) {
2803
timeBeforeEvents = Sys_Milliseconds ();
2812
if ( com_speeds->integer ) {
2813
timeBeforeClient = Sys_Milliseconds ();
2818
if ( com_speeds->integer ) {
2819
timeAfter = Sys_Milliseconds ();
2824
// report timing information
2826
if ( com_speeds->integer ) {
2827
int all, sv, ev, cl;
2829
all = timeAfter - timeBeforeServer;
2830
sv = timeBeforeEvents - timeBeforeServer;
2831
ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents;
2832
cl = timeAfter - timeBeforeClient;
2834
cl -= time_frontend + time_backend;
2836
Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n",
2837
com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend );
2841
// trace optimization tracking
2843
if ( com_showtrace->integer ) {
2845
extern int c_traces, c_brush_traces, c_patch_traces;
2846
extern int c_pointcontents;
2848
Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces,
2849
c_brush_traces, c_patch_traces, c_pointcontents);
2853
c_pointcontents = 0;
2856
// old net chan encryption key
2857
key = lastTime * 0x87243987;
2867
void Com_Shutdown (void) {
2869
FS_FCloseFile (logfile);
2873
if ( com_journalFile ) {
2874
FS_FCloseFile( com_journalFile );
2875
com_journalFile = 0;
2880
//------------------------------------------------------------------------
2884
=====================
2887
the msvc acos doesn't always return a value between -PI and PI:
2891
acos(*(float*) &i) == -1.#IND0
2893
This should go in q_math but it is too late to add new traps
2895
=====================
2897
float Q_acos(float c) {
2905
if (angle < -M_PI) {
2912
===========================================
2913
command line completion
2914
===========================================
2922
void Field_Clear( field_t *edit ) {
2923
memset(edit->buffer, 0, MAX_EDIT_LINE);
2928
static const char *completionString;
2929
static char shortestMatch[MAX_TOKEN_CHARS];
2930
static int matchCount;
2931
// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance)
2932
static field_t *completionField;
2940
static void FindMatches( const char *s ) {
2943
if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) {
2947
if ( matchCount == 1 ) {
2948
Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) );
2952
// cut shortestMatch to the amount common with s
2953
for ( i = 0 ; shortestMatch[i] ; i++ ) {
2954
if ( i >= strlen( s ) ) {
2955
shortestMatch[i] = 0;
2959
if ( tolower(shortestMatch[i]) != tolower(s[i]) ) {
2960
shortestMatch[i] = 0;
2971
static void PrintMatches( const char *s ) {
2972
if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
2973
Com_Printf( " %s\n", s );
2983
static void PrintCvarMatches( const char *s ) {
2984
char value[ TRUNCATE_LENGTH ];
2986
if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
2987
Com_TruncateLongString( value, Cvar_VariableString( s ) );
2988
Com_Printf( " %s = \"%s\"\n", s, value );
2994
Field_FindFirstSeparator
2997
static char *Field_FindFirstSeparator( char *s )
3001
for( i = 0; i < strlen( s ); i++ )
3012
Field_CompleteFilename
3015
static void Field_CompleteFilename( const char *dir,
3016
const char *ext, qboolean stripExt )
3019
shortestMatch[ 0 ] = 0;
3021
FS_FilenameCompletion( dir, ext, stripExt, FindMatches );
3023
if( matchCount == 0 )
3026
Q_strcat( completionField->buffer, sizeof( completionField->buffer ),
3027
shortestMatch + strlen( completionString ) );
3028
completionField->cursor = strlen( completionField->buffer );
3030
if( matchCount == 1 )
3032
Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
3033
completionField->cursor++;
3037
Com_Printf( "]%s\n", completionField->buffer );
3039
FS_FilenameCompletion( dir, ext, stripExt, PrintMatches );
3044
Field_CompleteCommand
3047
static void Field_CompleteCommand( char *cmd,
3048
qboolean doCommands, qboolean doCvars )
3050
int completionArgument = 0;
3053
// Skip leading whitespace and quotes
3054
cmd = Com_SkipCharset( cmd, " \"" );
3056
Cmd_TokenizeStringIgnoreQuotes( cmd );
3057
completionArgument = Cmd_Argc( );
3059
// If there is trailing whitespace on the cmd
3060
if( *( cmd + strlen( cmd ) - 1 ) == ' ' )
3062
completionString = "";
3063
completionArgument++;
3066
completionString = Cmd_Argv( completionArgument - 1 );
3068
if( completionArgument > 1 )
3070
const char *baseCmd = Cmd_Argv( 0 );
3073
// If the very first token does not have a leading \ or /,
3074
// refuse to autocomplete
3075
if( cmd == completionField->buffer )
3077
if( baseCmd[ 0 ] != '\\' && baseCmd[ 0 ] != '/' )
3084
if( ( p = Field_FindFirstSeparator( cmd ) ) )
3087
Field_CompleteCommand( p + 1, qtrue, qtrue );
3091
// FIXME: all this junk should really be associated with the respective
3092
// commands, instead of being hard coded here
3093
if( ( !Q_stricmp( baseCmd, "map" ) ||
3094
!Q_stricmp( baseCmd, "devmap" ) ||
3095
!Q_stricmp( baseCmd, "spmap" ) ||
3096
!Q_stricmp( baseCmd, "spdevmap" ) ) &&
3097
completionArgument == 2 )
3099
Field_CompleteFilename( "maps", "bsp", qtrue );
3101
else if( ( !Q_stricmp( baseCmd, "exec" ) ||
3102
!Q_stricmp( baseCmd, "writeconfig" ) ) &&
3103
completionArgument == 2 )
3105
Field_CompleteFilename( "", "cfg", qfalse );
3107
else if( !Q_stricmp( baseCmd, "condump" ) &&
3108
completionArgument == 2 )
3110
Field_CompleteFilename( "", "txt", qfalse );
3112
else if( !Q_stricmp( baseCmd, "demo" ) && completionArgument == 2 )
3116
Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION );
3117
Field_CompleteFilename( "demos", demoExt, qtrue );
3119
else if( ( !Q_stricmp( baseCmd, "toggle" ) ||
3120
!Q_stricmp( baseCmd, "vstr" ) ||
3121
!Q_stricmp( baseCmd, "set" ) ||
3122
!Q_stricmp( baseCmd, "seta" ) ||
3123
!Q_stricmp( baseCmd, "setu" ) ||
3124
!Q_stricmp( baseCmd, "sets" ) ) &&
3125
completionArgument == 2 )
3128
p = Com_SkipTokens( cmd, 1, " " );
3131
Field_CompleteCommand( p, qfalse, qtrue );
3133
else if( !Q_stricmp( baseCmd, "rcon" ) && completionArgument == 2 )
3136
p = Com_SkipTokens( cmd, 1, " " );
3139
Field_CompleteCommand( p, qtrue, qtrue );
3141
else if( !Q_stricmp( baseCmd, "bind" ) && completionArgument >= 3 )
3143
// Skip "bind <key> "
3144
p = Com_SkipTokens( cmd, 2, " " );
3147
Field_CompleteCommand( p, qtrue, qtrue );
3153
if( completionString[0] == '\\' || completionString[0] == '/' )
3157
shortestMatch[ 0 ] = 0;
3159
if( strlen( completionString ) == 0 )
3163
Cmd_CommandCompletion( FindMatches );
3166
Cvar_CommandCompletion( FindMatches );
3168
if( matchCount == 0 )
3169
return; // no matches
3171
if( cmd == completionField->buffer )
3174
Com_sprintf( completionField->buffer,
3175
sizeof( completionField->buffer ), "\\%s", shortestMatch );
3177
Com_sprintf( completionField->buffer,
3178
sizeof( completionField->buffer ), "%s", shortestMatch );
3183
Q_strcat( completionField->buffer, sizeof( completionField->buffer ),
3184
shortestMatch + strlen( completionString ) );
3187
completionField->cursor = strlen( completionField->buffer );
3189
if( matchCount == 1 )
3191
Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
3192
completionField->cursor++;
3196
Com_Printf( "]%s\n", completionField->buffer );
3198
// run through again, printing matches
3200
Cmd_CommandCompletion( PrintMatches );
3203
Cvar_CommandCompletion( PrintCvarMatches );
3211
Perform Tab expansion
3214
void Field_AutoComplete( field_t *field )
3216
completionField = field;
3218
Field_CompleteCommand( completionField->buffer, qtrue, qtrue );