1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 //
21 // common.c
22 // Functions used in client and server
23 //
24
25 #include "common.h"
26 #include <setjmp.h>
27
28 jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame
29
30 cVar_t nullCvar;
31
32 cVar_t *developer = &nullCvar;
33 cVar_t *cg_developer;
34 cVar_t *fs_developer = &nullCvar;
35
36 cVar_t *timescale;
37 cVar_t *fixedtime;
38 cVar_t *logfile; // 1 = buffer log, 2 = flush after each print
39 cVar_t *dedicated;
40
41 static qBool com_initialized;
42 static FILE *com_logFile;
43 static uint32 com_numErrors;
44 static uint32 com_numWarnings;
45
46 struct memPool_s *com_aliasSysPool;
47 struct memPool_s *com_cmdSysPool;
48 struct memPool_s *com_cmodelSysPool;
49 struct memPool_s *com_cvarSysPool;
50 struct memPool_s *com_fileSysPool;
51 struct memPool_s *com_genericPool;
52
53 /*
54 ============================================================================
55
56 HASH OPTIMIZING
57
58 ============================================================================
59 */
60
61 /*
62 ================
63 Com_HashFileName
64
65 Normalizes path slashes, and stops before the extension.
66 hashSize MUST be a power of two!
67 ================
68 */
Com_HashFileName(const char * fileName,const int hashSize)69 uint32 Com_HashFileName (const char *fileName, const int hashSize)
70 {
71 uint32 hashValue;
72 int ch, i;
73
74 for (i=0, hashValue=0 ; *fileName ; i++) {
75 ch = *(fileName++);
76 if (ch == '\\')
77 ch = '/';
78 else if (ch == '.')
79 break;
80
81 hashValue = hashValue * 33 + Q_tolower (ch);
82 }
83
84 return (hashValue + (hashValue >> 5)) & (hashSize-1);
85 }
86
87
88 /*
89 ================
90 Com_HashGeneric
91
92 hashSize MUST be a power of two!
93 ================
94 */
Com_HashGeneric(const char * name,const int hashSize)95 uint32 Com_HashGeneric (const char *name, const int hashSize)
96 {
97 uint32 hashValue;
98 int ch, i;
99
100 for (i=0, hashValue=0 ; *name ; i++) {
101 ch = *(name++);
102 hashValue = hashValue * 33 + Q_tolower (ch);
103 }
104
105 return (hashValue + (hashValue >> 5)) & (hashSize-1);
106 }
107
108
109 /*
110 ================
111 Com_HashGenericFast
112
113 hashSize MUST be a power of two!
114 Same as above except no Q_tolower.
115 ================
116 */
Com_HashGenericFast(const char * name,const int hashSize)117 uint32 Com_HashGenericFast (const char *name, const int hashSize)
118 {
119 uint32 hashValue;
120 int ch, i;
121
122 for (i=0, hashValue=0 ; *name ; i++) {
123 ch = *(name++);
124 hashValue = hashValue * 33 + ch;
125 }
126
127 return (hashValue + (hashValue >> 5)) & (hashSize-1);
128 }
129
130 /*
131 ============================================================================
132
133 CLIENT / SERVER INTERACTIONS
134
135 ============================================================================
136 */
137
138 static int com_redirectTarget;
139 static char *com_redirectBuffer;
140 static int com_redirectBufferSize;
141 static void (*com_redirectFlush) (int target, char *buffer);
142
143 /*
144 ================
145 Com_BeginRedirect
146 ================
147 */
Com_BeginRedirect(int target,char * buffer,int bufferSize,void (* flush)(int target,char * buffer))148 void Com_BeginRedirect (int target, char *buffer, int bufferSize, void (*flush)(int target, char *buffer))
149 {
150 if (!target || !buffer || !bufferSize || !flush)
151 return;
152
153 com_redirectTarget = target;
154 com_redirectBuffer = buffer;
155 com_redirectBufferSize = bufferSize;
156 com_redirectFlush = flush;
157
158 *com_redirectBuffer = 0;
159 }
160
161
162 /*
163 ================
164 Com_EndRedirect
165 ================
166 */
Com_EndRedirect(void)167 void Com_EndRedirect (void)
168 {
169 com_redirectFlush (com_redirectTarget, com_redirectBuffer);
170
171 com_redirectTarget = 0;
172 com_redirectBuffer = NULL;
173 com_redirectBufferSize = 0;
174 com_redirectFlush = NULL;
175 }
176
177
178 /*
179 =============
180 Com_ConPrint
181
182 Doesn't evaluate args. Com_Printf and Com_DevPrintf use this to hand off
183 console messages to the appropriate targets.
184 =============
185 */
Com_ConPrint(comPrint_t flags,char * string)186 void Com_ConPrint (comPrint_t flags, char *string)
187 {
188 // Tallying purposes
189 if (flags & PRNT_ERROR)
190 com_numErrors++;
191 else if (flags & PRNT_WARNING)
192 com_numWarnings++;
193
194 // Message redirecting
195 if (com_redirectTarget) {
196 if ((int)(strlen (string) + strlen (com_redirectBuffer)) > com_redirectBufferSize - 1) {
197 com_redirectFlush (com_redirectTarget, com_redirectBuffer);
198 *com_redirectBuffer = 0;
199 }
200 strcat (com_redirectBuffer, string);
201 return;
202 }
203
204 #ifndef DEDICATED_ONLY
205 // Print to the client console
206 if (dedicated && !dedicated->intVal)
207 CL_ConsolePrintf (flags, string);
208 #endif
209
210 // Also echo to debugging console
211 Sys_Print (string);
212
213 // Logfile
214 if (logfile && logfile->intVal) {
215 char name[MAX_QPATH];
216
217 if (!com_logFile) {
218 Q_snprintfz (name, sizeof (name), "%s/console.log", FS_Gamedir ());
219 if (logfile->intVal > 2)
220 com_logFile = fopen (name, "a");
221 else
222 com_logFile = fopen (name, "w");
223 }
224 if (com_logFile)
225 fprintf (com_logFile, "%s", string);
226 if (logfile->intVal > 1)
227 fflush (com_logFile); // force it to save every time
228 }
229 }
230
231
232 /*
233 =============
234 Com_Printf
235
236 Both client and server can use this, and it will output to the apropriate place.
237 =============
238 */
Com_Printf(comPrint_t flags,char * fmt,...)239 void Com_Printf (comPrint_t flags, char *fmt, ...)
240 {
241 va_list argptr;
242 char msg[MAX_COMPRINT];
243
244 // Evaluate args
245 va_start (argptr, fmt);
246 vsnprintf (msg, sizeof (msg), fmt, argptr);
247 va_end (argptr);
248
249 // Print
250 Com_ConPrint (flags, msg);
251 }
252
253
254 /*
255 =============
256 Com_DevPrintf
257
258 Hands off to Com_ConPrint if developer is on
259 =============
260 */
Com_DevPrintf(comPrint_t flags,char * fmt,...)261 void Com_DevPrintf (comPrint_t flags, char *fmt, ...)
262 {
263 va_list argptr;
264 char msg[MAX_COMPRINT];
265
266 if (!developer->intVal)
267 return;
268
269 // Evaluate args
270 va_start (argptr, fmt);
271 vsnprintf (msg, sizeof (msg), fmt, argptr);
272 va_end (argptr);
273
274 // Print
275 Com_ConPrint (flags, msg);
276 }
277
278
279 /*
280 =============
281 Com_Error
282
283 Both client and server can use this, and it will
284 do the apropriate things.
285 =============
286 */
Com_Error(comError_t code,char * fmt,...)287 void Com_Error (comError_t code, char *fmt, ...)
288 {
289 va_list argptr;
290 static char msg[MAX_COMPRINT];
291 static qBool recursive = qFalse;
292
293 if (recursive)
294 Sys_Error ("Com_Error: Recursive error after: %s", msg);
295 recursive = qTrue;
296
297 // Evaluate args
298 va_start (argptr, fmt);
299 vsnprintf (msg, sizeof (msg), fmt, argptr);
300 va_end (argptr);
301
302 switch (code) {
303 case ERR_DISCONNECT:
304 #ifndef DEDICATED_ONLY
305 if (!dedicated->intVal)
306 CL_Disconnect (qTrue);
307 #endif
308
309 recursive = qFalse;
310 if (!com_initialized)
311 Sys_Error ("%s", msg);
312 longjmp (abortframe, -1);
313 break;
314
315 case ERR_DROP:
316 Com_Printf (0, "********************\nERROR: %s\n********************\n", msg);
317
318 SV_ServerShutdown (Q_VarArgs ("Server exited: %s\n", msg), qFalse, qFalse);
319 #ifndef DEDICATED_ONLY
320 if (!dedicated->intVal)
321 CL_Disconnect (qTrue);
322 #endif
323
324 recursive = qFalse;
325 if (!com_initialized)
326 Sys_Error ("%s", msg);
327 longjmp (abortframe, -1);
328 break;
329
330 default:
331 case ERR_FATAL:
332 SV_ServerShutdown (Q_VarArgs ("Server fatal crashed: %s\n", msg), qFalse, qTrue);
333 #ifndef DEDICATED_ONLY
334 if (!dedicated->intVal)
335 CL_ClientShutdown (qTrue);
336 #endif
337 break;
338 }
339
340 if (com_logFile) {
341 fclose (com_logFile);
342 com_logFile = NULL;
343 }
344
345 Sys_Error ("%s", msg);
346 }
347
348
349 /*
350 =============
351 Com_Error_f
352
353 Just throw a fatal error to test error shutdown procedures
354 =============
355 */
Com_Error_f(void)356 void Com_Error_f (void)
357 {
358 Com_Error (ERR_FATAL, "%s", Cmd_Args ());
359 }
360
361
362 /*
363 =============
364 Com_Quit
365
366 Both client and server can use this, and it will do the apropriate things.
367 =============
368 */
Com_Quit(qBool error)369 void Com_Quit (qBool error)
370 {
371 if (Cmd_Argc () > 1)
372 SV_ServerShutdown (Q_VarArgs ("Server has shut down: %s\n", Cmd_Args ()), qFalse, error);
373 else
374 SV_ServerShutdown ("Server has shut down\n", qFalse, error);
375
376 if (com_logFile) {
377 fclose (com_logFile);
378 com_logFile = NULL;
379 }
380
381 Sys_Quit (error);
382 }
383
384
385 /*
386 =============
387 Com_Quit_f
388 =============
389 */
Com_Quit_f(void)390 static void Com_Quit_f (void)
391 {
392 Com_Quit (qFalse);
393 }
394
395 /*
396 ============================================================================
397
398 CLIENT / SERVER STATES
399
400 Non-zero state values indicate initialization
401 ============================================================================
402 */
403
404 static ssState_t com_svState;
405 static caState_t com_clState;
406
407 /*
408 ==================
409 Com_ClientState
410 ==================
411 */
Com_ClientState(void)412 caState_t Com_ClientState (void)
413 {
414 return com_clState;
415 }
416
417
418 /*
419 ==================
420 Com_SetClientState
421 ==================
422 */
Com_SetClientState(caState_t state)423 void Com_SetClientState (caState_t state)
424 {
425 com_clState = state;
426 }
427
428
429 /*
430 ==================
431 Com_ServerState
432 ==================
433 */
Com_ServerState(void)434 ssState_t Com_ServerState (void)
435 {
436 return com_svState;
437 }
438
439
440 /*
441 ==================
442 Com_SetServerState
443 ==================
444 */
Com_SetServerState(ssState_t state)445 void Com_SetServerState (ssState_t state)
446 {
447 com_svState = state;
448 }
449
450 /*
451 ============================================================================
452
453 PROGRAM ARGUMENTS
454
455 ============================================================================
456 */
457
458 #define MAX_NUM_ARGVS 51
459
460 static int com_argCount;
461 static char *com_argValues[MAX_NUM_ARGVS];
462
463 /*
464 ================
465 Com_InitArgv
466 ================
467 */
Com_InitArgv(int argc,char ** argv)468 static void Com_InitArgv (int argc, char **argv)
469 {
470 int i;
471
472 if (argc >= MAX_NUM_ARGVS)
473 Com_Error (ERR_FATAL, "argc >= MAX_NUM_ARGVS");
474
475 com_argCount = argc;
476 for (i=0 ; i<argc ; i++) {
477 if (!argv[i] || strlen(argv[i])+1 >= MAX_TOKEN_CHARS)
478 com_argValues[i] = "";
479 else
480 com_argValues[i] = argv[i];
481 }
482 }
483
484
485 /*
486 ================
487 Com_ClearArgv
488 ================
489 */
Com_ClearArgv(int arg)490 static void Com_ClearArgv (int arg)
491 {
492 if (arg < 0 || arg >= com_argCount || !com_argValues[arg])
493 return;
494
495 com_argValues[arg] = "";
496 }
497
498
499 /*
500 ================
501 Com_Argc
502 ================
503 */
Com_Argc(void)504 static int Com_Argc (void)
505 {
506 return com_argCount;
507 }
508
509
510 /*
511 ================
512 Com_Argv
513 ================
514 */
Com_Argv(int arg)515 static char *Com_Argv (int arg)
516 {
517 if (arg < 0 || arg >= com_argCount || !com_argValues[arg])
518 return "";
519
520 return com_argValues[arg];
521 }
522
523
524 /*
525 ===============
526 Com_AddEarlyCommands
527
528 Adds command line parameters as script statements
529 Commands lead with a +, and continue until another +
530
531 Set commands are added early, so they are guaranteed to be set before
532 the client and server initialize for the first time.
533
534 Other commands are added late, after all initialization is complete.
535 ===============
536 */
Com_AddEarlyCommands(qBool clear)537 static void Com_AddEarlyCommands (qBool clear)
538 {
539 int i;
540 char *s;
541
542 for (i=0 ; i<Com_Argc () ; i++) {
543 s = Com_Argv (i);
544 if (Q_stricmp (s, "+set"))
545 continue;
546
547 Cbuf_AddText (Q_VarArgs ("set %s %s\n", Com_Argv(i+1), Com_Argv(i+2)));
548 if (clear) {
549 Com_ClearArgv (i);
550 Com_ClearArgv (i+1);
551 Com_ClearArgv (i+2);
552 }
553 i+=2;
554 }
555 }
556
557
558 /*
559 =================
560 Com_AddLateCommands
561
562 Adds command line parameters as script statements
563 Commands lead with a + and continue until another + or -
564 egl +map amlev1 +
565
566 Returns qTrue if any late commands were added, which
567 will keep the demoloop from immediately starting
568 =================
569 */
Com_AddLateCommands(void)570 static qBool Com_AddLateCommands (void)
571 {
572 int i, j;
573 int s;
574 char *text, *build, c;
575 int argc;
576 qBool ret;
577
578 // Build the combined string to parse from
579 s = 0;
580 argc = Com_Argc ();
581 for (i=1 ; i<argc ; i++)
582 s += strlen (Com_Argv (i)) + 1;
583
584 if (!s)
585 return qFalse;
586
587 text = Mem_Alloc (s+1);
588 text[0] = 0;
589 for (i=1 ; i<argc ; i++) {
590 strcat (text, Com_Argv (i));
591 if (i != argc-1)
592 strcat (text, " ");
593 }
594
595 // Pull out the commands
596 build = Mem_Alloc (s+1);
597 build[0] = 0;
598
599 for (i=0 ; i<s-1 ; i++) {
600 if (text[i] == '+') {
601 i++;
602
603 // Skip '+'/'-'/'\0'
604 for (j=i ; text[j] != '+' && text[j] != '-' && text[j] != '\0' ; j++) ;
605
606 c = text[j];
607 text[j] = 0;
608
609 strcat (build, text+i);
610 strcat (build, "\n");
611 text[j] = c;
612 i = j-1;
613 }
614 }
615
616 ret = (build[0] != 0);
617 if (ret)
618 Cbuf_AddText (build);
619
620 Mem_Free (text);
621 Mem_Free (build);
622
623 return ret;
624 }
625
626 /*
627 ============================================================================
628
629 INITIALIZATION / FRAME PROCESSING
630
631 ============================================================================
632 */
633
634 /*
635 =================
636 Com_Init
637 =================
638 */
Com_Init(int argc,char ** argv)639 void Com_Init (int argc, char **argv)
640 {
641 uint32 initTime;
642 char *verString;
643
644 Swap_Init ();
645
646 initTime = Sys_UMilliseconds ();
647 if (setjmp (abortframe))
648 Sys_Error ("Error during initialization");
649
650 seedMT ((unsigned long)time(0));
651
652 // Memory init
653 Mem_Init ();
654 com_aliasSysPool = Mem_CreatePool ("Common: Alias system");
655 com_cmdSysPool = Mem_CreatePool ("Common: Command system");
656 com_cmodelSysPool = Mem_CreatePool ("Common: Collision model");
657 com_cvarSysPool = Mem_CreatePool ("Common: Cvar system");
658 com_fileSysPool = Mem_CreatePool ("Common: File system");
659 com_genericPool = Mem_CreatePool ("Generic");
660
661 // Prepare enough of the subsystems to handle cvar and command buffer management
662 Com_InitArgv (argc, argv);
663
664 Cmd_Init ();
665 Swap_Init ();
666 Cbuf_Init ();
667 Alias_Init ();
668 Cvar_Init ();
669 #ifndef DEDICATED_ONLY
670 Key_Init ();
671 #endif
672
673 // Init information variables
674 verString = Q_VarArgs ("EGL v%s: %s %s-%s", EGL_VERSTR, __DATE__, BUILDSTRING, CPUSTRING);
675 Cvar_Register ("cl_version", verString, CVAR_READONLY);
676 Cvar_Register ("version", verString, CVAR_SERVERINFO|CVAR_READONLY);
677 Cvar_Register ("vid_ref", verString, CVAR_READONLY);
678
679 /*
680 ** we need to add the early commands twice, because a basedir or cddir needs to be set before execing
681 ** config files, but we want other parms to override the settings of the config files
682 */
683 Com_AddEarlyCommands (qFalse);
684 Cbuf_Execute ();
685
686 #ifdef DEDICATED_ONLY
687 dedicated = Cvar_Register ("dedicated", "1", CVAR_READONLY);
688 #else
689 dedicated = Cvar_Register ("dedicated", "0", CVAR_READONLY);
690 #endif
691
692 developer = Cvar_Register ("developer", "0", 0);
693 cg_developer = Cvar_Register ("cg_developer", "0", 0);
694 fs_developer = Cvar_Register ("fs_developer", "0", 0);
695
696 Sys_Init ();
697
698 #ifndef DEDICATED_ONLY
699 if (!dedicated->intVal)
700 CL_ConsoleInit ();
701 #endif
702
703 Com_Printf (0, "========= Common Initialization ========\n");
704 Com_Printf (0, "EGL v%s by Echon\nhttp://egl.quakedev.com/\n", EGL_VERSTR);
705 Com_Printf (0, "Compiled: "__DATE__" @ "__TIME__"\n");
706 Com_Printf (0, "----------------------------------------\n");
707
708 FS_Init ();
709
710 Com_Printf (0, "\n");
711
712 #ifndef DEDICATED_ONLY
713 if (!dedicated->intVal) {
714 Cbuf_AddText ("exec default.cfg\n");
715 Cbuf_AddText ("exec config.cfg\n");
716 Cbuf_AddText ("exec eglcfg.cfg\n");
717 }
718 #endif
719
720 Com_AddEarlyCommands (qTrue);
721 Cbuf_Execute ();
722
723 // Init commands and vars
724 Mem_Register ();
725
726 #ifdef _DEBUG
727 Cmd_AddCommand ("error", Com_Error_f, "Error out with a message");
728 #endif
729
730 timescale = Cvar_Register ("timescale", "1", CVAR_CHEAT);
731 fixedtime = Cvar_Register ("fixedtime", "0", CVAR_CHEAT);
732 logfile = Cvar_Register ("logfile", "0", 0);
733
734 #ifndef DEDICATED_ONLY
735 if (dedicated->intVal) {
736 #endif
737 Cmd_AddCommand ("quit", Com_Quit_f, "Exits");
738 Cmd_AddCommand ("exit", Com_Quit_f, "Exits");
739 #ifndef DEDICATED_ONLY
740 }
741 else {
742 FS_ExecAutoexec ();
743 Cbuf_Execute ();
744 }
745 #endif
746
747 // Init the rest of the sub-systems
748 NET_Init ();
749 Netchan_Init ();
750
751 SV_ServerInit ();
752
753 #ifndef DEDICATED_ONLY
754 if (!dedicated->intVal) {
755 Sys_ShowConsole (0, qFalse);
756 CL_ClientInit ();
757 }
758 #endif
759
760 // Add + commands from command line
761 if (!Com_AddLateCommands ()) {
762 #ifndef DEDICATED_ONLY
763 if (dedicated->intVal)
764 #endif
765 Cbuf_AddText ("dedicated_start\n");
766
767 Cbuf_Execute ();
768 }
769
770 #ifndef DEDICATED_ONLY
771 if (!dedicated->intVal)
772 SCR_EndLoadingPlaque ();
773 #endif
774
775 // Check memory integrity
776 Mem_CheckGlobalIntegrity ();
777
778 // Touch memory
779 Mem_TouchGlobal ();
780
781 com_initialized = qTrue;
782
783 Com_Printf (0, "\nCOMMON - %i error(s), %i warning(s)\n", com_numErrors, com_numWarnings);
784 Com_Printf (0, "====== Common Initialized %6ums =====\n\n", Sys_UMilliseconds()-initTime);
785 }
786
787
788 /*
789 =================
790 Com_Frame
791 =================
792 */
Com_Frame(int msec)793 void __fastcall Com_Frame (int msec)
794 {
795 char *conInput;
796
797 if (setjmp (abortframe))
798 return; // an ERR_DROP was thrown
799
800 if (fixedtime->floatVal)
801 msec = fixedtime->floatVal;
802 else if (timescale->floatVal) {
803 msec *= timescale->floatVal;
804 if (msec < 1)
805 msec = 1;
806 }
807
808 // Print trace statistics if desired
809 CM_PrintStats ();
810
811 // Pump the message loop
812 Sys_SendKeyEvents ();
813
814 // Command console input
815 conInput = Sys_ConsoleInput ();
816 if (conInput)
817 Cbuf_AddText (conInput);
818 Cbuf_Execute ();
819
820 // Update server
821 SV_Frame (msec);
822
823 #ifndef DEDICATED_ONLY
824 // Update client
825 if (!dedicated->intVal)
826 CL_Frame (msec);
827 #endif
828 }
829
830
831 /*
832 =================
833 Com_Shutdown
834 =================
835 */
Com_Shutdown(void)836 void Com_Shutdown (void)
837 {
838 NET_Shutdown ();
839 }
840