]> git.xonotic.org Git - xonotic/darkplaces.git/blob - common.c
(Round 7) host_cmd.c is no more. Last remaining cmds/cvars moved.
[xonotic/darkplaces.git] / common.c
1 /*
2 Copyright (C) 1996-1997 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 // common.c -- misc functions used in client and server
21
22 #include <stdlib.h>
23 #include <fcntl.h>
24 #ifndef WIN32
25 #include <unistd.h>
26 #endif
27
28 #include "quakedef.h"
29 #include "utf8lib.h"
30
31 cvar_t registered = {CVAR_CLIENT | CVAR_SERVER, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
32 cvar_t cmdline = {CVAR_CLIENT | CVAR_SERVER, "cmdline","0", "contains commandline the engine was launched with"};
33
34 // FIXME: Find a better place for these.
35 cvar_t cl_playermodel = {CVAR_CLIENT | CVAR_SERVER | CVAR_USERINFO | CVAR_SAVE, "playermodel", "", "current player model in Nexuiz/Xonotic"};
36 cvar_t cl_playerskin = {CVAR_CLIENT | CVAR_SERVER | CVAR_USERINFO | CVAR_SAVE, "playerskin", "", "current player skin in Nexuiz/Xonotic"};
37
38 char com_token[MAX_INPUTLINE];
39
40 gamemode_t gamemode;
41 const char *gamename;
42 const char *gamenetworkfiltername; // same as gamename currently but with _ in place of spaces so that "getservers" packets parse correctly (this also means the 
43 const char *gamedirname1;
44 const char *gamedirname2;
45 const char *gamescreenshotname;
46 const char *gameuserdirname;
47 char com_modname[MAX_OSPATH] = "";
48
49 //===========================================================================
50
51 void SZ_Clear (sizebuf_t *buf)
52 {
53         buf->cursize = 0;
54 }
55
56 unsigned char *SZ_GetSpace (sizebuf_t *buf, int length)
57 {
58         unsigned char *data;
59
60         if (buf->cursize + length > buf->maxsize)
61         {
62                 if (!buf->allowoverflow)
63                         Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
64
65                 if (length > buf->maxsize)
66                         Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
67
68                 buf->overflowed = true;
69                 Con_Print("SZ_GetSpace: overflow\n");
70                 SZ_Clear (buf);
71         }
72
73         data = buf->data + buf->cursize;
74         buf->cursize += length;
75
76         return data;
77 }
78
79 void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
80 {
81         memcpy (SZ_GetSpace(buf,length),data,length);
82 }
83
84 // LadyHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
85 // attention, it has been eradicated from here, its only (former) use in
86 // all of darkplaces.
87
88 static const char *hexchar = "0123456789ABCDEF";
89 void Com_HexDumpToConsole(const unsigned char *data, int size)
90 {
91         int i, j, n;
92         char text[1024];
93         char *cur, *flushpointer;
94         const unsigned char *d;
95         cur = text;
96         flushpointer = text + 512;
97         for (i = 0;i < size;)
98         {
99                 n = 16;
100                 if (n > size - i)
101                         n = size - i;
102                 d = data + i;
103                 // print offset
104                 *cur++ = hexchar[(i >> 12) & 15];
105                 *cur++ = hexchar[(i >>  8) & 15];
106                 *cur++ = hexchar[(i >>  4) & 15];
107                 *cur++ = hexchar[(i >>  0) & 15];
108                 *cur++ = ':';
109                 // print hex
110                 for (j = 0;j < 16;j++)
111                 {
112                         if (j < n)
113                         {
114                                 *cur++ = hexchar[(d[j] >> 4) & 15];
115                                 *cur++ = hexchar[(d[j] >> 0) & 15];
116                         }
117                         else
118                         {
119                                 *cur++ = ' ';
120                                 *cur++ = ' ';
121                         }
122                         if ((j & 3) == 3)
123                                 *cur++ = ' ';
124                 }
125                 // print text
126                 for (j = 0;j < 16;j++)
127                 {
128                         if (j < n)
129                         {
130                                 // color change prefix character has to be treated specially
131                                 if (d[j] == STRING_COLOR_TAG)
132                                 {
133                                         *cur++ = STRING_COLOR_TAG;
134                                         *cur++ = STRING_COLOR_TAG;
135                                 }
136                                 else if (d[j] >= (unsigned char) ' ')
137                                         *cur++ = d[j];
138                                 else
139                                         *cur++ = '.';
140                         }
141                         else
142                                 *cur++ = ' ';
143                 }
144                 *cur++ = '\n';
145                 i += n;
146                 if (cur >= flushpointer || i >= size)
147                 {
148                         *cur++ = 0;
149                         Con_Print(text);
150                         cur = text;
151                 }
152         }
153 }
154
155 void SZ_HexDumpToConsole(const sizebuf_t *buf)
156 {
157         Com_HexDumpToConsole(buf->data, buf->cursize);
158 }
159
160
161 //============================================================================
162
163 /*
164 ==============
165 COM_Wordwrap
166
167 Word wraps a string. The wordWidth function is guaranteed to be called exactly
168 once for each word in the string, so it may be stateful, no idea what that
169 would be good for any more. At the beginning of the string, it will be called
170 for the char 0 to initialize a clean state, and then once with the string " "
171 (a space) so the routine knows how long a space is.
172
173 In case no single character fits into the given width, the wordWidth function
174 must return the width of exactly one character.
175
176 Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
177
178 The sum of the return values of the processLine function will be returned.
179 ==============
180 */
181 int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL)
182 {
183         // Logic is as follows:
184         //
185         // For each word or whitespace:
186         //   Newline found? Output current line, advance to next line. This is not a continuation. Continue.
187         //   Space found? Always add it to the current line, no matter if it fits.
188         //   Word found? Check if current line + current word fits.
189         //     If it fits, append it. Continue.
190         //     If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue.
191
192         qboolean isContinuation = false;
193         float spaceWidth;
194         const char *startOfLine = string;
195         const char *cursor = string;
196         const char *end = string + length;
197         float spaceUsedInLine = 0;
198         float spaceUsedForWord;
199         int result = 0;
200         size_t wordLen;
201         size_t dummy;
202
203         dummy = 0;
204         wordWidth(passthroughCW, NULL, &dummy, -1);
205         dummy = 1;
206         spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1);
207
208         for(;;)
209         {
210                 char ch = (cursor < end) ? *cursor : 0;
211                 switch(ch)
212                 {
213                         case 0: // end of string
214                                 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
215                                 goto out;
216                         case '\n': // end of line
217                                 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
218                                 isContinuation = false;
219                                 ++cursor;
220                                 startOfLine = cursor;
221                                 break;
222                         case ' ': // space
223                                 ++cursor;
224                                 spaceUsedInLine += spaceWidth;
225                                 break;
226                         default: // word
227                                 wordLen = 1;
228                                 while(cursor + wordLen < end)
229                                 {
230                                         switch(cursor[wordLen])
231                                         {
232                                                 case 0:
233                                                 case '\n':
234                                                 case ' ':
235                                                         goto out_inner;
236                                                 default:
237                                                         ++wordLen;
238                                                         break;
239                                         }
240                                 }
241                                 out_inner:
242                                 spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line
243                                 if(wordLen < 1) // cannot happen according to current spec of wordWidth
244                                 {
245                                         wordLen = 1;
246                                         spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself
247                                 }
248                                 if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine)
249                                 {
250                                         // we can simply append it
251                                         cursor += wordLen;
252                                         spaceUsedInLine += spaceUsedForWord;
253                                 }
254                                 else
255                                 {
256                                         // output current line
257                                         result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
258                                         isContinuation = true;
259                                         startOfLine = cursor;
260                                         cursor += wordLen;
261                                         spaceUsedInLine = continuationWidth + spaceUsedForWord;
262                                 }
263                 }
264         }
265         out:
266
267         return result;
268
269 /*
270         qboolean isContinuation = false;
271         float currentWordSpace = 0;
272         const char *currentWord = 0;
273         float minReserve = 0;
274
275         float spaceUsedInLine = 0;
276         const char *currentLine = 0;
277         const char *currentLineEnd = 0;
278         float currentLineFinalWhitespace = 0;
279         const char *p;
280
281         int result = 0;
282         minReserve = charWidth(passthroughCW, 0);
283         minReserve += charWidth(passthroughCW, ' ');
284
285         if(maxWidth < continuationWidth + minReserve)
286                 maxWidth = continuationWidth + minReserve;
287
288         charWidth(passthroughCW, 0);
289
290         for(p = string; p < string + length; ++p)
291         {
292                 char c = *p;
293                 float w = charWidth(passthroughCW, c);
294
295                 if(!currentWord)
296                 {
297                         currentWord = p;
298                         currentWordSpace = 0;
299                 }
300
301                 if(!currentLine)
302                 {
303                         currentLine = p;
304                         spaceUsedInLine = isContinuation ? continuationWidth : 0;
305                         currentLineEnd = 0;
306                 }
307
308                 if(c == ' ')
309                 {
310                         // 1. I can add the word AND a space - then just append it.
311                         if(spaceUsedInLine + currentWordSpace + w <= maxWidth)
312                         {
313                                 currentLineEnd = p; // note: space not included here
314                                 currentLineFinalWhitespace = w;
315                                 spaceUsedInLine += currentWordSpace + w;
316                         }
317                         // 2. I can just add the word - then append it, output current line and go to next one.
318                         else if(spaceUsedInLine + currentWordSpace <= maxWidth)
319                         {
320                                 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
321                                 currentLine = 0;
322                                 isContinuation = true;
323                         }
324                         // 3. Otherwise, output current line and go to next one, where I can add the word.
325                         else if(continuationWidth + currentWordSpace + w <= maxWidth)
326                         {
327                                 if(currentLineEnd)
328                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
329                                 currentLine = currentWord;
330                                 spaceUsedInLine = continuationWidth + currentWordSpace + w;
331                                 currentLineEnd = p;
332                                 currentLineFinalWhitespace = w;
333                                 isContinuation = true;
334                         }
335                         // 4. We can't even do that? Then output both current and next word as new lines.
336                         else
337                         {
338                                 if(currentLineEnd)
339                                 {
340                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
341                                         isContinuation = true;
342                                 }
343                                 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
344                                 currentLine = 0;
345                                 isContinuation = true;
346                         }
347                         currentWord = 0;
348                 }
349                 else if(c == '\n')
350                 {
351                         // 1. I can add the word - then do it.
352                         if(spaceUsedInLine + currentWordSpace <= maxWidth)
353                         {
354                                 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
355                         }
356                         // 2. Otherwise, output current line, next one and make tabula rasa.
357                         else
358                         {
359                                 if(currentLineEnd)
360                                 {
361                                         processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
362                                         isContinuation = true;
363                                 }
364                                 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
365                         }
366                         currentWord = 0;
367                         currentLine = 0;
368                         isContinuation = false;
369                 }
370                 else
371                 {
372                         currentWordSpace += w;
373                         if(
374                                 spaceUsedInLine + currentWordSpace > maxWidth // can't join this line...
375                                 &&
376                                 continuationWidth + currentWordSpace > maxWidth // can't join any other line...
377                         )
378                         {
379                                 // this word cannot join ANY line...
380                                 // so output the current line...
381                                 if(currentLineEnd)
382                                 {
383                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
384                                         isContinuation = true;
385                                 }
386
387                                 // then this word's beginning...
388                                 if(isContinuation)
389                                 {
390                                         // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces
391                                         float pieceWidth = maxWidth - continuationWidth;
392                                         const char *pos = currentWord;
393                                         currentWordSpace = 0;
394
395                                         // reset the char width function to a state where no kerning occurs (start of word)
396                                         charWidth(passthroughCW, ' ');
397                                         while(pos <= p)
398                                         {
399                                                 float w = charWidth(passthroughCW, *pos);
400                                                 if(currentWordSpace + w > pieceWidth) // this piece won't fit any more
401                                                 {
402                                                         // print everything until it
403                                                         result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true);
404                                                         // go to here
405                                                         currentWord = pos;
406                                                         currentWordSpace = 0;
407                                                 }
408                                                 currentWordSpace += w;
409                                                 ++pos;
410                                         }
411                                         // now we have a currentWord that fits... set up its next line
412                                         // currentWordSpace has been set
413                                         // currentWord has been set
414                                         spaceUsedInLine = continuationWidth;
415                                         currentLine = currentWord;
416                                         currentLineEnd = 0;
417                                         isContinuation = true;
418                                 }
419                                 else
420                                 {
421                                         // we have a guarantee that it will fix (see if clause)
422                                         result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation);
423
424                                         // and use the rest of this word as new start of a line
425                                         currentWordSpace = w;
426                                         currentWord = p;
427                                         spaceUsedInLine = continuationWidth;
428                                         currentLine = p;
429                                         currentLineEnd = 0;
430                                         isContinuation = true;
431                                 }
432                         }
433                 }
434         }
435
436         if(!currentWord)
437         {
438                 currentWord = p;
439                 currentWordSpace = 0;
440         }
441
442         if(currentLine) // Same procedure as \n
443         {
444                 // Can I append the current word?
445                 if(spaceUsedInLine + currentWordSpace <= maxWidth)
446                         result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
447                 else
448                 {
449                         if(currentLineEnd)
450                         {
451                                 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
452                                 isContinuation = true;
453                         }
454                         result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
455                 }
456         }
457
458         return result;
459 */
460 }
461
462 /*
463 ==============
464 COM_ParseToken_Simple
465
466 Parse a token out of a string
467 ==============
468 */
469 int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments)
470 {
471         int len;
472         int c;
473         const char *data = *datapointer;
474
475         len = 0;
476         com_token[0] = 0;
477
478         if (!data)
479         {
480                 *datapointer = NULL;
481                 return false;
482         }
483
484 // skip whitespace
485 skipwhite:
486         // line endings:
487         // UNIX: \n
488         // Mac: \r
489         // Windows: \r\n
490         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
491         {
492                 if (*data == 0)
493                 {
494                         // end of file
495                         *datapointer = NULL;
496                         return false;
497                 }
498         }
499
500         // handle Windows line ending
501         if (data[0] == '\r' && data[1] == '\n')
502                 data++;
503
504         if (parsecomments && data[0] == '/' && data[1] == '/')
505         {
506                 // comment
507                 while (*data && *data != '\n' && *data != '\r')
508                         data++;
509                 goto skipwhite;
510         }
511         else if (parsecomments && data[0] == '/' && data[1] == '*')
512         {
513                 // comment
514                 data++;
515                 while (*data && (data[0] != '*' || data[1] != '/'))
516                         data++;
517                 if (*data)
518                         data++;
519                 if (*data)
520                         data++;
521                 goto skipwhite;
522         }
523         else if (*data == '\"')
524         {
525                 // quoted string
526                 for (data++;*data && *data != '\"';data++)
527                 {
528                         c = *data;
529                         if (*data == '\\' && parsebackslash)
530                         {
531                                 data++;
532                                 c = *data;
533                                 if (c == 'n')
534                                         c = '\n';
535                                 else if (c == 't')
536                                         c = '\t';
537                         }
538                         if (len < (int)sizeof(com_token) - 1)
539                                 com_token[len++] = c;
540                 }
541                 com_token[len] = 0;
542                 if (*data == '\"')
543                         data++;
544                 *datapointer = data;
545                 return true;
546         }
547         else if (*data == '\r')
548         {
549                 // translate Mac line ending to UNIX
550                 com_token[len++] = '\n';data++;
551                 com_token[len] = 0;
552                 *datapointer = data;
553                 return true;
554         }
555         else if (*data == '\n')
556         {
557                 // single character
558                 com_token[len++] = *data++;
559                 com_token[len] = 0;
560                 *datapointer = data;
561                 return true;
562         }
563         else
564         {
565                 // regular word
566                 for (;!ISWHITESPACE(*data);data++)
567                         if (len < (int)sizeof(com_token) - 1)
568                                 com_token[len++] = *data;
569                 com_token[len] = 0;
570                 *datapointer = data;
571                 return true;
572         }
573 }
574
575 /*
576 ==============
577 COM_ParseToken_QuakeC
578
579 Parse a token out of a string
580 ==============
581 */
582 int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline)
583 {
584         int len;
585         int c;
586         const char *data = *datapointer;
587
588         len = 0;
589         com_token[0] = 0;
590
591         if (!data)
592         {
593                 *datapointer = NULL;
594                 return false;
595         }
596
597 // skip whitespace
598 skipwhite:
599         // line endings:
600         // UNIX: \n
601         // Mac: \r
602         // Windows: \r\n
603         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
604         {
605                 if (*data == 0)
606                 {
607                         // end of file
608                         *datapointer = NULL;
609                         return false;
610                 }
611         }
612
613         // handle Windows line ending
614         if (data[0] == '\r' && data[1] == '\n')
615                 data++;
616
617         if (data[0] == '/' && data[1] == '/')
618         {
619                 // comment
620                 while (*data && *data != '\n' && *data != '\r')
621                         data++;
622                 goto skipwhite;
623         }
624         else if (data[0] == '/' && data[1] == '*')
625         {
626                 // comment
627                 data++;
628                 while (*data && (data[0] != '*' || data[1] != '/'))
629                         data++;
630                 if (*data)
631                         data++;
632                 if (*data)
633                         data++;
634                 goto skipwhite;
635         }
636         else if (*data == '\"' || *data == '\'')
637         {
638                 // quoted string
639                 char quote = *data;
640                 for (data++;*data && *data != quote;data++)
641                 {
642                         c = *data;
643                         if (*data == '\\')
644                         {
645                                 data++;
646                                 c = *data;
647                                 if (c == 'n')
648                                         c = '\n';
649                                 else if (c == 't')
650                                         c = '\t';
651                         }
652                         if (len < (int)sizeof(com_token) - 1)
653                                 com_token[len++] = c;
654                 }
655                 com_token[len] = 0;
656                 if (*data == quote)
657                         data++;
658                 *datapointer = data;
659                 return true;
660         }
661         else if (*data == '\r')
662         {
663                 // translate Mac line ending to UNIX
664                 com_token[len++] = '\n';data++;
665                 com_token[len] = 0;
666                 *datapointer = data;
667                 return true;
668         }
669         else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
670         {
671                 // single character
672                 com_token[len++] = *data++;
673                 com_token[len] = 0;
674                 *datapointer = data;
675                 return true;
676         }
677         else
678         {
679                 // regular word
680                 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
681                         if (len < (int)sizeof(com_token) - 1)
682                                 com_token[len++] = *data;
683                 com_token[len] = 0;
684                 *datapointer = data;
685                 return true;
686         }
687 }
688
689 /*
690 ==============
691 COM_ParseToken_VM_Tokenize
692
693 Parse a token out of a string
694 ==============
695 */
696 int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline)
697 {
698         int len;
699         int c;
700         const char *data = *datapointer;
701
702         len = 0;
703         com_token[0] = 0;
704
705         if (!data)
706         {
707                 *datapointer = NULL;
708                 return false;
709         }
710
711 // skip whitespace
712 skipwhite:
713         // line endings:
714         // UNIX: \n
715         // Mac: \r
716         // Windows: \r\n
717         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
718         {
719                 if (*data == 0)
720                 {
721                         // end of file
722                         *datapointer = NULL;
723                         return false;
724                 }
725         }
726
727         // handle Windows line ending
728         if (data[0] == '\r' && data[1] == '\n')
729                 data++;
730
731         if (data[0] == '/' && data[1] == '/')
732         {
733                 // comment
734                 while (*data && *data != '\n' && *data != '\r')
735                         data++;
736                 goto skipwhite;
737         }
738         else if (data[0] == '/' && data[1] == '*')
739         {
740                 // comment
741                 data++;
742                 while (*data && (data[0] != '*' || data[1] != '/'))
743                         data++;
744                 if (*data)
745                         data++;
746                 if (*data)
747                         data++;
748                 goto skipwhite;
749         }
750         else if (*data == '\"' || *data == '\'')
751         {
752                 char quote = *data;
753                 // quoted string
754                 for (data++;*data && *data != quote;data++)
755                 {
756                         c = *data;
757                         if (*data == '\\')
758                         {
759                                 data++;
760                                 c = *data;
761                                 if (c == 'n')
762                                         c = '\n';
763                                 else if (c == 't')
764                                         c = '\t';
765                         }
766                         if (len < (int)sizeof(com_token) - 1)
767                                 com_token[len++] = c;
768                 }
769                 com_token[len] = 0;
770                 if (*data == quote)
771                         data++;
772                 *datapointer = data;
773                 return true;
774         }
775         else if (*data == '\r')
776         {
777                 // translate Mac line ending to UNIX
778                 com_token[len++] = '\n';data++;
779                 com_token[len] = 0;
780                 *datapointer = data;
781                 return true;
782         }
783         else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
784         {
785                 // single character
786                 com_token[len++] = *data++;
787                 com_token[len] = 0;
788                 *datapointer = data;
789                 return true;
790         }
791         else
792         {
793                 // regular word
794                 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
795                         if (len < (int)sizeof(com_token) - 1)
796                                 com_token[len++] = *data;
797                 com_token[len] = 0;
798                 *datapointer = data;
799                 return true;
800         }
801 }
802
803 /*
804 ==============
805 COM_ParseToken_Console
806
807 Parse a token out of a string, behaving like the qwcl console
808 ==============
809 */
810 int COM_ParseToken_Console(const char **datapointer)
811 {
812         int len;
813         const char *data = *datapointer;
814
815         len = 0;
816         com_token[0] = 0;
817
818         if (!data)
819         {
820                 *datapointer = NULL;
821                 return false;
822         }
823
824 // skip whitespace
825 skipwhite:
826         for (;ISWHITESPACE(*data);data++)
827         {
828                 if (*data == 0)
829                 {
830                         // end of file
831                         *datapointer = NULL;
832                         return false;
833                 }
834         }
835
836         if (*data == '/' && data[1] == '/')
837         {
838                 // comment
839                 while (*data && *data != '\n' && *data != '\r')
840                         data++;
841                 goto skipwhite;
842         }
843         else if (*data == '\"')
844         {
845                 // quoted string
846                 for (data++;*data && *data != '\"';data++)
847                 {
848                         // allow escaped " and \ case
849                         if (*data == '\\' && (data[1] == '\"' || data[1] == '\\'))
850                                 data++;
851                         if (len < (int)sizeof(com_token) - 1)
852                                 com_token[len++] = *data;
853                 }
854                 com_token[len] = 0;
855                 if (*data == '\"')
856                         data++;
857                 *datapointer = data;
858         }
859         else
860         {
861                 // regular word
862                 for (;!ISWHITESPACE(*data);data++)
863                         if (len < (int)sizeof(com_token) - 1)
864                                 com_token[len++] = *data;
865                 com_token[len] = 0;
866                 *datapointer = data;
867         }
868
869         return true;
870 }
871
872
873 /*
874 ================
875 COM_CheckParm
876
877 Returns the position (1 to argc-1) in the program's argument list
878 where the given parameter apears, or 0 if not present
879 ================
880 */
881 int COM_CheckParm (const char *parm)
882 {
883         int i;
884
885         for (i=1 ; i<sys.argc ; i++)
886         {
887                 if (!sys.argv[i])
888                         continue;               // NEXTSTEP sometimes clears appkit vars.
889                 if (!strcmp (parm,sys.argv[i]))
890                         return i;
891         }
892
893         return 0;
894 }
895
896 //===========================================================================
897
898 // Game mods
899
900 gamemode_t com_startupgamemode;
901 gamemode_t com_startupgamegroup;
902
903 typedef struct gamemode_info_s
904 {
905         gamemode_t mode; // this gamemode
906         gamemode_t group; // different games with same group can switch automatically when gamedirs change
907         const char* prog_name; // not null
908         const char* cmdline; // not null
909         const char* gamename; // not null
910         const char*     gamenetworkfiltername; // not null
911         const char* gamedirname1; // not null
912         const char* gamedirname2; // null
913         const char* gamescreenshotname; // not nul
914         const char* gameuserdirname; // not null
915 } gamemode_info_t;
916
917 static const gamemode_info_t gamemode_info [GAME_COUNT] =
918 {// game                                                basegame                                        prog_name                               cmdline                                         gamename                                        gamenetworkfilername            basegame        modgame                 screenshot                      userdir                                    // commandline option
919 { GAME_NORMAL,                                  GAME_NORMAL,                            "",                                             "-quake",                                       "DarkPlaces-Quake",                     "DarkPlaces-Quake",                     "id1",          NULL,                   "dp",                           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -quake runs the game Quake (default)
920 { GAME_HIPNOTIC,                                GAME_NORMAL,                            "hipnotic",                             "-hipnotic",                            "Darkplaces-Hipnotic",          "Darkplaces-Hipnotic",          "id1",          "hipnotic",             "dp",                           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -hipnotic runs Quake mission pack 1: The Scourge of Armagon
921 { GAME_ROGUE,                                   GAME_NORMAL,                            "rogue",                                "-rogue",                                       "Darkplaces-Rogue",                     "Darkplaces-Rogue",                     "id1",          "rogue",                "dp",                           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -rogue runs Quake mission pack 2: The Dissolution of Eternity
922 { GAME_NEHAHRA,                                 GAME_NORMAL,                            "nehahra",                              "-nehahra",                                     "DarkPlaces-Nehahra",           "DarkPlaces-Nehahra",           "id1",          "nehahra",              "dp",                           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -nehahra runs The Seal of Nehahra movie and game
923 { GAME_QUOTH,                                   GAME_NORMAL,                            "quoth",                                "-quoth",                                       "Darkplaces-Quoth",                     "Darkplaces-Quoth",                     "id1",          "quoth",                "dp",                           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -quoth runs the Quoth mod for playing community maps made for it
924 { GAME_NEXUIZ,                                  GAME_NEXUIZ,                            "nexuiz",                               "-nexuiz",                                      "Nexuiz",                                       "Nexuiz",                                       "data",         NULL,                   "nexuiz",                       "nexuiz"                                }, // COMMANDLINEOPTION: Game: -nexuiz runs the multiplayer game Nexuiz
925 { GAME_XONOTIC,                                 GAME_XONOTIC,                           "xonotic",                              "-xonotic",                                     "Xonotic",                                      "Xonotic",                                      "data",         NULL,                   "xonotic",                      "xonotic"                               }, // COMMANDLINEOPTION: Game: -xonotic runs the multiplayer game Xonotic
926 { GAME_TRANSFUSION,                             GAME_TRANSFUSION,                       "transfusion",                  "-transfusion",                         "Transfusion",                          "Transfusion",                          "basetf",       NULL,                   "transfusion",          "transfusion"                   }, // COMMANDLINEOPTION: Game: -transfusion runs Transfusion (the recreation of Blood in Quake)
927 { GAME_GOODVSBAD2,                              GAME_GOODVSBAD2,                        "gvb2",                                 "-goodvsbad2",                          "GoodVs.Bad2",                          "GoodVs.Bad2",                          "rts",          NULL,                   "gvb2",                         "gvb2"                                  }, // COMMANDLINEOPTION: Game: -goodvsbad2 runs the psychadelic RTS FPS game Good Vs Bad 2
928 { GAME_TEU,                                             GAME_TEU,                                       "teu",                                  "-teu",                                         "TheEvilUnleashed",                     "TheEvilUnleashed",                     "baseteu",      NULL,                   "teu",                          "teu"                                   }, // COMMANDLINEOPTION: Game: -teu runs The Evil Unleashed (this option is obsolete as they are not using darkplaces)
929 { GAME_BATTLEMECH,                              GAME_BATTLEMECH,                        "battlemech",                   "-battlemech",                          "Battlemech",                           "Battlemech",                           "base",         NULL,                   "battlemech",           "battlemech"                    }, // COMMANDLINEOPTION: Game: -battlemech runs the multiplayer topdown deathmatch game BattleMech
930 { GAME_ZYMOTIC,                                 GAME_ZYMOTIC,                           "zymotic",                              "-zymotic",                                     "Zymotic",                                      "Zymotic",                                      "basezym",      NULL,                   "zymotic",                      "zymotic"                               }, // COMMANDLINEOPTION: Game: -zymotic runs the singleplayer game Zymotic
931 { GAME_SETHERAL,                                GAME_SETHERAL,                          "setheral",                             "-setheral",                            "Setheral",                                     "Setheral",                                     "data",         NULL,                   "setheral",                     "setheral"                              }, // COMMANDLINEOPTION: Game: -setheral runs the multiplayer game Setheral
932 { GAME_TENEBRAE,                                GAME_NORMAL,                            "tenebrae",                             "-tenebrae",                            "DarkPlaces-Tenebrae",          "DarkPlaces-Tenebrae",          "id1",          "tenebrae",             "dp",                           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -tenebrae runs the graphics test mod known as Tenebrae (some features not implemented)
933 { GAME_NEOTERIC,                                GAME_NORMAL,                            "neoteric",                             "-neoteric",                            "Neoteric",                                     "Neoteric",                                     "id1",          "neobase",              "neo",                          "darkplaces"                    }, // COMMANDLINEOPTION: Game: -neoteric runs the game Neoteric
934 { GAME_OPENQUARTZ,                              GAME_NORMAL,                            "openquartz",                   "-openquartz",                          "OpenQuartz",                           "OpenQuartz",                           "id1",          NULL,                   "openquartz",           "darkplaces"                    }, // COMMANDLINEOPTION: Game: -openquartz runs the game OpenQuartz, a standalone GPL replacement of the quake content
935 { GAME_PRYDON,                                  GAME_NORMAL,                            "prydon",                               "-prydon",                                      "PrydonGate",                           "PrydonGate",                           "id1",          "prydon",               "prydon",                       "darkplaces"                    }, // COMMANDLINEOPTION: Game: -prydon runs the topdown point and click action-RPG Prydon Gate
936 { GAME_DELUXEQUAKE,                             GAME_DELUXEQUAKE,                       "dq",                                   "-dq",                                          "Deluxe Quake",                         "Deluxe_Quake",                         "basedq",       "extradq",              "basedq",                       "dq"                                    }, // COMMANDLINEOPTION: Game: -dq runs the game Deluxe Quake
937 { GAME_THEHUNTED,                               GAME_THEHUNTED,                         "thehunted",                    "-thehunted",                           "The Hunted",                           "The_Hunted",                           "thdata",       NULL,                   "th",                           "thehunted"                             }, // COMMANDLINEOPTION: Game: -thehunted runs the game The Hunted
938 { GAME_DEFEATINDETAIL2,                 GAME_DEFEATINDETAIL2,           "did2",                                 "-did2",                                        "Defeat In Detail 2",           "Defeat_In_Detail_2",           "data",         NULL,                   "did2_",                        "did2"                                  }, // COMMANDLINEOPTION: Game: -did2 runs the game Defeat In Detail 2
939 { GAME_DARSANA,                                 GAME_DARSANA,                           "darsana",                              "-darsana",                                     "Darsana",                                      "Darsana",                                      "ddata",        NULL,                   "darsana",                      "darsana"                               }, // COMMANDLINEOPTION: Game: -darsana runs the game Darsana
940 { GAME_CONTAGIONTHEORY,                 GAME_CONTAGIONTHEORY,           "contagiontheory",              "-contagiontheory",                     "Contagion Theory",                     "Contagion_Theory",                     "ctdata",       NULL,                   "ct",                           "contagiontheory"               }, // COMMANDLINEOPTION: Game: -contagiontheory runs the game Contagion Theory
941 { GAME_EDU2P,                                   GAME_EDU2P,                                     "edu2p",                                "-edu2p",                                       "EDU2 Prototype",                       "EDU2_Prototype",                       "id1",          "edu2",                 "edu2_p",                       "edu2prototype"                 }, // COMMANDLINEOPTION: Game: -edu2p runs the game Edu2 prototype
942 { GAME_PROPHECY,                                GAME_PROPHECY,                          "prophecy",                             "-prophecy",                            "Prophecy",                                     "Prophecy",                                     "gamedata",     NULL,                   "phcy",                         "prophecy"                              }, // COMMANDLINEOPTION: Game: -prophecy runs the game Prophecy
943 { GAME_BLOODOMNICIDE,                   GAME_BLOODOMNICIDE,                     "omnicide",                             "-omnicide",                            "Blood Omnicide",                       "Blood_Omnicide",                       "kain",         NULL,                   "omnicide",                     "omnicide"                              }, // COMMANDLINEOPTION: Game: -omnicide runs the game Blood Omnicide
944 { GAME_STEELSTORM,                              GAME_STEELSTORM,                        "steelstorm",                   "-steelstorm",                          "Steel-Storm",                          "Steel-Storm",                          "gamedata",     NULL,                   "ss",                           "steelstorm"                    }, // COMMANDLINEOPTION: Game: -steelstorm runs the game Steel Storm
945 { GAME_STEELSTORM2,                             GAME_STEELSTORM2,                       "steelstorm2",                  "-steelstorm2",                         "Steel Storm 2",                        "Steel_Storm_2",                        "gamedata",     NULL,                   "ss2",                          "steelstorm2"                   }, // COMMANDLINEOPTION: Game: -steelstorm2 runs the game Steel Storm 2
946 { GAME_SSAMMO,                                  GAME_SSAMMO,                            "steelstorm-ammo",              "-steelstormammo",                      "Steel Storm A.M.M.O.",         "Steel_Storm_A.M.M.O.",         "gamedata", NULL,                       "ssammo",                       "steelstorm-ammo"               }, // COMMANDLINEOPTION: Game: -steelstormammo runs the game Steel Storm A.M.M.O.
947 { GAME_STEELSTORMREVENANTS,             GAME_STEELSTORMREVENANTS,       "steelstorm-revenants", "-steelstormrev",                       "Steel Storm: Revenants",       "Steel_Storm_Revenants",        "base", NULL,                           "ssrev",                        "steelstorm-revenants"  }, // COMMANDLINEOPTION: Game: -steelstormrev runs the game Steel Storm: Revenants
948 { GAME_TOMESOFMEPHISTOPHELES,   GAME_TOMESOFMEPHISTOPHELES,     "tomesofmephistopheles","-tomesofmephistopheles",       "Tomes of Mephistopheles",      "Tomes_of_Mephistopheles",      "gamedata",     NULL,                   "tom",                          "tomesofmephistopheles" }, // COMMANDLINEOPTION: Game: -tomesofmephistopheles runs the game Tomes of Mephistopheles
949 { GAME_STRAPBOMB,                               GAME_STRAPBOMB,                         "strapbomb",                    "-strapbomb",                           "Strap-on-bomb Car",            "Strap-on-bomb_Car",            "id1",          NULL,                   "strap",                        "strapbomb"                             }, // COMMANDLINEOPTION: Game: -strapbomb runs the game Strap-on-bomb Car
950 { GAME_MOONHELM,                                GAME_MOONHELM,                          "moonhelm",                             "-moonhelm",                            "MoonHelm",                                     "MoonHelm",                                     "data",         NULL,                   "mh",                           "moonhelm"                              }, // COMMANDLINEOPTION: Game: -moonhelm runs the game MoonHelm
951 { GAME_VORETOURNAMENT,                  GAME_VORETOURNAMENT,            "voretournament",               "-voretournament",                      "Vore Tournament",                      "Vore_Tournament",                      "data",         NULL,                   "voretournament",       "voretournament"                }, // COMMANDLINEOPTION: Game: -voretournament runs the multiplayer game Vore Tournament
952 };
953
954 static void COM_SetGameType(int index);
955 void COM_InitGameType (void)
956 {
957         char name [MAX_OSPATH];
958         int i;
959         int index = 0;
960
961 #ifdef FORCEGAME
962         COM_ToLowerString(FORCEGAME, name, sizeof (name));
963 #else
964         // check executable filename for keywords, but do it SMARTLY - only check the last path element
965         FS_StripExtension(FS_FileWithoutPath(sys.argv[0]), name, sizeof (name));
966         COM_ToLowerString(name, name, sizeof (name));
967 #endif
968         for (i = 1;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
969                 if (gamemode_info[i].prog_name && gamemode_info[i].prog_name[0] && strstr (name, gamemode_info[i].prog_name))
970                         index = i;
971
972         // check commandline options for keywords
973         for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
974                 if (COM_CheckParm (gamemode_info[i].cmdline))
975                         index = i;
976
977         com_startupgamemode = gamemode_info[index].mode;
978         com_startupgamegroup = gamemode_info[index].group;
979         COM_SetGameType(index);
980 }
981
982 void COM_ChangeGameTypeForGameDirs(void)
983 {
984         int i;
985         int index = -1;
986         // this will not not change the gamegroup
987         // first check if a base game (single gamedir) matches
988         for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
989         {
990                 if (gamemode_info[i].group == com_startupgamegroup && !(gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]))
991                 {
992                         index = i;
993                         break;
994                 }
995         }
996         // now that we have a base game, see if there is a matching derivative game (two gamedirs)
997         if (fs_numgamedirs)
998         {
999                 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
1000                 {
1001                         if (gamemode_info[i].group == com_startupgamegroup && (gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]) && !strcasecmp(fs_gamedirs[0], gamemode_info[i].gamedirname2))
1002                         {
1003                                 index = i;
1004                                 break;
1005                         }
1006                 }
1007         }
1008         // we now have a good guess at which game this is meant to be...
1009         if (index >= 0 && gamemode != gamemode_info[index].mode)
1010                 COM_SetGameType(index);
1011 }
1012
1013 static void COM_SetGameType(int index)
1014 {
1015         static char gamenetworkfilternamebuffer[64];
1016         int i, t;
1017         if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0])))
1018                 index = 0;
1019         gamemode = gamemode_info[index].mode;
1020         gamename = gamemode_info[index].gamename;
1021         gamenetworkfiltername = gamemode_info[index].gamenetworkfiltername;
1022         gamedirname1 = gamemode_info[index].gamedirname1;
1023         gamedirname2 = gamemode_info[index].gamedirname2;
1024         gamescreenshotname = gamemode_info[index].gamescreenshotname;
1025         gameuserdirname = gamemode_info[index].gameuserdirname;
1026
1027         if (gamemode == com_startupgamemode)
1028         {
1029                 if((t = COM_CheckParm("-customgamename")) && t + 1 < sys.argc)
1030                         gamename = gamenetworkfiltername = sys.argv[t+1];
1031                 if((t = COM_CheckParm("-customgamenetworkfiltername")) && t + 1 < sys.argc)
1032                         gamenetworkfiltername = sys.argv[t+1];
1033                 if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < sys.argc)
1034                         gamedirname1 = sys.argv[t+1];
1035                 if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < sys.argc)
1036                         gamedirname2 = *sys.argv[t+1] ? sys.argv[t+1] : NULL;
1037                 if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < sys.argc)
1038                         gamescreenshotname = sys.argv[t+1];
1039                 if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < sys.argc)
1040                         gameuserdirname = sys.argv[t+1];
1041         }
1042
1043         if (gamedirname2 && gamedirname2[0])
1044                 Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2);
1045         else
1046                 Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1);
1047         for (i = 0;i < fs_numgamedirs;i++)
1048         {
1049                 if (i == 0)
1050                         Con_Printf(", with mod gamedirs");
1051                 Con_Printf(" %s", fs_gamedirs[i]);
1052         }
1053         Con_Printf("\n");
1054
1055         if (strchr(gamenetworkfiltername, ' '))
1056         {
1057                 char *s;
1058                 // if there are spaces in the game's network filter name it would
1059                 // cause parse errors in getservers in dpmaster, so we need to replace
1060                 // them with _ characters
1061                 strlcpy(gamenetworkfilternamebuffer, gamenetworkfiltername, sizeof(gamenetworkfilternamebuffer));
1062                 while ((s = strchr(gamenetworkfilternamebuffer, ' ')) != NULL)
1063                         *s = '_';
1064                 gamenetworkfiltername = gamenetworkfilternamebuffer;
1065         }
1066
1067         Con_Printf("gamename for server filtering: %s\n", gamenetworkfiltername);
1068 }
1069
1070
1071 /*
1072 ================
1073 COM_Init
1074 ================
1075 */
1076 void COM_Init_Commands (void)
1077 {
1078         int i, j, n;
1079         char com_cmdline[MAX_INPUTLINE];
1080
1081         Cvar_RegisterVariable (&registered);
1082         Cvar_RegisterVariable (&cmdline);
1083         Cvar_RegisterVariable(&cl_playermodel);
1084         Cvar_RegisterAlias(&cl_playermodel, "_cl_playermodel");
1085         Cvar_RegisterVariable(&cl_playerskin);
1086         Cvar_RegisterAlias(&cl_playerskin, "_cl_playerskin");
1087
1088         // reconstitute the command line for the cmdline externally visible cvar
1089         n = 0;
1090         for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
1091         {
1092                 i = 0;
1093                 if (strstr(sys.argv[j], " "))
1094                 {
1095                         // arg contains whitespace, store quotes around it
1096                         // This condition checks whether we can allow to put
1097                         // in two quote characters.
1098                         if (n >= ((int)sizeof(com_cmdline) - 2))
1099                                 break;
1100                         com_cmdline[n++] = '\"';
1101                         // This condition checks whether we can allow one
1102                         // more character and a quote character.
1103                         while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
1104                                 // FIXME: Doesn't quote special characters.
1105                                 com_cmdline[n++] = sys.argv[j][i++];
1106                         com_cmdline[n++] = '\"';
1107                 }
1108                 else
1109                 {
1110                         // This condition checks whether we can allow one
1111                         // more character.
1112                         while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
1113                                 com_cmdline[n++] = sys.argv[j][i++];
1114                 }
1115                 if (n < ((int)sizeof(com_cmdline) - 1))
1116                         com_cmdline[n++] = ' ';
1117                 else
1118                         break;
1119         }
1120         com_cmdline[n] = 0;
1121         Cvar_SetQuick(&cmdline, com_cmdline);
1122 }
1123
1124 /*
1125 ============
1126 va
1127
1128 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
1129 ============
1130 */
1131 char *va(char *buf, size_t buflen, const char *format, ...)
1132 {
1133         va_list argptr;
1134
1135         va_start (argptr, format);
1136         dpvsnprintf (buf, buflen, format,argptr);
1137         va_end (argptr);
1138
1139         return buf;
1140 }
1141
1142
1143 //======================================
1144
1145 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
1146
1147 #undef snprintf
1148 #undef vsnprintf
1149
1150 #ifdef WIN32
1151 # define snprintf _snprintf
1152 # define vsnprintf _vsnprintf
1153 #endif
1154
1155
1156 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
1157 {
1158         va_list args;
1159         int result;
1160
1161         va_start (args, format);
1162         result = dpvsnprintf (buffer, buffersize, format, args);
1163         va_end (args);
1164
1165         return result;
1166 }
1167
1168
1169 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
1170 {
1171         int result;
1172
1173 #if _MSC_VER >= 1400
1174         result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
1175 #else
1176         result = vsnprintf (buffer, buffersize, format, args);
1177 #endif
1178         if (result < 0 || (size_t)result >= buffersize)
1179         {
1180                 buffer[buffersize - 1] = '\0';
1181                 return -1;
1182         }
1183
1184         return result;
1185 }
1186
1187
1188 //======================================
1189
1190 void COM_ToLowerString (const char *in, char *out, size_t size_out)
1191 {
1192         if (size_out == 0)
1193                 return;
1194
1195         if(utf8_enable.integer)
1196         {
1197                 *out = 0;
1198                 while(*in && size_out > 1)
1199                 {
1200                         int n;
1201                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1202                         ch = u8_tolower(ch);
1203                         n = u8_fromchar(ch, out, size_out);
1204                         if(n <= 0)
1205                                 break;
1206                         out += n;
1207                         size_out -= n;
1208                 }
1209                 return;
1210         }
1211
1212         while (*in && size_out > 1)
1213         {
1214                 if (*in >= 'A' && *in <= 'Z')
1215                         *out++ = *in++ + 'a' - 'A';
1216                 else
1217                         *out++ = *in++;
1218                 size_out--;
1219         }
1220         *out = '\0';
1221 }
1222
1223 void COM_ToUpperString (const char *in, char *out, size_t size_out)
1224 {
1225         if (size_out == 0)
1226                 return;
1227
1228         if(utf8_enable.integer)
1229         {
1230                 *out = 0;
1231                 while(*in && size_out > 1)
1232                 {
1233                         int n;
1234                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1235                         ch = u8_toupper(ch);
1236                         n = u8_fromchar(ch, out, size_out);
1237                         if(n <= 0)
1238                                 break;
1239                         out += n;
1240                         size_out -= n;
1241                 }
1242                 return;
1243         }
1244
1245         while (*in && size_out > 1)
1246         {
1247                 if (*in >= 'a' && *in <= 'z')
1248                         *out++ = *in++ + 'A' - 'a';
1249                 else
1250                         *out++ = *in++;
1251                 size_out--;
1252         }
1253         *out = '\0';
1254 }
1255
1256 int COM_StringBeginsWith(const char *s, const char *match)
1257 {
1258         for (;*s && *match;s++, match++)
1259                 if (*s != *match)
1260                         return false;
1261         return true;
1262 }
1263
1264 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1265 {
1266         int argc, commentprefixlength;
1267         char *tokenbufend;
1268         const char *l;
1269         argc = 0;
1270         tokenbufend = tokenbuf + tokenbufsize;
1271         l = *text;
1272         commentprefixlength = 0;
1273         if (commentprefix)
1274                 commentprefixlength = (int)strlen(commentprefix);
1275         while (*l && *l != '\n' && *l != '\r')
1276         {
1277                 if (!ISWHITESPACE(*l))
1278                 {
1279                         if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1280                         {
1281                                 while (*l && *l != '\n' && *l != '\r')
1282                                         l++;
1283                                 break;
1284                         }
1285                         if (argc >= maxargc)
1286                                 return -1;
1287                         argv[argc++] = tokenbuf;
1288                         if (*l == '"')
1289                         {
1290                                 l++;
1291                                 while (*l && *l != '"')
1292                                 {
1293                                         if (tokenbuf >= tokenbufend)
1294                                                 return -1;
1295                                         *tokenbuf++ = *l++;
1296                                 }
1297                                 if (*l == '"')
1298                                         l++;
1299                         }
1300                         else
1301                         {
1302                                 while (!ISWHITESPACE(*l))
1303                                 {
1304                                         if (tokenbuf >= tokenbufend)
1305                                                 return -1;
1306                                         *tokenbuf++ = *l++;
1307                                 }
1308                         }
1309                         if (tokenbuf >= tokenbufend)
1310                                 return -1;
1311                         *tokenbuf++ = 0;
1312                 }
1313                 else
1314                         l++;
1315         }
1316         // line endings:
1317         // UNIX: \n
1318         // Mac: \r
1319         // Windows: \r\n
1320         if (*l == '\r')
1321                 l++;
1322         if (*l == '\n')
1323                 l++;
1324         *text = l;
1325         return argc;
1326 }
1327
1328 /*
1329 ============
1330 COM_StringLengthNoColors
1331
1332 calculates the visible width of a color coded string.
1333
1334 *valid is filled with TRUE if the string is a valid colored string (that is, if
1335 it does not end with an unfinished color code). If it gets filled with FALSE, a
1336 fix would be adding a STRING_COLOR_TAG at the end of the string.
1337
1338 valid can be set to NULL if the caller doesn't care.
1339
1340 For size_s, specify the maximum number of characters from s to use, or 0 to use
1341 all characters until the zero terminator.
1342 ============
1343 */
1344 size_t
1345 COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid)
1346 {
1347         const char *end = size_s ? (s + size_s) : NULL;
1348         size_t len = 0;
1349         for(;;)
1350         {
1351                 switch((s == end) ? 0 : *s)
1352                 {
1353                         case 0:
1354                                 if(valid)
1355                                         *valid = true;
1356                                 return len;
1357                         case STRING_COLOR_TAG:
1358                                 ++s;
1359                                 switch((s == end) ? 0 : *s)
1360                                 {
1361                                         case STRING_COLOR_RGB_TAG_CHAR:
1362                                                 if (s+1 != end && isxdigit(s[1]) &&
1363                                                         s+2 != end && isxdigit(s[2]) &&
1364                                                         s+3 != end && isxdigit(s[3]) )
1365                                                 {
1366                                                         s+=3;
1367                                                         break;
1368                                                 }
1369                                                 ++len; // STRING_COLOR_TAG
1370                                                 ++len; // STRING_COLOR_RGB_TAG_CHAR
1371                                                 break;
1372                                         case 0: // ends with unfinished color code!
1373                                                 ++len;
1374                                                 if(valid)
1375                                                         *valid = false;
1376                                                 return len;
1377                                         case STRING_COLOR_TAG: // escaped ^
1378                                                 ++len;
1379                                                 break;
1380                                         case '0': case '1': case '2': case '3': case '4':
1381                                         case '5': case '6': case '7': case '8': case '9': // color code
1382                                                 break;
1383                                         default: // not a color code
1384                                                 ++len; // STRING_COLOR_TAG
1385                                                 ++len; // the character
1386                                                 break;
1387                                 }
1388                                 break;
1389                         default:
1390                                 ++len;
1391                                 break;
1392                 }
1393                 ++s;
1394         }
1395         // never get here
1396 }
1397
1398 /*
1399 ============
1400 COM_StringDecolorize
1401
1402 removes color codes from a string.
1403
1404 If escape_carets is true, the resulting string will be safe for printing. If
1405 escape_carets is false, the function will just strip color codes (for logging
1406 for example).
1407
1408 If the output buffer size did not suffice for converting, the function returns
1409 FALSE. Generally, if escape_carets is false, the output buffer needs
1410 strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
1411 bytes. In any case, the function makes sure that the resulting string is
1412 zero terminated.
1413
1414 For size_in, specify the maximum number of characters from in to use, or 0 to use
1415 all characters until the zero terminator.
1416 ============
1417 */
1418 qboolean
1419 COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets)
1420 {
1421 #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return false; } } while(0)
1422         const char *end = size_in ? (in + size_in) : NULL;
1423         if(size_out < 1)
1424                 return false;
1425         for(;;)
1426         {
1427                 switch((in == end) ? 0 : *in)
1428                 {
1429                         case 0:
1430                                 *out++ = 0;
1431                                 return true;
1432                         case STRING_COLOR_TAG:
1433                                 ++in;
1434                                 switch((in == end) ? 0 : *in)
1435                                 {
1436                                         case STRING_COLOR_RGB_TAG_CHAR:
1437                                                 if (in+1 != end && isxdigit(in[1]) &&
1438                                                         in+2 != end && isxdigit(in[2]) &&
1439                                                         in+3 != end && isxdigit(in[3]) )
1440                                                 {
1441                                                         in+=3;
1442                                                         break;
1443                                                 }
1444                                                 APPEND(STRING_COLOR_TAG);
1445                                                 if(escape_carets)
1446                                                         APPEND(STRING_COLOR_TAG);
1447                                                 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1448                                                 break;
1449                                         case 0: // ends with unfinished color code!
1450                                                 APPEND(STRING_COLOR_TAG);
1451                                                 // finish the code by appending another caret when escaping
1452                                                 if(escape_carets)
1453                                                         APPEND(STRING_COLOR_TAG);
1454                                                 *out++ = 0;
1455                                                 return true;
1456                                         case STRING_COLOR_TAG: // escaped ^
1457                                                 APPEND(STRING_COLOR_TAG);
1458                                                 // append a ^ twice when escaping
1459                                                 if(escape_carets)
1460                                                         APPEND(STRING_COLOR_TAG);
1461                                                 break;
1462                                         case '0': case '1': case '2': case '3': case '4':
1463                                         case '5': case '6': case '7': case '8': case '9': // color code
1464                                                 break;
1465                                         default: // not a color code
1466                                                 APPEND(STRING_COLOR_TAG);
1467                                                 APPEND(*in);
1468                                                 break;
1469                                 }
1470                                 break;
1471                         default:
1472                                 APPEND(*in);
1473                                 break;
1474                 }
1475                 ++in;
1476         }
1477         // never get here
1478 #undef APPEND
1479 }
1480
1481 char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
1482 {
1483         int pos = 0, j;
1484         size_t keylength;
1485         if (!key)
1486                 key = "";
1487         keylength = strlen(key);
1488         if (valuelength < 1 || !value)
1489         {
1490                 Con_Printf("InfoString_GetValue: no room in value\n");
1491                 return NULL;
1492         }
1493         value[0] = 0;
1494         if (strchr(key, '\\'))
1495         {
1496                 Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
1497                 return NULL;
1498         }
1499         if (strchr(key, '\"'))
1500         {
1501                 Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
1502                 return NULL;
1503         }
1504         if (!key[0])
1505         {
1506                 Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
1507                 return NULL;
1508         }
1509         while (buffer[pos] == '\\')
1510         {
1511                 if (!memcmp(buffer + pos+1, key, keylength) &&
1512                                 (buffer[pos+1 + keylength] == 0 ||
1513                                  buffer[pos+1 + keylength] == '\\'))
1514                 {
1515                         pos += 1 + (int)keylength;           // Skip \key
1516                         if (buffer[pos] == '\\') pos++; // Skip \ before value.
1517                         for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
1518                                 value[j] = buffer[pos+j];
1519                         value[j] = 0;
1520                         return value;
1521                 }
1522                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1523                 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1524                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1525                 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1526         }
1527         // if we reach this point the key was not found
1528         return NULL;
1529 }
1530
1531 void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value)
1532 {
1533         int pos = 0, pos2;
1534         size_t keylength;
1535         if (!key)
1536                 key = "";
1537         if (!value)
1538                 value = "";
1539         keylength = strlen(key);
1540         if (strchr(key, '\\') || strchr(value, '\\'))
1541         {
1542                 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value);
1543                 return;
1544         }
1545         if (strchr(key, '\"') || strchr(value, '\"'))
1546         {
1547                 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value);
1548                 return;
1549         }
1550         if (!key[0])
1551         {
1552                 Con_Printf("InfoString_SetValue: can not set a key with no name\n");
1553                 return;
1554         }
1555         while (buffer[pos] == '\\')
1556         {
1557                 if (!memcmp(buffer + pos+1, key, keylength) &&
1558                                 (buffer[pos+1 + keylength] == 0 ||
1559                                  buffer[pos+1 + keylength] == '\\'))
1560                         break;
1561                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1562                 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1563                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1564                 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1565         }
1566         // if we found the key, find the end of it because we will be replacing it
1567         pos2 = pos;
1568         if (buffer[pos] == '\\')
1569         {
1570                 pos2 += 1 + (int)keylength;  // Skip \key
1571                 if (buffer[pos2] == '\\') pos2++; // Skip \ before value.
1572                 for (;buffer[pos2] && buffer[pos2] != '\\';pos2++);
1573         }
1574         if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2))
1575         {
1576                 Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value);
1577                 return;
1578         }
1579         if (value[0])
1580         {
1581                 // set the key/value and append the remaining text
1582                 char tempbuffer[MAX_INPUTLINE];
1583                 strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer));
1584                 dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer);
1585         }
1586         else
1587         {
1588                 // just remove the key from the text
1589                 strlcpy(buffer + pos, buffer + pos2, bufferlength - pos);
1590         }
1591 }
1592
1593 void InfoString_Print(char *buffer)
1594 {
1595         int i;
1596         char key[MAX_INPUTLINE];
1597         char value[MAX_INPUTLINE];
1598         while (*buffer)
1599         {
1600                 if (*buffer != '\\')
1601                 {
1602                         Con_Printf("InfoString_Print: corrupt string\n");
1603                         return;
1604                 }
1605                 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1606                         if (i < (int)sizeof(key)-1)
1607                                 key[i++] = *buffer;
1608                 key[i] = 0;
1609                 if (*buffer != '\\')
1610                 {
1611                         Con_Printf("InfoString_Print: corrupt string\n");
1612                         return;
1613                 }
1614                 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1615                         if (i < (int)sizeof(value)-1)
1616                                 value[i++] = *buffer;
1617                 value[i] = 0;
1618                 // empty value is an error case
1619                 Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE");
1620         }
1621 }
1622
1623 //========================================================
1624 // strlcat and strlcpy, from OpenBSD
1625
1626 /*
1627  * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
1628  *
1629  * Permission to use, copy, modify, and distribute this software for any
1630  * purpose with or without fee is hereby granted, provided that the above
1631  * copyright notice and this permission notice appear in all copies.
1632  *
1633  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1634  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1635  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1636  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1637  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1638  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1639  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1640  */
1641
1642 /*      $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $    */
1643 /*      $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $     */
1644
1645
1646 #ifndef HAVE_STRLCAT
1647 size_t
1648 strlcat(char *dst, const char *src, size_t siz)
1649 {
1650         register char *d = dst;
1651         register const char *s = src;
1652         register size_t n = siz;
1653         size_t dlen;
1654
1655         /* Find the end of dst and adjust bytes left but don't go past end */
1656         while (n-- != 0 && *d != '\0')
1657                 d++;
1658         dlen = d - dst;
1659         n = siz - dlen;
1660
1661         if (n == 0)
1662                 return(dlen + strlen(s));
1663         while (*s != '\0') {
1664                 if (n != 1) {
1665                         *d++ = *s;
1666                         n--;
1667                 }
1668                 s++;
1669         }
1670         *d = '\0';
1671
1672         return(dlen + (s - src));       /* count does not include NUL */
1673 }
1674 #endif  // #ifndef HAVE_STRLCAT
1675
1676
1677 #ifndef HAVE_STRLCPY
1678 size_t
1679 strlcpy(char *dst, const char *src, size_t siz)
1680 {
1681         register char *d = dst;
1682         register const char *s = src;
1683         register size_t n = siz;
1684
1685         /* Copy as many bytes as will fit */
1686         if (n != 0 && --n != 0) {
1687                 do {
1688                         if ((*d++ = *s++) == 0)
1689                                 break;
1690                 } while (--n != 0);
1691         }
1692
1693         /* Not enough room in dst, add NUL and traverse rest of src */
1694         if (n == 0) {
1695                 if (siz != 0)
1696                         *d = '\0';              /* NUL-terminate dst */
1697                 while (*s++)
1698                         ;
1699         }
1700
1701         return(s - src - 1);    /* count does not include NUL */
1702 }
1703
1704 #endif  // #ifndef HAVE_STRLCPY
1705
1706 void FindFraction(double val, int *num, int *denom, int denomMax)
1707 {
1708         int i;
1709         double bestdiff;
1710         // initialize
1711         bestdiff = fabs(val);
1712         *num = 0;
1713         *denom = 1;
1714
1715         for(i = 1; i <= denomMax; ++i)
1716         {
1717                 int inum = (int) floor(0.5 + val * i);
1718                 double diff = fabs(val - inum / (double)i);
1719                 if(diff < bestdiff)
1720                 {
1721                         bestdiff = diff;
1722                         *num = inum;
1723                         *denom = i;
1724                 }
1725         }
1726 }
1727
1728 // decodes an XPM from C syntax
1729 char **XPM_DecodeString(const char *in)
1730 {
1731         static char *tokens[257];
1732         static char lines[257][512];
1733         size_t line = 0;
1734
1735         // skip until "{" token
1736         while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1737
1738         // now, read in succession: string, comma-or-}
1739         while(COM_ParseToken_QuakeC(&in, false))
1740         {
1741                 tokens[line] = lines[line];
1742                 strlcpy(lines[line++], com_token, sizeof(lines[0]));
1743                 if(!COM_ParseToken_QuakeC(&in, false))
1744                         return NULL;
1745                 if(!strcmp(com_token, "}"))
1746                         break;
1747                 if(strcmp(com_token, ","))
1748                         return NULL;
1749                 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1750                         return NULL;
1751         }
1752
1753         return tokens;
1754 }
1755
1756 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1757 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
1758 {
1759         unsigned char i0 = (bytes > 0) ? in[0] : 0;
1760         unsigned char i1 = (bytes > 1) ? in[1] : 0;
1761         unsigned char i2 = (bytes > 2) ? in[2] : 0;
1762         unsigned char o0 = base64[i0 >> 2];
1763         unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
1764         unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
1765         unsigned char o3 = base64[i2 & 077];
1766         out[0] = (bytes > 0) ? o0 : '?';
1767         out[1] = (bytes > 0) ? o1 : '?';
1768         out[2] = (bytes > 1) ? o2 : '=';
1769         out[3] = (bytes > 2) ? o3 : '=';
1770 }
1771
1772 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1773 {
1774         size_t blocks, i;
1775         // expand the out-buffer
1776         blocks = (buflen + 2) / 3;
1777         if(blocks*4 > outbuflen)
1778                 return 0;
1779         for(i = blocks; i > 0; )
1780         {
1781                 --i;
1782                 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));
1783         }
1784         return blocks * 4;
1785 }