1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
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.
11
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.
16
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 ===========================================================================
21 */
22 // common.c -- misc functions used in client and server
23
24 #include "q_shared.h"
25 #include "qcommon.h"
26 #include <setjmp.h>
27 #ifndef _WIN32
28 #include <sys/types.h>
29 #include <netinet/in.h>
30 #include <sys/stat.h> // umask
31 #else
32 #include <winsock.h>
33 #endif
34
35 int demo_protocols[] =
36 { 66, 67, 68, 0 };
37
38 #define MAX_NUM_ARGVS 50
39
40 #define MIN_DEDICATED_COMHUNKMEGS 1
41 #define MIN_COMHUNKMEGS 96
42 #define DEF_COMHUNKMEGS 128
43 #define DEF_COMZONEMEGS 24
44 #define XSTRING(x) STRING(x)
45 #define STRING(x) #x
46 #define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS)
47 #define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS)
48
49 int com_argc;
50 char *com_argv[MAX_NUM_ARGVS+1];
51
52 jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame
53
54
55 FILE *debuglogfile;
56 static fileHandle_t logfile;
57 fileHandle_t com_journalFile; // events are written here
58 fileHandle_t com_journalDataFile; // config files are written here
59
60 cvar_t *com_speeds;
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
66 cvar_t *com_journal;
67 cvar_t *com_maxfps;
68 cvar_t *com_altivec;
69 cvar_t *com_timedemo;
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;
74 cvar_t *com_version;
75 cvar_t *com_blood;
76 cvar_t *com_buildScript; // for automated data building scripts
77 cvar_t *com_introPlayed;
78 cvar_t *cl_paused;
79 cvar_t *sv_paused;
80 cvar_t *cl_packetdelay;
81 cvar_t *sv_packetdelay;
82 cvar_t *com_cameraMode;
83 cvar_t *com_ansiColor;
84 cvar_t *com_unfocused;
85 cvar_t *com_minimized;
86 cvar_t *com_standalone;
87 cvar_t *win_multiUser;
88
89 // com_speeds times
90 int time_game;
91 int time_frontend; // renderer frontend time
92 int time_backend; // renderer backend time
93
94 int com_frameTime;
95 int com_frameMsec;
96 int com_frameNumber;
97
98 qboolean com_errorEntered;
99 qboolean com_fullyInitialized;
100
101 char com_errorMessage[MAXPRINTMSG];
102
103 void Com_WriteConfig_f( void );
104 void CIN_CloseAllVideos( void );
105
106 //============================================================================
107
108 static char *rd_buffer;
109 static int rd_buffersize;
110 static void (*rd_flush)( char *buffer );
111
Com_BeginRedirect(char * buffer,int buffersize,void (* flush)(char *))112 void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) )
113 {
114 if (!buffer || !buffersize || !flush)
115 return;
116 rd_buffer = buffer;
117 rd_buffersize = buffersize;
118 rd_flush = flush;
119
120 *rd_buffer = 0;
121 }
122
Com_EndRedirect(void)123 void Com_EndRedirect (void)
124 {
125 if ( rd_flush ) {
126 rd_flush(rd_buffer);
127 }
128
129 rd_buffer = NULL;
130 rd_buffersize = 0;
131 rd_flush = NULL;
132 }
133
134 /*
135 =============
136 Com_Printf
137
138 Both client and server can use this, and it will output
139 to the apropriate place.
140
141 A raw string should NEVER be passed as fmt, because of "%f" type crashers.
142 =============
143 */
Com_Printf(const char * fmt,...)144 void QDECL Com_Printf( const char *fmt, ... ) {
145 va_list argptr;
146 char msg[MAXPRINTMSG];
147 static qboolean opening_qconsole = qfalse;
148
149
150 va_start (argptr,fmt);
151 Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
152 va_end (argptr);
153
154 if ( rd_buffer ) {
155 if ((strlen (msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) {
156 rd_flush(rd_buffer);
157 *rd_buffer = 0;
158 }
159 Q_strcat(rd_buffer, rd_buffersize, msg);
160 // TTimo nooo .. that would defeat the purpose
161 //rd_flush(rd_buffer);
162 //*rd_buffer = 0;
163 return;
164 }
165
166 #ifndef DEDICATED
167 CL_ConsolePrint( msg );
168 #endif
169
170 // echo to dedicated console and early console
171 Sys_Print( msg );
172
173 // logfile
174 if ( com_logfile && com_logfile->integer ) {
175 // TTimo: only open the qconsole.log if the filesystem is in an initialized state
176 // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on)
177 if ( !logfile && FS_Initialized() && !opening_qconsole) {
178 struct tm *newtime;
179 time_t aclock;
180
181 opening_qconsole = qtrue;
182
183 time( &aclock );
184 newtime = localtime( &aclock );
185
186 logfile = FS_FOpenFileWrite( "qconsole.log" );
187
188 if(logfile)
189 {
190 Com_Printf( "logfile opened on %s\n", asctime( newtime ) );
191
192 if ( com_logfile->integer > 1 )
193 {
194 // force it to not buffer so we get valid
195 // data even if we are crashing
196 FS_ForceFlush(logfile);
197 }
198 }
199 else
200 {
201 Com_Printf("Opening qconsole.log failed!\n");
202 Cvar_SetValue("logfile", 0);
203 }
204
205 opening_qconsole = qfalse;
206 }
207 if ( logfile && FS_Initialized()) {
208 FS_Write(msg, strlen(msg), logfile);
209 }
210 }
211 }
212
213
214 /*
215 ================
216 Com_DPrintf
217
218 A Com_Printf that only shows up if the "developer" cvar is set
219 ================
220 */
Com_DPrintf(const char * fmt,...)221 void QDECL Com_DPrintf( const char *fmt, ...) {
222 va_list argptr;
223 char msg[MAXPRINTMSG];
224
225 if ( !com_developer || !com_developer->integer ) {
226 return; // don't confuse non-developers with techie stuff...
227 }
228
229 va_start (argptr,fmt);
230 Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
231 va_end (argptr);
232
233 Com_Printf ("%s", msg);
234 }
235
236 /*
237 =============
238 Com_Error
239
240 Both client and server can use this, and it will
241 do the apropriate things.
242 =============
243 */
Com_Error(int code,const char * fmt,...)244 void QDECL Com_Error( int code, const char *fmt, ... ) {
245 va_list argptr;
246 static int lastErrorTime;
247 static int errorCount;
248 int currentTime;
249
250 Cvar_Set( "com_errorCode", va( "%i", code ) );
251
252 // when we are running automated scripts, make sure we
253 // know if anything failed
254 if ( com_buildScript && com_buildScript->integer ) {
255 code = ERR_FATAL;
256 }
257
258 // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL
259 currentTime = Sys_Milliseconds();
260 if ( currentTime - lastErrorTime < 100 ) {
261 if ( ++errorCount > 3 ) {
262 code = ERR_FATAL;
263 }
264 } else {
265 errorCount = 0;
266 }
267 lastErrorTime = currentTime;
268
269 if ( com_errorEntered ) {
270 Sys_Error( "recursive error after: %s", com_errorMessage );
271 }
272 com_errorEntered = qtrue;
273
274 va_start (argptr,fmt);
275 Q_vsnprintf (com_errorMessage, sizeof(com_errorMessage),fmt,argptr);
276 va_end (argptr);
277
278 if (code != ERR_DISCONNECT && code != ERR_NEED_CD)
279 Cvar_Set("com_errorMessage", com_errorMessage);
280
281 if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) {
282 CL_Disconnect( qtrue );
283 VM_Forced_Unload_Start();
284 CL_FlushMemory( );
285 VM_Forced_Unload_Done();
286 // make sure we can get at our local stuff
287 FS_PureServerSetLoadedPaks("", "");
288 com_errorEntered = qfalse;
289 longjmp (abortframe, -1);
290 } else if (code == ERR_DROP) {
291 Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage);
292 SV_Shutdown (va("Server crashed: %s", com_errorMessage));
293 CL_Disconnect( qtrue );
294 VM_Forced_Unload_Start();
295 CL_FlushMemory( );
296 VM_Forced_Unload_Done();
297 FS_PureServerSetLoadedPaks("", "");
298 com_errorEntered = qfalse;
299 longjmp (abortframe, -1);
300 } else if ( code == ERR_NEED_CD ) {
301 SV_Shutdown( "Server didn't have CD" );
302 if ( com_cl_running && com_cl_running->integer ) {
303 CL_Disconnect( qtrue );
304 VM_Forced_Unload_Start();
305 CL_FlushMemory( );
306 VM_Forced_Unload_Done();
307 com_errorEntered = qfalse;
308 CL_CDDialog();
309 } else {
310 Com_Printf("Server didn't have CD\n" );
311 }
312 FS_PureServerSetLoadedPaks("", "");
313 longjmp (abortframe, -1);
314 } else {
315 CL_Shutdown ();
316 SV_Shutdown (va("Server fatal crashed: %s", com_errorMessage));
317 }
318
319 Com_Shutdown ();
320
321 Sys_Error ("%s", com_errorMessage);
322 }
323
324
325 /*
326 =============
327 Com_Quit_f
328
329 Both client and server can use this, and it will
330 do the apropriate things.
331 =============
332 */
Com_Quit_f(void)333 void Com_Quit_f( void ) {
334 // don't try to shutdown if we are in a recursive error
335 if ( !com_errorEntered ) {
336 SV_Shutdown ("Server quit");
337 CL_Shutdown ();
338 Com_Shutdown ();
339 FS_Shutdown(qtrue);
340 }
341 Sys_Quit ();
342 }
343
344
345
346 /*
347 ============================================================================
348
349 COMMAND LINE FUNCTIONS
350
351 + characters seperate the commandLine string into multiple console
352 command lines.
353
354 All of these are valid:
355
356 quake3 +set test blah +map test
357 quake3 set test blah+map test
358 quake3 set test blah + map test
359
360 ============================================================================
361 */
362
363 #define MAX_CONSOLE_LINES 32
364 int com_numConsoleLines;
365 char *com_consoleLines[MAX_CONSOLE_LINES];
366
367 /*
368 ==================
369 Com_ParseCommandLine
370
371 Break it up into multiple console lines
372 ==================
373 */
Com_ParseCommandLine(char * commandLine)374 void Com_ParseCommandLine( char *commandLine ) {
375 int inq = 0;
376 com_consoleLines[0] = commandLine;
377 com_numConsoleLines = 1;
378
379 while ( *commandLine ) {
380 if (*commandLine == '"') {
381 inq = !inq;
382 }
383 // look for a + seperating character
384 // if commandLine came from a file, we might have real line seperators
385 if ( (*commandLine == '+' && !inq) || *commandLine == '\n' || *commandLine == '\r' ) {
386 if ( com_numConsoleLines == MAX_CONSOLE_LINES ) {
387 return;
388 }
389 com_consoleLines[com_numConsoleLines] = commandLine + 1;
390 com_numConsoleLines++;
391 *commandLine = 0;
392 }
393 commandLine++;
394 }
395 }
396
397
398 /*
399 ===================
400 Com_SafeMode
401
402 Check for "safe" on the command line, which will
403 skip loading of q3config.cfg
404 ===================
405 */
Com_SafeMode(void)406 qboolean Com_SafeMode( void ) {
407 int i;
408
409 for ( i = 0 ; i < com_numConsoleLines ; i++ ) {
410 Cmd_TokenizeString( com_consoleLines[i] );
411 if ( !Q_stricmp( Cmd_Argv(0), "safe" )
412 || !Q_stricmp( Cmd_Argv(0), "cvar_restart" ) ) {
413 com_consoleLines[i][0] = 0;
414 return qtrue;
415 }
416 }
417 return qfalse;
418 }
419
420
421 /*
422 ===============
423 Com_StartupVariable
424
425 Searches for command line parameters that are set commands.
426 If match is not NULL, only that cvar will be looked for.
427 That is necessary because cddir and basedir need to be set
428 before the filesystem is started, but all other sets should
429 be after execing the config and default.
430 ===============
431 */
Com_StartupVariable(const char * match)432 void Com_StartupVariable( const char *match ) {
433 int i;
434 char *s;
435 cvar_t *cv;
436
437 for (i=0 ; i < com_numConsoleLines ; i++) {
438 Cmd_TokenizeString( com_consoleLines[i] );
439 if ( strcmp( Cmd_Argv(0), "set" ) ) {
440 continue;
441 }
442
443 s = Cmd_Argv(1);
444 if ( !match || !strcmp( s, match ) ) {
445 Cvar_Set( s, Cmd_Argv(2) );
446 cv = Cvar_Get( s, "", 0 );
447 cv->flags |= CVAR_USER_CREATED;
448 // com_consoleLines[i] = 0;
449 }
450 }
451 }
452
453
454 /*
455 =================
456 Com_AddStartupCommands
457
458 Adds command line parameters as script statements
459 Commands are seperated by + signs
460
461 Returns qtrue if any late commands were added, which
462 will keep the demoloop from immediately starting
463 =================
464 */
Com_AddStartupCommands(void)465 qboolean Com_AddStartupCommands( void ) {
466 int i;
467 qboolean added;
468
469 added = qfalse;
470 // quote every token, so args with semicolons can work
471 for (i=0 ; i < com_numConsoleLines ; i++) {
472 if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) {
473 continue;
474 }
475
476 // set commands won't override menu startup
477 if ( Q_stricmpn( com_consoleLines[i], "set", 3 ) ) {
478 added = qtrue;
479 }
480 Cbuf_AddText( com_consoleLines[i] );
481 Cbuf_AddText( "\n" );
482 }
483
484 return added;
485 }
486
487
488 //============================================================================
489
Info_Print(const char * s)490 void Info_Print( const char *s ) {
491 char key[512];
492 char value[512];
493 char *o;
494 int l;
495
496 if (*s == '\\')
497 s++;
498 while (*s)
499 {
500 o = key;
501 while (*s && *s != '\\')
502 *o++ = *s++;
503
504 l = o - key;
505 if (l < 20)
506 {
507 Com_Memset (o, ' ', 20-l);
508 key[20] = 0;
509 }
510 else
511 *o = 0;
512 Com_Printf ("%s", key);
513
514 if (!*s)
515 {
516 Com_Printf ("MISSING VALUE\n");
517 return;
518 }
519
520 o = value;
521 s++;
522 while (*s && *s != '\\')
523 *o++ = *s++;
524 *o = 0;
525
526 if (*s)
527 s++;
528 Com_Printf ("%s\n", value);
529 }
530 }
531
532 /*
533 ============
534 Com_StringContains
535 ============
536 */
Com_StringContains(char * str1,char * str2,int casesensitive)537 char *Com_StringContains(char *str1, char *str2, int casesensitive) {
538 int len, i, j;
539
540 len = strlen(str1) - strlen(str2);
541 for (i = 0; i <= len; i++, str1++) {
542 for (j = 0; str2[j]; j++) {
543 if (casesensitive) {
544 if (str1[j] != str2[j]) {
545 break;
546 }
547 }
548 else {
549 if (toupper(str1[j]) != toupper(str2[j])) {
550 break;
551 }
552 }
553 }
554 if (!str2[j]) {
555 return str1;
556 }
557 }
558 return NULL;
559 }
560
561 /*
562 ============
563 Com_Filter
564 ============
565 */
Com_Filter(char * filter,char * name,int casesensitive)566 int Com_Filter(char *filter, char *name, int casesensitive)
567 {
568 char buf[MAX_TOKEN_CHARS];
569 char *ptr;
570 int i, found;
571
572 while(*filter) {
573 if (*filter == '*') {
574 filter++;
575 for (i = 0; *filter; i++) {
576 if (*filter == '*' || *filter == '?') break;
577 buf[i] = *filter;
578 filter++;
579 }
580 buf[i] = '\0';
581 if (strlen(buf)) {
582 ptr = Com_StringContains(name, buf, casesensitive);
583 if (!ptr) return qfalse;
584 name = ptr + strlen(buf);
585 }
586 }
587 else if (*filter == '?') {
588 filter++;
589 name++;
590 }
591 else if (*filter == '[' && *(filter+1) == '[') {
592 filter++;
593 }
594 else if (*filter == '[') {
595 filter++;
596 found = qfalse;
597 while(*filter && !found) {
598 if (*filter == ']' && *(filter+1) != ']') break;
599 if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) {
600 if (casesensitive) {
601 if (*name >= *filter && *name <= *(filter+2)) found = qtrue;
602 }
603 else {
604 if (toupper(*name) >= toupper(*filter) &&
605 toupper(*name) <= toupper(*(filter+2))) found = qtrue;
606 }
607 filter += 3;
608 }
609 else {
610 if (casesensitive) {
611 if (*filter == *name) found = qtrue;
612 }
613 else {
614 if (toupper(*filter) == toupper(*name)) found = qtrue;
615 }
616 filter++;
617 }
618 }
619 if (!found) return qfalse;
620 while(*filter) {
621 if (*filter == ']' && *(filter+1) != ']') break;
622 filter++;
623 }
624 filter++;
625 name++;
626 }
627 else {
628 if (casesensitive) {
629 if (*filter != *name) return qfalse;
630 }
631 else {
632 if (toupper(*filter) != toupper(*name)) return qfalse;
633 }
634 filter++;
635 name++;
636 }
637 }
638 return qtrue;
639 }
640
641 /*
642 ============
643 Com_FilterPath
644 ============
645 */
Com_FilterPath(char * filter,char * name,int casesensitive)646 int Com_FilterPath(char *filter, char *name, int casesensitive)
647 {
648 int i;
649 char new_filter[MAX_QPATH];
650 char new_name[MAX_QPATH];
651
652 for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) {
653 if ( filter[i] == '\\' || filter[i] == ':' ) {
654 new_filter[i] = '/';
655 }
656 else {
657 new_filter[i] = filter[i];
658 }
659 }
660 new_filter[i] = '\0';
661 for (i = 0; i < MAX_QPATH-1 && name[i]; i++) {
662 if ( name[i] == '\\' || name[i] == ':' ) {
663 new_name[i] = '/';
664 }
665 else {
666 new_name[i] = name[i];
667 }
668 }
669 new_name[i] = '\0';
670 return Com_Filter(new_filter, new_name, casesensitive);
671 }
672
673 /*
674 ============
675 Com_HashKey
676 ============
677 */
Com_HashKey(char * string,int maxlen)678 int Com_HashKey(char *string, int maxlen) {
679 int register hash, i;
680
681 hash = 0;
682 for (i = 0; i < maxlen && string[i] != '\0'; i++) {
683 hash += string[i] * (119 + i);
684 }
685 hash = (hash ^ (hash >> 10) ^ (hash >> 20));
686 return hash;
687 }
688
689 /*
690 ================
691 Com_RealTime
692 ================
693 */
Com_RealTime(qtime_t * qtime)694 int Com_RealTime(qtime_t *qtime) {
695 time_t t;
696 struct tm *tms;
697
698 t = time(NULL);
699 if (!qtime)
700 return t;
701 tms = localtime(&t);
702 if (tms) {
703 qtime->tm_sec = tms->tm_sec;
704 qtime->tm_min = tms->tm_min;
705 qtime->tm_hour = tms->tm_hour;
706 qtime->tm_mday = tms->tm_mday;
707 qtime->tm_mon = tms->tm_mon;
708 qtime->tm_year = tms->tm_year;
709 qtime->tm_wday = tms->tm_wday;
710 qtime->tm_yday = tms->tm_yday;
711 qtime->tm_isdst = tms->tm_isdst;
712 }
713 return t;
714 }
715
716
717 /*
718 ==============================================================================
719
720 ZONE MEMORY ALLOCATION
721
722 There is never any space between memblocks, and there will never be two
723 contiguous free memblocks.
724
725 The rover can be left pointing at a non-empty block
726
727 The zone calls are pretty much only used for small strings and structures,
728 all big things are allocated on the hunk.
729 ==============================================================================
730 */
731
732 #define ZONEID 0x1d4a11
733 #define MINFRAGMENT 64
734
735 typedef struct zonedebug_s {
736 char *label;
737 char *file;
738 int line;
739 int allocSize;
740 } zonedebug_t;
741
742 typedef struct memblock_s {
743 int size; // including the header and possibly tiny fragments
744 int tag; // a tag of 0 is a free block
745 struct memblock_s *next, *prev;
746 int id; // should be ZONEID
747 #ifdef ZONE_DEBUG
748 zonedebug_t d;
749 #endif
750 } memblock_t;
751
752 typedef struct {
753 int size; // total bytes malloced, including header
754 int used; // total bytes used
755 memblock_t blocklist; // start / end cap for linked list
756 memblock_t *rover;
757 } memzone_t;
758
759 // main zone for all "dynamic" memory allocation
760 memzone_t *mainzone;
761 // we also have a small zone for small allocations that would only
762 // fragment the main zone (think of cvar and cmd strings)
763 memzone_t *smallzone;
764
765 void Z_CheckHeap( void );
766
767 /*
768 ========================
769 Z_ClearZone
770 ========================
771 */
Z_ClearZone(memzone_t * zone,int size)772 void Z_ClearZone( memzone_t *zone, int size ) {
773 memblock_t *block;
774
775 // set the entire zone to one free block
776
777 zone->blocklist.next = zone->blocklist.prev = block =
778 (memblock_t *)( (byte *)zone + sizeof(memzone_t) );
779 zone->blocklist.tag = 1; // in use block
780 zone->blocklist.id = 0;
781 zone->blocklist.size = 0;
782 zone->rover = block;
783 zone->size = size;
784 zone->used = 0;
785
786 block->prev = block->next = &zone->blocklist;
787 block->tag = 0; // free block
788 block->id = ZONEID;
789 block->size = size - sizeof(memzone_t);
790 }
791
792 /*
793 ========================
794 Z_AvailableZoneMemory
795 ========================
796 */
Z_AvailableZoneMemory(memzone_t * zone)797 int Z_AvailableZoneMemory( memzone_t *zone ) {
798 return zone->size - zone->used;
799 }
800
801 /*
802 ========================
803 Z_AvailableMemory
804 ========================
805 */
Z_AvailableMemory(void)806 int Z_AvailableMemory( void ) {
807 return Z_AvailableZoneMemory( mainzone );
808 }
809
810 /*
811 ========================
812 Z_Free
813 ========================
814 */
Z_Free(void * ptr)815 void Z_Free( void *ptr ) {
816 memblock_t *block, *other;
817 memzone_t *zone;
818
819 if (!ptr) {
820 Com_Error( ERR_DROP, "Z_Free: NULL pointer" );
821 }
822
823 block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
824 if (block->id != ZONEID) {
825 Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" );
826 }
827 if (block->tag == 0) {
828 Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" );
829 }
830 // if static memory
831 if (block->tag == TAG_STATIC) {
832 return;
833 }
834
835 // check the memory trash tester
836 if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) {
837 Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" );
838 }
839
840 if (block->tag == TAG_SMALL) {
841 zone = smallzone;
842 }
843 else {
844 zone = mainzone;
845 }
846
847 zone->used -= block->size;
848 // set the block to something that should cause problems
849 // if it is referenced...
850 Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) );
851
852 block->tag = 0; // mark as free
853
854 other = block->prev;
855 if (!other->tag) {
856 // merge with previous free block
857 other->size += block->size;
858 other->next = block->next;
859 other->next->prev = other;
860 if (block == zone->rover) {
861 zone->rover = other;
862 }
863 block = other;
864 }
865
866 zone->rover = block;
867
868 other = block->next;
869 if ( !other->tag ) {
870 // merge the next free block onto the end
871 block->size += other->size;
872 block->next = other->next;
873 block->next->prev = block;
874 if (other == zone->rover) {
875 zone->rover = block;
876 }
877 }
878 }
879
880
881 /*
882 ================
883 Z_FreeTags
884 ================
885 */
Z_FreeTags(int tag)886 void Z_FreeTags( int tag ) {
887 int count;
888 memzone_t *zone;
889
890 if ( tag == TAG_SMALL ) {
891 zone = smallzone;
892 }
893 else {
894 zone = mainzone;
895 }
896 count = 0;
897 // use the rover as our pointer, because
898 // Z_Free automatically adjusts it
899 zone->rover = zone->blocklist.next;
900 do {
901 if ( zone->rover->tag == tag ) {
902 count++;
903 Z_Free( (void *)(zone->rover + 1) );
904 continue;
905 }
906 zone->rover = zone->rover->next;
907 } while ( zone->rover != &zone->blocklist );
908 }
909
910
911 /*
912 ================
913 Z_TagMalloc
914 ================
915 */
916 #ifdef ZONE_DEBUG
Z_TagMallocDebug(int size,int tag,char * label,char * file,int line)917 void *Z_TagMallocDebug( int size, int tag, char *label, char *file, int line ) {
918 #else
919 void *Z_TagMalloc( int size, int tag ) {
920 #endif
921 int extra, allocSize;
922 memblock_t *start, *rover, *new, *base;
923 memzone_t *zone;
924
925 if (!tag) {
926 Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" );
927 }
928
929 if ( tag == TAG_SMALL ) {
930 zone = smallzone;
931 }
932 else {
933 zone = mainzone;
934 }
935
936 allocSize = size;
937 //
938 // scan through the block list looking for the first free block
939 // of sufficient size
940 //
941 size += sizeof(memblock_t); // account for size of block header
942 size += 4; // space for memory trash tester
943 size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary
944
945 base = rover = zone->rover;
946 start = base->prev;
947
948 do {
949 if (rover == start) {
950 #ifdef ZONE_DEBUG
951 Z_LogHeap();
952 #endif
953 // scaned all the way around the list
954 Com_Error( ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone",
955 size, zone == smallzone ? "small" : "main");
956 return NULL;
957 }
958 if (rover->tag) {
959 base = rover = rover->next;
960 } else {
961 rover = rover->next;
962 }
963 } while (base->tag || base->size < size);
964
965 //
966 // found a block big enough
967 //
968 extra = base->size - size;
969 if (extra > MINFRAGMENT) {
970 // there will be a free fragment after the allocated block
971 new = (memblock_t *) ((byte *)base + size );
972 new->size = extra;
973 new->tag = 0; // free block
974 new->prev = base;
975 new->id = ZONEID;
976 new->next = base->next;
977 new->next->prev = new;
978 base->next = new;
979 base->size = size;
980 }
981
982 base->tag = tag; // no longer a free block
983
984 zone->rover = base->next; // next allocation will start looking here
985 zone->used += base->size; //
986
987 base->id = ZONEID;
988
989 #ifdef ZONE_DEBUG
990 base->d.label = label;
991 base->d.file = file;
992 base->d.line = line;
993 base->d.allocSize = allocSize;
994 #endif
995
996 // marker for memory trash testing
997 *(int *)((byte *)base + base->size - 4) = ZONEID;
998
999 return (void *) ((byte *)base + sizeof(memblock_t));
1000 }
1001
1002 /*
1003 ========================
1004 Z_Malloc
1005 ========================
1006 */
1007 #ifdef ZONE_DEBUG
1008 void *Z_MallocDebug( int size, char *label, char *file, int line ) {
1009 #else
1010 void *Z_Malloc( int size ) {
1011 #endif
1012 void *buf;
1013
1014 //Z_CheckHeap (); // DEBUG
1015
1016 #ifdef ZONE_DEBUG
1017 buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line );
1018 #else
1019 buf = Z_TagMalloc( size, TAG_GENERAL );
1020 #endif
1021 Com_Memset( buf, 0, size );
1022
1023 return buf;
1024 }
1025
1026 #ifdef ZONE_DEBUG
1027 void *S_MallocDebug( int size, char *label, char *file, int line ) {
1028 return Z_TagMallocDebug( size, TAG_SMALL, label, file, line );
1029 }
1030 #else
1031 void *S_Malloc( int size ) {
1032 return Z_TagMalloc( size, TAG_SMALL );
1033 }
1034 #endif
1035
1036 /*
1037 ========================
1038 Z_CheckHeap
1039 ========================
1040 */
1041 void Z_CheckHeap( void ) {
1042 memblock_t *block;
1043
1044 for (block = mainzone->blocklist.next ; ; block = block->next) {
1045 if (block->next == &mainzone->blocklist) {
1046 break; // all blocks have been hit
1047 }
1048 if ( (byte *)block + block->size != (byte *)block->next)
1049 Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block\n" );
1050 if ( block->next->prev != block) {
1051 Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link\n" );
1052 }
1053 if ( !block->tag && !block->next->tag ) {
1054 Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks\n" );
1055 }
1056 }
1057 }
1058
1059 /*
1060 ========================
1061 Z_LogZoneHeap
1062 ========================
1063 */
1064 void Z_LogZoneHeap( memzone_t *zone, char *name ) {
1065 #ifdef ZONE_DEBUG
1066 char dump[32], *ptr;
1067 int i, j;
1068 #endif
1069 memblock_t *block;
1070 char buf[4096];
1071 int size, allocSize, numBlocks;
1072
1073 if (!logfile || !FS_Initialized())
1074 return;
1075 size = allocSize = numBlocks = 0;
1076 Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name);
1077 FS_Write(buf, strlen(buf), logfile);
1078 for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) {
1079 if (block->tag) {
1080 #ifdef ZONE_DEBUG
1081 ptr = ((char *) block) + sizeof(memblock_t);
1082 j = 0;
1083 for (i = 0; i < 20 && i < block->d.allocSize; i++) {
1084 if (ptr[i] >= 32 && ptr[i] < 127) {
1085 dump[j++] = ptr[i];
1086 }
1087 else {
1088 dump[j++] = '_';
1089 }
1090 }
1091 dump[j] = '\0';
1092 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);
1093 FS_Write(buf, strlen(buf), logfile);
1094 allocSize += block->d.allocSize;
1095 #endif
1096 size += block->size;
1097 numBlocks++;
1098 }
1099 }
1100 #ifdef ZONE_DEBUG
1101 // subtract debug memory
1102 size -= numBlocks * sizeof(zonedebug_t);
1103 #else
1104 allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment
1105 #endif
1106 Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks);
1107 FS_Write(buf, strlen(buf), logfile);
1108 Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name);
1109 FS_Write(buf, strlen(buf), logfile);
1110 }
1111
1112 /*
1113 ========================
1114 Z_LogHeap
1115 ========================
1116 */
1117 void Z_LogHeap( void ) {
1118 Z_LogZoneHeap( mainzone, "MAIN" );
1119 Z_LogZoneHeap( smallzone, "SMALL" );
1120 }
1121
1122 // static mem blocks to reduce a lot of small zone overhead
1123 typedef struct memstatic_s {
1124 memblock_t b;
1125 byte mem[2];
1126 } memstatic_t;
1127
1128 memstatic_t emptystring =
1129 { {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} };
1130 memstatic_t numberstring[] = {
1131 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} },
1132 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} },
1133 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} },
1134 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} },
1135 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} },
1136 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} },
1137 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} },
1138 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} },
1139 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} },
1140 { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} }
1141 };
1142
1143 /*
1144 ========================
1145 CopyString
1146
1147 NOTE: never write over the memory CopyString returns because
1148 memory from a memstatic_t might be returned
1149 ========================
1150 */
1151 char *CopyString( const char *in ) {
1152 char *out;
1153
1154 if (!in[0]) {
1155 return ((char *)&emptystring) + sizeof(memblock_t);
1156 }
1157 else if (!in[1]) {
1158 if (in[0] >= '0' && in[0] <= '9') {
1159 return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t);
1160 }
1161 }
1162 out = S_Malloc (strlen(in)+1);
1163 strcpy (out, in);
1164 return out;
1165 }
1166
1167 /*
1168 ==============================================================================
1169
1170 Goals:
1171 reproducable without history effects -- no out of memory errors on weird map to map changes
1172 allow restarting of the client without fragmentation
1173 minimize total pages in use at run time
1174 minimize total pages needed during load time
1175
1176 Single block of memory with stack allocators coming from both ends towards the middle.
1177
1178 One side is designated the temporary memory allocator.
1179
1180 Temporary memory can be allocated and freed in any order.
1181
1182 A highwater mark is kept of the most in use at any time.
1183
1184 When there is no temporary memory allocated, the permanent and temp sides
1185 can be switched, allowing the already touched temp memory to be used for
1186 permanent storage.
1187
1188 Temp memory must never be allocated on two ends at once, or fragmentation
1189 could occur.
1190
1191 If we have any in-use temp memory, additional temp allocations must come from
1192 that side.
1193
1194 If not, we can choose to make either side the new temp side and push future
1195 permanent allocations to the other side. Permanent allocations should be
1196 kept on the side that has the current greatest wasted highwater mark.
1197
1198 ==============================================================================
1199 */
1200
1201
1202 #define HUNK_MAGIC 0x89537892
1203 #define HUNK_FREE_MAGIC 0x89537893
1204
1205 typedef struct {
1206 int magic;
1207 int size;
1208 } hunkHeader_t;
1209
1210 typedef struct {
1211 int mark;
1212 int permanent;
1213 int temp;
1214 int tempHighwater;
1215 } hunkUsed_t;
1216
1217 typedef struct hunkblock_s {
1218 int size;
1219 byte printed;
1220 struct hunkblock_s *next;
1221 char *label;
1222 char *file;
1223 int line;
1224 } hunkblock_t;
1225
1226 static hunkblock_t *hunkblocks;
1227
1228 static hunkUsed_t hunk_low, hunk_high;
1229 static hunkUsed_t *hunk_permanent, *hunk_temp;
1230
1231 static byte *s_hunkData = NULL;
1232 static int s_hunkTotal;
1233
1234 static int s_zoneTotal;
1235 static int s_smallZoneTotal;
1236
1237
1238 /*
1239 =================
1240 Com_Meminfo_f
1241 =================
1242 */
1243 void Com_Meminfo_f( void ) {
1244 memblock_t *block;
1245 int zoneBytes, zoneBlocks;
1246 int smallZoneBytes, smallZoneBlocks;
1247 int botlibBytes, rendererBytes;
1248 int unused;
1249
1250 zoneBytes = 0;
1251 botlibBytes = 0;
1252 rendererBytes = 0;
1253 zoneBlocks = 0;
1254 for (block = mainzone->blocklist.next ; ; block = block->next) {
1255 if ( Cmd_Argc() != 1 ) {
1256 Com_Printf ("block:%p size:%7i tag:%3i\n",
1257 (void *)block, block->size, block->tag);
1258 }
1259 if ( block->tag ) {
1260 zoneBytes += block->size;
1261 zoneBlocks++;
1262 if ( block->tag == TAG_BOTLIB ) {
1263 botlibBytes += block->size;
1264 } else if ( block->tag == TAG_RENDERER ) {
1265 rendererBytes += block->size;
1266 }
1267 }
1268
1269 if (block->next == &mainzone->blocklist) {
1270 break; // all blocks have been hit
1271 }
1272 if ( (byte *)block + block->size != (byte *)block->next) {
1273 Com_Printf ("ERROR: block size does not touch the next block\n");
1274 }
1275 if ( block->next->prev != block) {
1276 Com_Printf ("ERROR: next block doesn't have proper back link\n");
1277 }
1278 if ( !block->tag && !block->next->tag ) {
1279 Com_Printf ("ERROR: two consecutive free blocks\n");
1280 }
1281 }
1282
1283 smallZoneBytes = 0;
1284 smallZoneBlocks = 0;
1285 for (block = smallzone->blocklist.next ; ; block = block->next) {
1286 if ( block->tag ) {
1287 smallZoneBytes += block->size;
1288 smallZoneBlocks++;
1289 }
1290
1291 if (block->next == &smallzone->blocklist) {
1292 break; // all blocks have been hit
1293 }
1294 }
1295
1296 Com_Printf( "%8i bytes total hunk\n", s_hunkTotal );
1297 Com_Printf( "%8i bytes total zone\n", s_zoneTotal );
1298 Com_Printf( "\n" );
1299 Com_Printf( "%8i low mark\n", hunk_low.mark );
1300 Com_Printf( "%8i low permanent\n", hunk_low.permanent );
1301 if ( hunk_low.temp != hunk_low.permanent ) {
1302 Com_Printf( "%8i low temp\n", hunk_low.temp );
1303 }
1304 Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater );
1305 Com_Printf( "\n" );
1306 Com_Printf( "%8i high mark\n", hunk_high.mark );
1307 Com_Printf( "%8i high permanent\n", hunk_high.permanent );
1308 if ( hunk_high.temp != hunk_high.permanent ) {
1309 Com_Printf( "%8i high temp\n", hunk_high.temp );
1310 }
1311 Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater );
1312 Com_Printf( "\n" );
1313 Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent );
1314 unused = 0;
1315 if ( hunk_low.tempHighwater > hunk_low.permanent ) {
1316 unused += hunk_low.tempHighwater - hunk_low.permanent;
1317 }
1318 if ( hunk_high.tempHighwater > hunk_high.permanent ) {
1319 unused += hunk_high.tempHighwater - hunk_high.permanent;
1320 }
1321 Com_Printf( "%8i unused highwater\n", unused );
1322 Com_Printf( "\n" );
1323 Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks );
1324 Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes );
1325 Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes );
1326 Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) );
1327 Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes );
1328 }
1329
1330 /*
1331 ===============
1332 Com_TouchMemory
1333
1334 Touch all known used data to make sure it is paged in
1335 ===============
1336 */
1337 void Com_TouchMemory( void ) {
1338 int start, end;
1339 int i, j;
1340 int sum;
1341 memblock_t *block;
1342
1343 Z_CheckHeap();
1344
1345 start = Sys_Milliseconds();
1346
1347 sum = 0;
1348
1349 j = hunk_low.permanent >> 2;
1350 for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
1351 sum += ((int *)s_hunkData)[i];
1352 }
1353
1354 i = ( s_hunkTotal - hunk_high.permanent ) >> 2;
1355 j = hunk_high.permanent >> 2;
1356 for ( ; i < j ; i+=64 ) { // only need to touch each page
1357 sum += ((int *)s_hunkData)[i];
1358 }
1359
1360 for (block = mainzone->blocklist.next ; ; block = block->next) {
1361 if ( block->tag ) {
1362 j = block->size >> 2;
1363 for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page
1364 sum += ((int *)block)[i];
1365 }
1366 }
1367 if ( block->next == &mainzone->blocklist ) {
1368 break; // all blocks have been hit
1369 }
1370 }
1371
1372 end = Sys_Milliseconds();
1373
1374 Com_Printf( "Com_TouchMemory: %i msec\n", end - start );
1375 }
1376
1377
1378
1379 /*
1380 =================
1381 Com_InitZoneMemory
1382 =================
1383 */
1384 void Com_InitSmallZoneMemory( void ) {
1385 s_smallZoneTotal = 512 * 1024;
1386 smallzone = calloc( s_smallZoneTotal, 1 );
1387 if ( !smallzone ) {
1388 Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) );
1389 }
1390 Z_ClearZone( smallzone, s_smallZoneTotal );
1391
1392 return;
1393 }
1394
1395 void Com_InitZoneMemory( void ) {
1396 cvar_t *cv;
1397
1398 //FIXME: 05/01/06 com_zoneMegs is useless right now as neither q3config.cfg nor
1399 // Com_StartupVariable have been executed by this point. The net result is that
1400 // s_zoneTotal will always be set to the default value.
1401
1402 // allocate the random block zone
1403 cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
1404
1405 if ( cv->integer < DEF_COMZONEMEGS ) {
1406 s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS;
1407 } else {
1408 s_zoneTotal = cv->integer * 1024 * 1024;
1409 }
1410
1411 mainzone = calloc( s_zoneTotal, 1 );
1412 if ( !mainzone ) {
1413 Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) );
1414 }
1415 Z_ClearZone( mainzone, s_zoneTotal );
1416
1417 }
1418
1419 /*
1420 =================
1421 Hunk_Log
1422 =================
1423 */
1424 void Hunk_Log( void) {
1425 hunkblock_t *block;
1426 char buf[4096];
1427 int size, numBlocks;
1428
1429 if (!logfile || !FS_Initialized())
1430 return;
1431 size = 0;
1432 numBlocks = 0;
1433 Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n");
1434 FS_Write(buf, strlen(buf), logfile);
1435 for (block = hunkblocks ; block; block = block->next) {
1436 #ifdef HUNK_DEBUG
1437 Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label);
1438 FS_Write(buf, strlen(buf), logfile);
1439 #endif
1440 size += block->size;
1441 numBlocks++;
1442 }
1443 Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
1444 FS_Write(buf, strlen(buf), logfile);
1445 Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
1446 FS_Write(buf, strlen(buf), logfile);
1447 }
1448
1449 /*
1450 =================
1451 Hunk_SmallLog
1452 =================
1453 */
1454 void Hunk_SmallLog( void) {
1455 hunkblock_t *block, *block2;
1456 char buf[4096];
1457 int size, locsize, numBlocks;
1458
1459 if (!logfile || !FS_Initialized())
1460 return;
1461 for (block = hunkblocks ; block; block = block->next) {
1462 block->printed = qfalse;
1463 }
1464 size = 0;
1465 numBlocks = 0;
1466 Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n");
1467 FS_Write(buf, strlen(buf), logfile);
1468 for (block = hunkblocks; block; block = block->next) {
1469 if (block->printed) {
1470 continue;
1471 }
1472 locsize = block->size;
1473 for (block2 = block->next; block2; block2 = block2->next) {
1474 if (block->line != block2->line) {
1475 continue;
1476 }
1477 if (Q_stricmp(block->file, block2->file)) {
1478 continue;
1479 }
1480 size += block2->size;
1481 locsize += block2->size;
1482 block2->printed = qtrue;
1483 }
1484 #ifdef HUNK_DEBUG
1485 Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label);
1486 FS_Write(buf, strlen(buf), logfile);
1487 #endif
1488 size += block->size;
1489 numBlocks++;
1490 }
1491 Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size);
1492 FS_Write(buf, strlen(buf), logfile);
1493 Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks);
1494 FS_Write(buf, strlen(buf), logfile);
1495 }
1496
1497 /*
1498 =================
1499 Com_InitZoneMemory
1500 =================
1501 */
1502 void Com_InitHunkMemory( void ) {
1503 cvar_t *cv;
1504 int nMinAlloc;
1505 char *pMsg = NULL;
1506
1507 // make sure the file system has allocated and "not" freed any temp blocks
1508 // this allows the config and product id files ( journal files too ) to be loaded
1509 // by the file system without redunant routines in the file system utilizing different
1510 // memory systems
1511 if (FS_LoadStack() != 0) {
1512 Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero");
1513 }
1514
1515 // allocate the stack based hunk allocator
1516 cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE );
1517
1518 // if we are not dedicated min allocation is 56, otherwise min is 1
1519 if (com_dedicated && com_dedicated->integer) {
1520 nMinAlloc = MIN_DEDICATED_COMHUNKMEGS;
1521 pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n";
1522 }
1523 else {
1524 nMinAlloc = MIN_COMHUNKMEGS;
1525 pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n";
1526 }
1527
1528 if ( cv->integer < nMinAlloc ) {
1529 s_hunkTotal = 1024 * 1024 * nMinAlloc;
1530 Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024));
1531 } else {
1532 s_hunkTotal = cv->integer * 1024 * 1024;
1533 }
1534
1535 s_hunkData = calloc( s_hunkTotal + 31, 1 );
1536 if ( !s_hunkData ) {
1537 Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) );
1538 }
1539 // cacheline align
1540 s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 );
1541 Hunk_Clear();
1542
1543 Cmd_AddCommand( "meminfo", Com_Meminfo_f );
1544 #ifdef ZONE_DEBUG
1545 Cmd_AddCommand( "zonelog", Z_LogHeap );
1546 #endif
1547 #ifdef HUNK_DEBUG
1548 Cmd_AddCommand( "hunklog", Hunk_Log );
1549 Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog );
1550 #endif
1551 }
1552
1553 /*
1554 ====================
1555 Hunk_MemoryRemaining
1556 ====================
1557 */
1558 int Hunk_MemoryRemaining( void ) {
1559 int low, high;
1560
1561 low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp;
1562 high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp;
1563
1564 return s_hunkTotal - ( low + high );
1565 }
1566
1567 /*
1568 ===================
1569 Hunk_SetMark
1570
1571 The server calls this after the level and game VM have been loaded
1572 ===================
1573 */
1574 void Hunk_SetMark( void ) {
1575 hunk_low.mark = hunk_low.permanent;
1576 hunk_high.mark = hunk_high.permanent;
1577 }
1578
1579 /*
1580 =================
1581 Hunk_ClearToMark
1582
1583 The client calls this before starting a vid_restart or snd_restart
1584 =================
1585 */
1586 void Hunk_ClearToMark( void ) {
1587 hunk_low.permanent = hunk_low.temp = hunk_low.mark;
1588 hunk_high.permanent = hunk_high.temp = hunk_high.mark;
1589 }
1590
1591 /*
1592 =================
1593 Hunk_CheckMark
1594 =================
1595 */
1596 qboolean Hunk_CheckMark( void ) {
1597 if( hunk_low.mark || hunk_high.mark ) {
1598 return qtrue;
1599 }
1600 return qfalse;
1601 }
1602
1603 void CL_ShutdownCGame( void );
1604 void CL_ShutdownUI( void );
1605 void SV_ShutdownGameProgs( void );
1606
1607 /*
1608 =================
1609 Hunk_Clear
1610
1611 The server calls this before shutting down or loading a new map
1612 =================
1613 */
1614 void Hunk_Clear( void ) {
1615
1616 #ifndef DEDICATED
1617 CL_ShutdownCGame();
1618 CL_ShutdownUI();
1619 #endif
1620 SV_ShutdownGameProgs();
1621 #ifndef DEDICATED
1622 CIN_CloseAllVideos();
1623 #endif
1624 hunk_low.mark = 0;
1625 hunk_low.permanent = 0;
1626 hunk_low.temp = 0;
1627 hunk_low.tempHighwater = 0;
1628
1629 hunk_high.mark = 0;
1630 hunk_high.permanent = 0;
1631 hunk_high.temp = 0;
1632 hunk_high.tempHighwater = 0;
1633
1634 hunk_permanent = &hunk_low;
1635 hunk_temp = &hunk_high;
1636
1637 Com_Printf( "Hunk_Clear: reset the hunk ok\n" );
1638 VM_Clear();
1639 #ifdef HUNK_DEBUG
1640 hunkblocks = NULL;
1641 #endif
1642 }
1643
1644 static void Hunk_SwapBanks( void ) {
1645 hunkUsed_t *swap;
1646
1647 // can't swap banks if there is any temp already allocated
1648 if ( hunk_temp->temp != hunk_temp->permanent ) {
1649 return;
1650 }
1651
1652 // if we have a larger highwater mark on this side, start making
1653 // our permanent allocations here and use the other side for temp
1654 if ( hunk_temp->tempHighwater - hunk_temp->permanent >
1655 hunk_permanent->tempHighwater - hunk_permanent->permanent ) {
1656 swap = hunk_temp;
1657 hunk_temp = hunk_permanent;
1658 hunk_permanent = swap;
1659 }
1660 }
1661
1662 /*
1663 =================
1664 Hunk_Alloc
1665
1666 Allocate permanent (until the hunk is cleared) memory
1667 =================
1668 */
1669 #ifdef HUNK_DEBUG
1670 void *Hunk_AllocDebug( int size, ha_pref preference, char *label, char *file, int line ) {
1671 #else
1672 void *Hunk_Alloc( int size, ha_pref preference ) {
1673 #endif
1674 void *buf;
1675
1676 if ( s_hunkData == NULL)
1677 {
1678 Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" );
1679 }
1680
1681 // can't do preference if there is any temp allocated
1682 if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) {
1683 Hunk_SwapBanks();
1684 } else {
1685 if (preference == h_low && hunk_permanent != &hunk_low) {
1686 Hunk_SwapBanks();
1687 } else if (preference == h_high && hunk_permanent != &hunk_high) {
1688 Hunk_SwapBanks();
1689 }
1690 }
1691
1692 #ifdef HUNK_DEBUG
1693 size += sizeof(hunkblock_t);
1694 #endif
1695
1696 // round to cacheline
1697 size = (size+31)&~31;
1698
1699 if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) {
1700 #ifdef HUNK_DEBUG
1701 Hunk_Log();
1702 Hunk_SmallLog();
1703 #endif
1704 Com_Error( ERR_DROP, "Hunk_Alloc failed on %i", size );
1705 }
1706
1707 if ( hunk_permanent == &hunk_low ) {
1708 buf = (void *)(s_hunkData + hunk_permanent->permanent);
1709 hunk_permanent->permanent += size;
1710 } else {
1711 hunk_permanent->permanent += size;
1712 buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
1713 }
1714
1715 hunk_permanent->temp = hunk_permanent->permanent;
1716
1717 Com_Memset( buf, 0, size );
1718
1719 #ifdef HUNK_DEBUG
1720 {
1721 hunkblock_t *block;
1722
1723 block = (hunkblock_t *) buf;
1724 block->size = size - sizeof(hunkblock_t);
1725 block->file = file;
1726 block->label = label;
1727 block->line = line;
1728 block->next = hunkblocks;
1729 hunkblocks = block;
1730 buf = ((byte *) buf) + sizeof(hunkblock_t);
1731 }
1732 #endif
1733 return buf;
1734 }
1735
1736 /*
1737 =================
1738 Hunk_AllocateTempMemory
1739
1740 This is used by the file loading system.
1741 Multiple files can be loaded in temporary memory.
1742 When the files-in-use count reaches zero, all temp memory will be deleted
1743 =================
1744 */
1745 void *Hunk_AllocateTempMemory( int size ) {
1746 void *buf;
1747 hunkHeader_t *hdr;
1748
1749 // return a Z_Malloc'd block if the hunk has not been initialized
1750 // this allows the config and product id files ( journal files too ) to be loaded
1751 // by the file system without redunant routines in the file system utilizing different
1752 // memory systems
1753 if ( s_hunkData == NULL )
1754 {
1755 return Z_Malloc(size);
1756 }
1757
1758 Hunk_SwapBanks();
1759
1760 size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t );
1761
1762 if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) {
1763 Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size );
1764 }
1765
1766 if ( hunk_temp == &hunk_low ) {
1767 buf = (void *)(s_hunkData + hunk_temp->temp);
1768 hunk_temp->temp += size;
1769 } else {
1770 hunk_temp->temp += size;
1771 buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp );
1772 }
1773
1774 if ( hunk_temp->temp > hunk_temp->tempHighwater ) {
1775 hunk_temp->tempHighwater = hunk_temp->temp;
1776 }
1777
1778 hdr = (hunkHeader_t *)buf;
1779 buf = (void *)(hdr+1);
1780
1781 hdr->magic = HUNK_MAGIC;
1782 hdr->size = size;
1783
1784 // don't bother clearing, because we are going to load a file over it
1785 return buf;
1786 }
1787
1788
1789 /*
1790 ==================
1791 Hunk_FreeTempMemory
1792 ==================
1793 */
1794 void Hunk_FreeTempMemory( void *buf ) {
1795 hunkHeader_t *hdr;
1796
1797 // free with Z_Free if the hunk has not been initialized
1798 // this allows the config and product id files ( journal files too ) to be loaded
1799 // by the file system without redunant routines in the file system utilizing different
1800 // memory systems
1801 if ( s_hunkData == NULL )
1802 {
1803 Z_Free(buf);
1804 return;
1805 }
1806
1807
1808 hdr = ( (hunkHeader_t *)buf ) - 1;
1809 if ( hdr->magic != HUNK_MAGIC ) {
1810 Com_Error( ERR_FATAL, "Hunk_FreeTempMemory: bad magic" );
1811 }
1812
1813 hdr->magic = HUNK_FREE_MAGIC;
1814
1815 // this only works if the files are freed in stack order,
1816 // otherwise the memory will stay around until Hunk_ClearTempMemory
1817 if ( hunk_temp == &hunk_low ) {
1818 if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) {
1819 hunk_temp->temp -= hdr->size;
1820 } else {
1821 Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
1822 }
1823 } else {
1824 if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) {
1825 hunk_temp->temp -= hdr->size;
1826 } else {
1827 Com_Printf( "Hunk_FreeTempMemory: not the final block\n" );
1828 }
1829 }
1830 }
1831
1832
1833 /*
1834 =================
1835 Hunk_ClearTempMemory
1836
1837 The temp space is no longer needed. If we have left more
1838 touched but unused memory on this side, have future
1839 permanent allocs use this side.
1840 =================
1841 */
1842 void Hunk_ClearTempMemory( void ) {
1843 if ( s_hunkData != NULL ) {
1844 hunk_temp->temp = hunk_temp->permanent;
1845 }
1846 }
1847
1848 /*
1849 =================
1850 Hunk_Trash
1851 =================
1852 */
1853 void Hunk_Trash( void ) {
1854 int length, i, rnd;
1855 char *buf, value;
1856
1857 return;
1858
1859 if ( s_hunkData == NULL )
1860 return;
1861
1862 #ifdef _DEBUG
1863 Com_Error(ERR_DROP, "hunk trashed\n");
1864 return;
1865 #endif
1866
1867 Cvar_Set("com_jp", "1");
1868 Hunk_SwapBanks();
1869
1870 if ( hunk_permanent == &hunk_low ) {
1871 buf = (void *)(s_hunkData + hunk_permanent->permanent);
1872 } else {
1873 buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent );
1874 }
1875 length = hunk_permanent->permanent;
1876
1877 if (length > 0x7FFFF) {
1878 //randomly trash data within buf
1879 rnd = random() * (length - 0x7FFFF);
1880 value = 31;
1881 for (i = 0; i < 0x7FFFF; i++) {
1882 value *= 109;
1883 buf[rnd+i] ^= value;
1884 }
1885 }
1886 }
1887
1888 /*
1889 ===================================================================
1890
1891 EVENTS AND JOURNALING
1892
1893 In addition to these events, .cfg files are also copied to the
1894 journaled file
1895 ===================================================================
1896 */
1897
1898 #define MAX_PUSHED_EVENTS 1024
1899 static int com_pushedEventsHead = 0;
1900 static int com_pushedEventsTail = 0;
1901 static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS];
1902
1903 /*
1904 =================
1905 Com_InitJournaling
1906 =================
1907 */
1908 void Com_InitJournaling( void ) {
1909 Com_StartupVariable( "journal" );
1910 com_journal = Cvar_Get ("journal", "0", CVAR_INIT);
1911 if ( !com_journal->integer ) {
1912 return;
1913 }
1914
1915 if ( com_journal->integer == 1 ) {
1916 Com_Printf( "Journaling events\n");
1917 com_journalFile = FS_FOpenFileWrite( "journal.dat" );
1918 com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" );
1919 } else if ( com_journal->integer == 2 ) {
1920 Com_Printf( "Replaying journaled events\n");
1921 FS_FOpenFileRead( "journal.dat", &com_journalFile, qtrue );
1922 FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, qtrue );
1923 }
1924
1925 if ( !com_journalFile || !com_journalDataFile ) {
1926 Cvar_Set( "com_journal", "0" );
1927 com_journalFile = 0;
1928 com_journalDataFile = 0;
1929 Com_Printf( "Couldn't open journal files\n" );
1930 }
1931 }
1932
1933 /*
1934 ========================================================================
1935
1936 EVENT LOOP
1937
1938 ========================================================================
1939 */
1940
1941 #define MAX_QUEUED_EVENTS 256
1942 #define MASK_QUEUED_EVENTS ( MAX_QUEUED_EVENTS - 1 )
1943
1944 static sysEvent_t eventQueue[ MAX_QUEUED_EVENTS ];
1945 static int eventHead = 0;
1946 static int eventTail = 0;
1947 static byte sys_packetReceived[ MAX_MSGLEN ];
1948
1949 /*
1950 ================
1951 Com_QueueEvent
1952
1953 A time of 0 will get the current time
1954 Ptr should either be null, or point to a block of data that can
1955 be freed by the game later.
1956 ================
1957 */
1958 void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr )
1959 {
1960 sysEvent_t *ev;
1961
1962 ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ];
1963
1964 if ( eventHead - eventTail >= MAX_QUEUED_EVENTS )
1965 {
1966 Com_Printf("Com_QueueEvent: overflow\n");
1967 // we are discarding an event, but don't leak memory
1968 if ( ev->evPtr )
1969 {
1970 Z_Free( ev->evPtr );
1971 }
1972 eventTail++;
1973 }
1974
1975 eventHead++;
1976
1977 if ( time == 0 )
1978 {
1979 time = Sys_Milliseconds();
1980 }
1981
1982 ev->evTime = time;
1983 ev->evType = type;
1984 ev->evValue = value;
1985 ev->evValue2 = value2;
1986 ev->evPtrLength = ptrLength;
1987 ev->evPtr = ptr;
1988 }
1989
1990 /*
1991 ================
1992 Com_GetSystemEvent
1993
1994 ================
1995 */
1996 sysEvent_t Com_GetSystemEvent( void )
1997 {
1998 sysEvent_t ev;
1999 char *s;
2000 msg_t netmsg;
2001 netadr_t adr;
2002
2003 // return if we have data
2004 if ( eventHead > eventTail )
2005 {
2006 eventTail++;
2007 return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ];
2008 }
2009
2010 // check for console commands
2011 s = Sys_ConsoleInput();
2012 if ( s )
2013 {
2014 char *b;
2015 int len;
2016
2017 len = strlen( s ) + 1;
2018 b = Z_Malloc( len );
2019 strcpy( b, s );
2020 Com_QueueEvent( 0, SE_CONSOLE, 0, 0, len, b );
2021 }
2022
2023 // check for network packets
2024 MSG_Init( &netmsg, sys_packetReceived, sizeof( sys_packetReceived ) );
2025 if ( Sys_GetPacket ( &adr, &netmsg ) )
2026 {
2027 netadr_t *buf;
2028 int len;
2029
2030 // copy out to a seperate buffer for qeueing
2031 len = sizeof( netadr_t ) + netmsg.cursize;
2032 buf = Z_Malloc( len );
2033 *buf = adr;
2034 memcpy( buf+1, netmsg.data, netmsg.cursize );
2035 Com_QueueEvent( 0, SE_PACKET, 0, 0, len, buf );
2036 }
2037
2038 // return if we have data
2039 if ( eventHead > eventTail )
2040 {
2041 eventTail++;
2042 return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ];
2043 }
2044
2045 // create an empty event to return
2046 memset( &ev, 0, sizeof( ev ) );
2047 ev.evTime = Sys_Milliseconds();
2048
2049 return ev;
2050 }
2051
2052 /*
2053 =================
2054 Com_GetRealEvent
2055 =================
2056 */
2057 sysEvent_t Com_GetRealEvent( void ) {
2058 int r;
2059 sysEvent_t ev;
2060
2061 // either get an event from the system or the journal file
2062 if ( com_journal->integer == 2 ) {
2063 r = FS_Read( &ev, sizeof(ev), com_journalFile );
2064 if ( r != sizeof(ev) ) {
2065 Com_Error( ERR_FATAL, "Error reading from journal file" );
2066 }
2067 if ( ev.evPtrLength ) {
2068 ev.evPtr = Z_Malloc( ev.evPtrLength );
2069 r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile );
2070 if ( r != ev.evPtrLength ) {
2071 Com_Error( ERR_FATAL, "Error reading from journal file" );
2072 }
2073 }
2074 } else {
2075 ev = Com_GetSystemEvent();
2076
2077 // write the journal value out if needed
2078 if ( com_journal->integer == 1 ) {
2079 r = FS_Write( &ev, sizeof(ev), com_journalFile );
2080 if ( r != sizeof(ev) ) {
2081 Com_Error( ERR_FATAL, "Error writing to journal file" );
2082 }
2083 if ( ev.evPtrLength ) {
2084 r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile );
2085 if ( r != ev.evPtrLength ) {
2086 Com_Error( ERR_FATAL, "Error writing to journal file" );
2087 }
2088 }
2089 }
2090 }
2091
2092 return ev;
2093 }
2094
2095
2096 /*
2097 =================
2098 Com_InitPushEvent
2099 =================
2100 */
2101 void Com_InitPushEvent( void ) {
2102 // clear the static buffer array
2103 // this requires SE_NONE to be accepted as a valid but NOP event
2104 memset( com_pushedEvents, 0, sizeof(com_pushedEvents) );
2105 // reset counters while we are at it
2106 // beware: GetEvent might still return an SE_NONE from the buffer
2107 com_pushedEventsHead = 0;
2108 com_pushedEventsTail = 0;
2109 }
2110
2111
2112 /*
2113 =================
2114 Com_PushEvent
2115 =================
2116 */
2117 void Com_PushEvent( sysEvent_t *event ) {
2118 sysEvent_t *ev;
2119 static int printedWarning = 0;
2120
2121 ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ];
2122
2123 if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) {
2124
2125 // don't print the warning constantly, or it can give time for more...
2126 if ( !printedWarning ) {
2127 printedWarning = qtrue;
2128 Com_Printf( "WARNING: Com_PushEvent overflow\n" );
2129 }
2130
2131 if ( ev->evPtr ) {
2132 Z_Free( ev->evPtr );
2133 }
2134 com_pushedEventsTail++;
2135 } else {
2136 printedWarning = qfalse;
2137 }
2138
2139 *ev = *event;
2140 com_pushedEventsHead++;
2141 }
2142
2143 /*
2144 =================
2145 Com_GetEvent
2146 =================
2147 */
2148 sysEvent_t Com_GetEvent( void ) {
2149 if ( com_pushedEventsHead > com_pushedEventsTail ) {
2150 com_pushedEventsTail++;
2151 return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ];
2152 }
2153 return Com_GetRealEvent();
2154 }
2155
2156 /*
2157 =================
2158 Com_RunAndTimeServerPacket
2159 =================
2160 */
2161 void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) {
2162 int t1, t2, msec;
2163
2164 t1 = 0;
2165
2166 if ( com_speeds->integer ) {
2167 t1 = Sys_Milliseconds ();
2168 }
2169
2170 SV_PacketEvent( *evFrom, buf );
2171
2172 if ( com_speeds->integer ) {
2173 t2 = Sys_Milliseconds ();
2174 msec = t2 - t1;
2175 if ( com_speeds->integer == 3 ) {
2176 Com_Printf( "SV_PacketEvent time: %i\n", msec );
2177 }
2178 }
2179 }
2180
2181 /*
2182 =================
2183 Com_EventLoop
2184
2185 Returns last event time
2186 =================
2187 */
2188 int Com_EventLoop( void ) {
2189 sysEvent_t ev;
2190 netadr_t evFrom;
2191 byte bufData[MAX_MSGLEN];
2192 msg_t buf;
2193
2194 MSG_Init( &buf, bufData, sizeof( bufData ) );
2195
2196 while ( 1 ) {
2197 NET_FlushPacketQueue();
2198 ev = Com_GetEvent();
2199
2200 // if no more events are available
2201 if ( ev.evType == SE_NONE ) {
2202 // manually send packet events for the loopback channel
2203 while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) {
2204 CL_PacketEvent( evFrom, &buf );
2205 }
2206
2207 while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) {
2208 // if the server just shut down, flush the events
2209 if ( com_sv_running->integer ) {
2210 Com_RunAndTimeServerPacket( &evFrom, &buf );
2211 }
2212 }
2213
2214 return ev.evTime;
2215 }
2216
2217
2218 switch ( ev.evType ) {
2219 default:
2220 Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType );
2221 break;
2222 case SE_NONE:
2223 break;
2224 case SE_KEY:
2225 CL_KeyEvent( ev.evValue, ev.evValue2, ev.evTime );
2226 break;
2227 case SE_CHAR:
2228 CL_CharEvent( ev.evValue );
2229 break;
2230 case SE_MOUSE:
2231 CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime );
2232 break;
2233 case SE_JOYSTICK_AXIS:
2234 CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime );
2235 break;
2236 case SE_CONSOLE:
2237 Cbuf_AddText( (char *)ev.evPtr );
2238 Cbuf_AddText( "\n" );
2239 break;
2240 case SE_PACKET:
2241 // this cvar allows simulation of connections that
2242 // drop a lot of packets. Note that loopback connections
2243 // don't go through here at all.
2244 if ( com_dropsim->value > 0 ) {
2245 static int seed;
2246
2247 if ( Q_random( &seed ) < com_dropsim->value ) {
2248 break; // drop this packet
2249 }
2250 }
2251
2252 evFrom = *(netadr_t *)ev.evPtr;
2253 buf.cursize = ev.evPtrLength - sizeof( evFrom );
2254
2255 // we must copy the contents of the message out, because
2256 // the event buffers are only large enough to hold the
2257 // exact payload, but channel messages need to be large
2258 // enough to hold fragment reassembly
2259 if ( (unsigned)buf.cursize > buf.maxsize ) {
2260 Com_Printf("Com_EventLoop: oversize packet\n");
2261 continue;
2262 }
2263 Com_Memcpy( buf.data, (byte *)((netadr_t *)ev.evPtr + 1), buf.cursize );
2264 if ( com_sv_running->integer ) {
2265 Com_RunAndTimeServerPacket( &evFrom, &buf );
2266 } else {
2267 CL_PacketEvent( evFrom, &buf );
2268 }
2269 break;
2270 }
2271
2272 // free any block data
2273 if ( ev.evPtr ) {
2274 Z_Free( ev.evPtr );
2275 }
2276 }
2277
2278 return 0; // never reached
2279 }
2280
2281 /*
2282 ================
2283 Com_Milliseconds
2284
2285 Can be used for profiling, but will be journaled accurately
2286 ================
2287 */
2288 int Com_Milliseconds (void) {
2289 sysEvent_t ev;
2290
2291 // get events and push them until we get a null event with the current time
2292 do {
2293
2294 ev = Com_GetRealEvent();
2295 if ( ev.evType != SE_NONE ) {
2296 Com_PushEvent( &ev );
2297 }
2298 } while ( ev.evType != SE_NONE );
2299
2300 return ev.evTime;
2301 }
2302
2303 //============================================================================
2304
2305 /*
2306 =============
2307 Com_Error_f
2308
2309 Just throw a fatal error to
2310 test error shutdown procedures
2311 =============
2312 */
2313 static void Com_Error_f (void) {
2314 if ( Cmd_Argc() > 1 ) {
2315 Com_Error( ERR_DROP, "Testing drop error" );
2316 } else {
2317 Com_Error( ERR_FATAL, "Testing fatal error" );
2318 }
2319 }
2320
2321
2322 /*
2323 =============
2324 Com_Freeze_f
2325
2326 Just freeze in place for a given number of seconds to test
2327 error recovery
2328 =============
2329 */
2330 static void Com_Freeze_f (void) {
2331 float s;
2332 int start, now;
2333
2334 if ( Cmd_Argc() != 2 ) {
2335 Com_Printf( "freeze <seconds>\n" );
2336 return;
2337 }
2338 s = atof( Cmd_Argv(1) );
2339
2340 start = Com_Milliseconds();
2341
2342 while ( 1 ) {
2343 now = Com_Milliseconds();
2344 if ( ( now - start ) * 0.001 > s ) {
2345 break;
2346 }
2347 }
2348 }
2349
2350 /*
2351 =================
2352 Com_Crash_f
2353
2354 A way to force a bus error for development reasons
2355 =================
2356 */
2357 static void Com_Crash_f( void ) {
2358 * ( int * ) 0 = 0x12345678;
2359 }
2360
2361 #ifndef STANDALONE
2362
2363 // TTimo: centralizing the cl_cdkey stuff after I discovered a buffer overflow problem with the dedicated server version
2364 // not sure it's necessary to have different defaults for regular and dedicated, but I don't want to risk it
2365 // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=470
2366 #ifndef DEDICATED
2367 char cl_cdkey[34] = " ";
2368 #else
2369 char cl_cdkey[34] = "123456789";
2370 #endif
2371
2372 /*
2373 =================
2374 Com_ReadCDKey
2375 =================
2376 */
2377 qboolean CL_CDKeyValidate( const char *key, const char *checksum );
2378 void Com_ReadCDKey( const char *filename ) {
2379 fileHandle_t f;
2380 char buffer[33];
2381 char fbuffer[MAX_OSPATH];
2382
2383 sprintf(fbuffer, "%s/q3key", filename);
2384
2385 FS_SV_FOpenFileRead( fbuffer, &f );
2386 if ( !f ) {
2387 Q_strncpyz( cl_cdkey, " ", 17 );
2388 return;
2389 }
2390
2391 Com_Memset( buffer, 0, sizeof(buffer) );
2392
2393 FS_Read( buffer, 16, f );
2394 FS_FCloseFile( f );
2395
2396 if (CL_CDKeyValidate(buffer, NULL)) {
2397 Q_strncpyz( cl_cdkey, buffer, 17 );
2398 } else {
2399 Q_strncpyz( cl_cdkey, " ", 17 );
2400 }
2401 }
2402
2403 /*
2404 =================
2405 Com_AppendCDKey
2406 =================
2407 */
2408 void Com_AppendCDKey( const char *filename ) {
2409 fileHandle_t f;
2410 char buffer[33];
2411 char fbuffer[MAX_OSPATH];
2412
2413 sprintf(fbuffer, "%s/q3key", filename);
2414
2415 FS_SV_FOpenFileRead( fbuffer, &f );
2416 if (!f) {
2417 Q_strncpyz( &cl_cdkey[16], " ", 17 );
2418 return;
2419 }
2420
2421 Com_Memset( buffer, 0, sizeof(buffer) );
2422
2423 FS_Read( buffer, 16, f );
2424 FS_FCloseFile( f );
2425
2426 if (CL_CDKeyValidate(buffer, NULL)) {
2427 strcat( &cl_cdkey[16], buffer );
2428 } else {
2429 Q_strncpyz( &cl_cdkey[16], " ", 17 );
2430 }
2431 }
2432
2433 #ifndef DEDICATED
2434 /*
2435 =================
2436 Com_WriteCDKey
2437 =================
2438 */
2439 /* ... nonCDKEY
2440 static void Com_WriteCDKey( const char *filename, const char *ikey ) {
2441 fileHandle_t f;
2442 char fbuffer[MAX_OSPATH];
2443 char key[17];
2444 #ifndef _WIN32
2445 mode_t savedumask;
2446 #endif
2447
2448
2449 sprintf(fbuffer, "%s/q3key", filename);
2450
2451
2452 Q_strncpyz( key, ikey, 17 );
2453
2454 if(!CL_CDKeyValidate(key, NULL) ) {
2455 return;
2456 }
2457
2458 #ifndef _WIN32
2459 savedumask = umask(0077);
2460 #endif
2461 f = FS_SV_FOpenFileWrite( fbuffer );
2462 if ( !f ) {
2463 Com_Printf ("Couldn't write CD key to %s.\n", fbuffer );
2464 goto out;
2465 }
2466
2467 FS_Write( key, 16, f );
2468
2469 FS_Printf( f, "\n// generated by quake, do not modify\r\n" );
2470 FS_Printf( f, "// Do not give this file to ANYONE.\r\n" );
2471 FS_Printf( f, "// id Software and Activision will NOT ask you to send this file to them.\r\n");
2472
2473 FS_FCloseFile( f );
2474 out:
2475 #ifndef _WIN32
2476 umask(savedumask);
2477 #endif
2478 return;
2479 }
2480 */
2481 #endif
2482
2483 #endif // STANDALONE
2484
2485 static void Com_DetectAltivec(void)
2486 {
2487 // Only detect if user hasn't forcibly disabled it.
2488 if (com_altivec->integer) {
2489 static qboolean altivec = qfalse;
2490 static qboolean detected = qfalse;
2491 if (!detected) {
2492 altivec = ( Sys_GetProcessorFeatures( ) & CF_ALTIVEC );
2493 detected = qtrue;
2494 }
2495
2496 if (!altivec) {
2497 Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support!
2498 }
2499 }
2500 }
2501
2502
2503 /*
2504 =================
2505 Com_Init
2506 =================
2507 */
2508 void Com_Init( char *commandLine ) {
2509 char *s;
2510
2511 Com_Printf( "%s %s %s\n", VERSION_INFO, PLATFORM_STRING, __DATE__ );
2512
2513 if ( setjmp (abortframe) ) {
2514 Sys_Error ("Error during initialization");
2515 }
2516
2517 // Clear queues
2518 Com_Memset( &eventQueue[ 0 ], 0, MAX_QUEUED_EVENTS * sizeof( sysEvent_t ) );
2519 Com_Memset( &sys_packetReceived[ 0 ], 0, MAX_MSGLEN * sizeof( byte ) );
2520
2521 // do this before anything else decides to push events
2522 Com_InitPushEvent();
2523
2524 Com_InitSmallZoneMemory();
2525 Cvar_Init ();
2526
2527 win_multiUser = Cvar_Get("win_multiUser","0",0);// CVAR_ARCHIVE
2528
2529 // prepare enough of the subsystems to handle
2530 // cvar and command buffer management
2531 Com_ParseCommandLine( commandLine );
2532
2533 // Swap_Init ();
2534 Cbuf_Init ();
2535
2536 Com_InitZoneMemory();
2537 Cmd_Init ();
2538
2539 // override anything from the config files with command line args
2540 Com_StartupVariable( NULL );
2541
2542 // get the developer cvar set as early as possible
2543 Com_StartupVariable( "developer" );
2544
2545 // done early so bind command exists
2546 CL_InitKeyCommands();
2547
2548 FS_InitFilesystem ();
2549
2550 Com_InitJournaling();
2551
2552 Cbuf_AddText ("exec default.cfg\n");
2553
2554 // skip the q3config.cfg if "safe" is on the command line
2555 if ( !Com_SafeMode() ) {
2556 Cbuf_AddText ("exec " Q3CONFIG_CFG "\n");
2557 }
2558
2559 Cbuf_AddText ("exec autoexec.cfg\n");
2560
2561 Cbuf_Execute ();
2562
2563 // override anything from the config files with command line args
2564 Com_StartupVariable( NULL );
2565
2566 // get dedicated here for proper hunk megs initialization
2567 #ifdef DEDICATED
2568 com_dedicated = Cvar_Get ("dedicated", "1", CVAR_ROM);
2569 #else
2570 com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH);
2571 #endif
2572 // allocate the stack based hunk allocator
2573 Com_InitHunkMemory();
2574
2575 // if any archived cvars are modified after this, we will trigger a writing
2576 // of the config file
2577 cvar_modifiedFlags &= ~CVAR_ARCHIVE;
2578
2579 //
2580 // init commands and vars
2581 //
2582 com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE);
2583 com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE);
2584 com_blood = Cvar_Get ("com_blood", "1", CVAR_ARCHIVE);
2585
2586 com_developer = Cvar_Get ("developer", "0", CVAR_TEMP );
2587 com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP );
2588
2589 com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO );
2590 com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT);
2591 com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT);
2592 com_dropsim = Cvar_Get ("com_dropsim", "0", CVAR_CHEAT);
2593 com_speeds = Cvar_Get ("com_speeds", "0", 0);
2594 com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT);
2595 com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT);
2596
2597 cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM);
2598 sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM);
2599 cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT);
2600 sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT);
2601 com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM);
2602 com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM);
2603 com_buildScript = Cvar_Get( "com_buildScript", "0", 0 );
2604 com_ansiColor = Cvar_Get( "com_ansiColor", "0", CVAR_ARCHIVE );
2605
2606 com_unfocused = Cvar_Get( "com_unfocused", "0", CVAR_ROM );
2607 com_minimized = Cvar_Get( "com_minimized", "0", CVAR_ROM );
2608 com_standalone = Cvar_Get( "com_standalone", "0", CVAR_INIT );
2609
2610 com_introPlayed = Cvar_Get( "com_introplayed", "0", CVAR_ARCHIVE);
2611
2612 if ( com_developer && com_developer->integer ) {
2613 Cmd_AddCommand ("error", Com_Error_f);
2614 Cmd_AddCommand ("crash", Com_Crash_f );
2615 Cmd_AddCommand ("freeze", Com_Freeze_f);
2616 }
2617 Cmd_AddCommand ("quit", Com_Quit_f);
2618 Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f );
2619 Cmd_AddCommand ("writeconfig", Com_WriteConfig_f );
2620
2621 s = va("%s %s %s", Q3_VERSION, PLATFORM_STRING, __DATE__ );
2622 com_version = Cvar_Get ("version", s, CVAR_ROM | CVAR_SERVERINFO );
2623
2624 Sys_Init();
2625 Netchan_Init( Com_Milliseconds() & 0xffff ); // pick a port value that should be nice and random
2626 VM_Init();
2627 SV_Init();
2628
2629 com_dedicated->modified = qfalse;
2630 #ifndef DEDICATED
2631 CL_Init();
2632 #endif
2633
2634 // set com_frameTime so that if a map is started on the
2635 // command line it will still be able to count on com_frameTime
2636 // being random enough for a serverid
2637 com_frameTime = Com_Milliseconds();
2638
2639 // add + commands from command line
2640 if ( !Com_AddStartupCommands() ) {
2641 // if the user didn't give any commands, run default action
2642 if ( !com_dedicated->integer ) {
2643 Cbuf_AddText ("cinematic idlogo.RoQ\n");
2644 /*
2645 if( !com_introPlayed->integer ) {
2646 Cvar_Set( com_introPlayed->name, "1" );
2647 Cvar_Set( "nextmap", "cinematic intro.RoQ" );
2648 }
2649 */
2650 }
2651 }
2652
2653 // start in full screen ui mode
2654 Cvar_Set("r_uiFullScreen", "1");
2655
2656 CL_StartHunkUsers( qfalse );
2657
2658 // make sure single player is off by default
2659 Cvar_Set("ui_singlePlayerActive", "0");
2660
2661 com_fullyInitialized = qtrue;
2662
2663 // always set the cvar, but only print the info if it makes sense.
2664 Com_DetectAltivec();
2665 #if idppc
2666 Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled");
2667 #endif
2668
2669 Com_Printf ("--- Common Initialization Complete ---\n");
2670 }
2671
2672 //==================================================================
2673
2674 void Com_WriteConfigToFile( const char *filename ) {
2675 fileHandle_t f;
2676
2677 f = FS_FOpenFileWrite( filename );
2678 if ( !f ) {
2679 Com_Printf ("Couldn't write %s.\n", filename );
2680 return;
2681 }
2682
2683 FS_Printf (f, "// generated by WoP-Engine, do not modify\n");
2684 Key_WriteBindings (f);
2685 Cvar_WriteVariables (f);
2686 FS_FCloseFile( f );
2687 }
2688
2689
2690 /*
2691 ===============
2692 Com_WriteConfiguration
2693
2694 Writes key bindings and archived cvars to config file if modified
2695 ===============
2696 */
2697 void Com_WriteConfiguration( void ) {
2698 #ifndef DEDICATED
2699 cvar_t *fs;
2700 #endif
2701 // if we are quiting without fully initializing, make sure
2702 // we don't write out anything
2703 if ( !com_fullyInitialized ) {
2704 return;
2705 }
2706
2707 if ( !(cvar_modifiedFlags & CVAR_ARCHIVE ) ) {
2708 return;
2709 }
2710 cvar_modifiedFlags &= ~CVAR_ARCHIVE;
2711
2712 Com_WriteConfigToFile( Q3CONFIG_CFG );
2713
2714 // not needed for dedicated
2715 #ifndef DEDICATED
2716 fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
2717 #ifndef STANDALONE
2718 if(!Cvar_VariableIntegerValue("com_standalone"))
2719 {
2720 if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) {
2721 Com_WriteCDKey( fs->string, &cl_cdkey[16] );
2722 } else {
2723 Com_WriteCDKey( BASEGAME, cl_cdkey );
2724 }
2725 }
2726 #endif
2727 #endif
2728 }
2729
2730
2731 /*
2732 ===============
2733 Com_WriteConfig_f
2734
2735 Write the config file to a specific name
2736 ===============
2737 */
2738 void Com_WriteConfig_f( void ) {
2739 char filename[MAX_QPATH];
2740
2741 if ( Cmd_Argc() != 2 ) {
2742 Com_Printf( "Usage: writeconfig <filename>\n" );
2743 return;
2744 }
2745
2746 Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) );
2747 COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
2748 Com_Printf( "Writing %s.\n", filename );
2749 Com_WriteConfigToFile( filename );
2750 }
2751
2752 /*
2753 ================
2754 Com_ModifyMsec
2755 ================
2756 */
2757 int Com_ModifyMsec( int msec ) {
2758 int clampTime;
2759
2760 //
2761 // modify time for debugging values
2762 //
2763 if ( com_fixedtime->integer ) {
2764 msec = com_fixedtime->integer;
2765 } else if ( com_timescale->value ) {
2766 msec *= com_timescale->value;
2767 } else if (com_cameraMode->integer) {
2768 msec *= com_timescale->value;
2769 }
2770
2771 // don't let it scale below 1 msec
2772 if ( msec < 1 && com_timescale->value) {
2773 msec = 1;
2774 }
2775
2776 if ( com_dedicated->integer ) {
2777 // dedicated servers don't want to clamp for a much longer
2778 // period, because it would mess up all the client's views
2779 // of time.
2780 if (com_sv_running->integer && msec > 500)
2781 Com_Printf( "Hitch warning: %i msec frame time\n", msec );
2782
2783 clampTime = 5000;
2784 } else
2785 if ( !com_sv_running->integer ) {
2786 // clients of remote servers do not want to clamp time, because
2787 // it would skew their view of the server's time temporarily
2788 clampTime = 5000;
2789 } else {
2790 // for local single player gaming
2791 // we may want to clamp the time to prevent players from
2792 // flying off edges when something hitches.
2793 clampTime = 200;
2794 }
2795
2796 if ( msec > clampTime ) {
2797 msec = clampTime;
2798 }
2799
2800 return msec;
2801 }
2802
2803 /*
2804 =================
2805 Com_Frame
2806 =================
2807 */
2808 void Com_Frame( void ) {
2809
2810 int msec, minMsec;
2811 static int lastTime;
2812 int key;
2813
2814 int timeBeforeFirstEvents;
2815 int timeBeforeServer;
2816 int timeBeforeEvents;
2817 int timeBeforeClient;
2818 int timeAfter;
2819
2820
2821
2822
2823
2824 if ( setjmp (abortframe) ) {
2825 return; // an ERR_DROP was thrown
2826 }
2827
2828 timeBeforeFirstEvents =0;
2829 timeBeforeServer =0;
2830 timeBeforeEvents =0;
2831 timeBeforeClient = 0;
2832 timeAfter = 0;
2833
2834
2835 // old net chan encryption key
2836 key = 0x87243987;
2837
2838 // write config file if anything changed
2839 Com_WriteConfiguration();
2840
2841 //
2842 // main event loop
2843 //
2844 if ( com_speeds->integer ) {
2845 timeBeforeFirstEvents = Sys_Milliseconds ();
2846 }
2847
2848 // we may want to spin here if things are going too fast
2849 if ( !com_dedicated->integer && com_maxfps->integer > 0 && !com_timedemo->integer ) {
2850 minMsec = 1000 / com_maxfps->integer;
2851 } else {
2852 minMsec = 1;
2853 }
2854 do {
2855 com_frameTime = Com_EventLoop();
2856 if ( lastTime > com_frameTime ) {
2857 lastTime = com_frameTime; // possible on first frame
2858 }
2859 msec = com_frameTime - lastTime;
2860 } while ( msec < minMsec );
2861 Cbuf_Execute ();
2862
2863 if (com_altivec->modified)
2864 {
2865 Com_DetectAltivec();
2866 com_altivec->modified = qfalse;
2867 }
2868
2869 lastTime = com_frameTime;
2870
2871 // mess with msec if needed
2872 com_frameMsec = msec;
2873 msec = Com_ModifyMsec( msec );
2874
2875 //
2876 // server side
2877 //
2878 if ( com_speeds->integer ) {
2879 timeBeforeServer = Sys_Milliseconds ();
2880 }
2881
2882 SV_Frame( msec );
2883
2884 // if "dedicated" has been modified, start up
2885 // or shut down the client system.
2886 // Do this after the server may have started,
2887 // but before the client tries to auto-connect
2888 if ( com_dedicated->modified ) {
2889 // get the latched value
2890 Cvar_Get( "dedicated", "0", 0 );
2891 com_dedicated->modified = qfalse;
2892 if ( !com_dedicated->integer ) {
2893 SV_Shutdown( "dedicated set to 0" );
2894 CL_FlushMemory();
2895 }
2896 }
2897
2898 #ifndef DEDICATED
2899 //
2900 // client system
2901 //
2902 //
2903 // run event loop a second time to get server to client packets
2904 // without a frame of latency
2905 //
2906 if ( com_speeds->integer ) {
2907 timeBeforeEvents = Sys_Milliseconds ();
2908 }
2909 Com_EventLoop();
2910 Cbuf_Execute ();
2911
2912
2913 //
2914 // client side
2915 //
2916 if ( com_speeds->integer ) {
2917 timeBeforeClient = Sys_Milliseconds ();
2918 }
2919
2920 CL_Frame( msec );
2921
2922 if ( com_speeds->integer ) {
2923 timeAfter = Sys_Milliseconds ();
2924 }
2925 #endif
2926
2927 //
2928 // report timing information
2929 //
2930 if ( com_speeds->integer ) {
2931 int all, sv, ev, cl;
2932
2933 all = timeAfter - timeBeforeServer;
2934 sv = timeBeforeEvents - timeBeforeServer;
2935 ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents;
2936 cl = timeAfter - timeBeforeClient;
2937 sv -= time_game;
2938 cl -= time_frontend + time_backend;
2939
2940 Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n",
2941 com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend );
2942 }
2943
2944 //
2945 // trace optimization tracking
2946 //
2947 if ( com_showtrace->integer ) {
2948
2949 extern int c_traces, c_brush_traces, c_patch_traces;
2950 extern int c_pointcontents;
2951
2952 Com_Printf ("%4i traces (%ib %ip) %4i points\n", c_traces,
2953 c_brush_traces, c_patch_traces, c_pointcontents);
2954 c_traces = 0;
2955 c_brush_traces = 0;
2956 c_patch_traces = 0;
2957 c_pointcontents = 0;
2958 }
2959
2960 // old net chan encryption key
2961 key = lastTime * 0x87243987;
2962
2963 com_frameNumber++;
2964 }
2965
2966 /*
2967 =================
2968 Com_Shutdown
2969 =================
2970 */
2971 void Com_Shutdown (void) {
2972 if (logfile) {
2973 FS_FCloseFile (logfile);
2974 logfile = 0;
2975 }
2976
2977 if ( com_journalFile ) {
2978 FS_FCloseFile( com_journalFile );
2979 com_journalFile = 0;
2980 }
2981
2982 }
2983
2984 //------------------------------------------------------------------------
2985
2986
2987 /*
2988 =====================
2989 Q_acos
2990
2991 the msvc acos doesn't always return a value between -PI and PI:
2992
2993 int i;
2994 i = 1065353246;
2995 acos(*(float*) &i) == -1.#IND0
2996
2997 This should go in q_math but it is too late to add new traps
2998 to game and ui
2999 =====================
3000 */
3001 float Q_acos(float c) {
3002 float angle;
3003
3004 angle = acos(c);
3005
3006 if (angle > M_PI) {
3007 return (float)M_PI;
3008 }
3009 if (angle < -M_PI) {
3010 return (float)M_PI;
3011 }
3012 return angle;
3013 }
3014
3015 /*
3016 ===========================================
3017 command line completion
3018 ===========================================
3019 */
3020
3021 /*
3022 ==================
3023 Field_Clear
3024 ==================
3025 */
3026 void Field_Clear( field_t *edit ) {
3027 memset(edit->buffer, 0, MAX_EDIT_LINE);
3028 edit->cursor = 0;
3029 edit->scroll = 0;
3030 }
3031
3032 static const char *completionString;
3033 static char shortestMatch[MAX_TOKEN_CHARS];
3034 static int matchCount;
3035 // field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance)
3036 static field_t *completionField;
3037
3038 /*
3039 ===============
3040 FindMatches
3041
3042 ===============
3043 */
3044 static void FindMatches( const char *s ) {
3045 int i;
3046
3047 if ( Q_stricmpn( s, completionString, strlen( completionString ) ) ) {
3048 return;
3049 }
3050 matchCount++;
3051 if ( matchCount == 1 ) {
3052 Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) );
3053 return;
3054 }
3055
3056 // cut shortestMatch to the amount common with s
3057 for ( i = 0 ; shortestMatch[i] ; i++ ) {
3058 if ( i >= strlen( s ) ) {
3059 shortestMatch[i] = 0;
3060 break;
3061 }
3062
3063 if ( tolower(shortestMatch[i]) != tolower(s[i]) ) {
3064 shortestMatch[i] = 0;
3065 }
3066 }
3067 }
3068
3069 /*
3070 ===============
3071 PrintMatches
3072
3073 ===============
3074 */
3075 static void PrintMatches( const char *s ) {
3076 if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
3077 Com_Printf( " %s\n", s );
3078 }
3079 }
3080
3081 /*
3082 ===============
3083 PrintCvarMatches
3084
3085 ===============
3086 */
3087 static void PrintCvarMatches( const char *s ) {
3088 char value[ TRUNCATE_LENGTH ];
3089
3090 if ( !Q_stricmpn( s, shortestMatch, strlen( shortestMatch ) ) ) {
3091 Com_TruncateLongString( value, Cvar_VariableString( s ) );
3092 Com_Printf( " %s = \"%s\"\n", s, value );
3093 }
3094 }
3095
3096 /*
3097 ===============
3098 Field_FindFirstSeparator
3099 ===============
3100 */
3101 static char *Field_FindFirstSeparator( char *s )
3102 {
3103 int i;
3104
3105 for( i = 0; i < strlen( s ); i++ )
3106 {
3107 if( s[ i ] == ';' )
3108 return &s[ i ];
3109 }
3110
3111 return NULL;
3112 }
3113
3114 #ifndef DEDICATED
3115 /*
3116 ===============
3117 Field_CompleteKeyname
3118 ===============
3119 */
3120 static void Field_CompleteKeyname( void )
3121 {
3122 matchCount = 0;
3123 shortestMatch[ 0 ] = 0;
3124
3125 Key_KeynameCompletion( FindMatches );
3126
3127 if( matchCount == 0 )
3128 return;
3129
3130 Q_strncpyz( &completionField->buffer[ strlen( completionField->buffer ) -
3131 strlen( completionString ) ], shortestMatch,
3132 sizeof( completionField->buffer ) );
3133 completionField->cursor = strlen( completionField->buffer );
3134
3135 if( matchCount == 1 )
3136 {
3137 Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
3138 completionField->cursor++;
3139 return;
3140 }
3141
3142 Com_Printf( "]%s\n", completionField->buffer );
3143
3144 Key_KeynameCompletion( PrintMatches );
3145 }
3146 #endif
3147
3148 /*
3149 ===============
3150 Field_CompleteFilename
3151 ===============
3152 */
3153 static void Field_CompleteFilename( const char *dir,
3154 const char *ext, qboolean stripExt )
3155 {
3156 matchCount = 0;
3157 shortestMatch[ 0 ] = 0;
3158
3159 FS_FilenameCompletion( dir, ext, stripExt, FindMatches );
3160
3161 if( matchCount == 0 )
3162 return;
3163
3164 Q_strncpyz( &completionField->buffer[ strlen( completionField->buffer ) -
3165 strlen( completionString ) ], shortestMatch,
3166 sizeof( completionField->buffer ) );
3167 completionField->cursor = strlen( completionField->buffer );
3168
3169 if( matchCount == 1 )
3170 {
3171 Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
3172 completionField->cursor++;
3173 return;
3174 }
3175
3176 Com_Printf( "]%s\n", completionField->buffer );
3177
3178 FS_FilenameCompletion( dir, ext, stripExt, PrintMatches );
3179 }
3180
3181 /*
3182 ===============
3183 Field_CompleteCommand
3184 ===============
3185 */
3186 static void Field_CompleteCommand( char *cmd,
3187 qboolean doCommands, qboolean doCvars )
3188 {
3189 int completionArgument = 0;
3190 char *p;
3191
3192 // Skip leading whitespace and quotes
3193 cmd = Com_SkipCharset( cmd, " \"" );
3194
3195 Cmd_TokenizeStringIgnoreQuotes( cmd );
3196 completionArgument = Cmd_Argc( );
3197
3198 // If there is trailing whitespace on the cmd
3199 if( *( cmd + strlen( cmd ) - 1 ) == ' ' )
3200 {
3201 completionString = "";
3202 completionArgument++;
3203 }
3204 else
3205 completionString = Cmd_Argv( completionArgument - 1 );
3206
3207 #ifndef DEDICATED
3208 // Unconditionally add a '\' to the start of the buffer
3209 if( completionField->buffer[ 0 ] &&
3210 completionField->buffer[ 0 ] != '\\' )
3211 {
3212 if( completionField->buffer[ 0 ] != '/' )
3213 {
3214 // Buffer is full, refuse to complete
3215 if( strlen( completionField->buffer ) + 1 >=
3216 sizeof( completionField->buffer ) )
3217 return;
3218
3219 memmove( &completionField->buffer[ 1 ],
3220 &completionField->buffer[ 0 ],
3221 strlen( completionField->buffer ) + 1 );
3222 completionField->cursor++;
3223 }
3224
3225 completionField->buffer[ 0 ] = '\\';
3226 }
3227 #endif
3228
3229 if( completionArgument > 1 )
3230 {
3231 const char *baseCmd = Cmd_Argv( 0 );
3232
3233 #ifndef DEDICATED
3234 // This should always be true
3235 if( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' )
3236 baseCmd++;
3237 #endif
3238
3239 if( ( p = Field_FindFirstSeparator( cmd ) ) )
3240 {
3241 // Compound command
3242 Field_CompleteCommand( p + 1, qtrue, qtrue );
3243 }
3244 else
3245 {
3246 // FIXME: all this junk should really be associated with the respective
3247 // commands, instead of being hard coded here
3248 if( ( !Q_stricmp( baseCmd, "map" ) ||
3249 !Q_stricmp( baseCmd, "devmap" ) ||
3250 !Q_stricmp( baseCmd, "spmap" ) ||
3251 !Q_stricmp( baseCmd, "spdevmap" ) ) &&
3252 completionArgument == 2 )
3253 {
3254 Field_CompleteFilename( "maps", "bsp", qtrue );
3255 }
3256 else if( ( !Q_stricmp( baseCmd, "exec" ) ||
3257 !Q_stricmp( baseCmd, "writeconfig" ) ) &&
3258 completionArgument == 2 )
3259 {
3260 Field_CompleteFilename( "", "cfg", qfalse );
3261 }
3262 else if( !Q_stricmp( baseCmd, "condump" ) &&
3263 completionArgument == 2 )
3264 {
3265 Field_CompleteFilename( "", "txt", qfalse );
3266 }
3267 else if( ( !Q_stricmp( baseCmd, "toggle" ) ||
3268 !Q_stricmp( baseCmd, "vstr" ) ||
3269 !Q_stricmp( baseCmd, "set" ) ||
3270 !Q_stricmp( baseCmd, "seta" ) ||
3271 !Q_stricmp( baseCmd, "setu" ) ||
3272 !Q_stricmp( baseCmd, "sets" ) ) &&
3273 completionArgument == 2 )
3274 {
3275 // Skip "<cmd> "
3276 p = Com_SkipTokens( cmd, 1, " " );
3277
3278 if( p > cmd )
3279 Field_CompleteCommand( p, qfalse, qtrue );
3280 }
3281 #ifndef DEDICATED
3282 else if( !Q_stricmp( baseCmd, "demo" ) && completionArgument == 2 )
3283 {
3284 char demoExt[ 16 ];
3285
3286 Com_sprintf( demoExt, sizeof( demoExt ), ".dm_%d", PROTOCOL_VERSION );
3287 Field_CompleteFilename( "demos", demoExt, qtrue );
3288 }
3289 else if( !Q_stricmp( baseCmd, "rcon" ) && completionArgument == 2 )
3290 {
3291 // Skip "rcon "
3292 p = Com_SkipTokens( cmd, 1, " " );
3293
3294 if( p > cmd )
3295 Field_CompleteCommand( p, qtrue, qtrue );
3296 }
3297 else if( !Q_stricmp( baseCmd, "bind" ) )
3298 {
3299 if( completionArgument == 2 )
3300 {
3301 // Skip "bind "
3302 p = Com_SkipTokens( cmd, 1, " " );
3303
3304 if( p > cmd )
3305 Field_CompleteKeyname( );
3306 }
3307 else if( completionArgument >= 3 )
3308 {
3309 // Skip "bind <key> "
3310 p = Com_SkipTokens( cmd, 2, " " );
3311
3312 if( p > cmd )
3313 Field_CompleteCommand( p, qtrue, qtrue );
3314 }
3315 }
3316 #endif
3317 }
3318 }
3319 else
3320 {
3321 int completionOffset;
3322
3323 if( completionString[0] == '\\' || completionString[0] == '/' )
3324 completionString++;
3325
3326 matchCount = 0;
3327 shortestMatch[ 0 ] = 0;
3328
3329 if( strlen( completionString ) == 0 )
3330 return;
3331
3332 if( doCommands )
3333 Cmd_CommandCompletion( FindMatches );
3334
3335 if( doCvars )
3336 Cvar_CommandCompletion( FindMatches );
3337
3338 if( matchCount == 0 )
3339 return; // no matches
3340
3341 completionOffset = strlen( completionField->buffer ) - strlen( completionString );
3342
3343 Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch,
3344 sizeof( completionField->buffer ) - completionOffset );
3345
3346 completionField->cursor = strlen( completionField->buffer );
3347
3348 if( matchCount == 1 )
3349 {
3350 Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " );
3351 completionField->cursor++;
3352 return;
3353 }
3354
3355 Com_Printf( "]%s\n", completionField->buffer );
3356
3357 // run through again, printing matches
3358 if( doCommands )
3359 Cmd_CommandCompletion( PrintMatches );
3360
3361 if( doCvars )
3362 Cvar_CommandCompletion( PrintCvarMatches );
3363 }
3364 }
3365
3366 /*
3367 ===============
3368 Field_AutoComplete
3369
3370 Perform Tab expansion
3371 ===============
3372 */
3373 void Field_AutoComplete( field_t *field )
3374 {
3375 completionField = field;
3376
3377 Field_CompleteCommand( completionField->buffer, qtrue, qtrue );
3378 }
3379
3380 /*
3381 ==================
3382 Com_RandomBytes
3383
3384 fills string array with len radom bytes, peferably from the OS randomizer
3385 ==================
3386 */
3387 void Com_RandomBytes( byte *string, int len )
3388 {
3389 int i;
3390
3391 if( Sys_RandomBytes( string, len ) )
3392 return;
3393
3394 Com_Printf( "Com_RandomBytes: using weak randomization\n" );
3395 srand( time( 0 ) );
3396 for( i = 0; i < len; i++ )
3397 string[i] = (unsigned char)( rand() % 255 );
3398 }
3399
3400