]> git.xonotic.org Git - xonotic/darkplaces.git/blob - common.c
Merge PR 'Use the text from modinfo.txt as the mod menu entry'
[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 { GAME_DOOMBRINGER,                             GAME_DOOMBRINGER,                       "doombringer",                  "-doombringer",                         "DOOMBRINGER",                          "DOOMBRINGER",                          "dbdata",       NULL,                   "doombringer",          "doombringer"                   }, // COMMANDLINEOPTION: Game: -doombringer runs the game DOOMBRINGER
953 };
954
955 static void COM_SetGameType(int index);
956 void COM_InitGameType (void)
957 {
958         char name [MAX_OSPATH];
959         int i;
960         int index = 0;
961
962 #ifdef FORCEGAME
963         COM_ToLowerString(FORCEGAME, name, sizeof (name));
964 #else
965         // check executable filename for keywords, but do it SMARTLY - only check the last path element
966         FS_StripExtension(FS_FileWithoutPath(sys.argv[0]), name, sizeof (name));
967         COM_ToLowerString(name, name, sizeof (name));
968 #endif
969         for (i = 1;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
970                 if (gamemode_info[i].prog_name && gamemode_info[i].prog_name[0] && strstr (name, gamemode_info[i].prog_name))
971                         index = i;
972
973         // check commandline options for keywords
974         for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
975                 if (COM_CheckParm (gamemode_info[i].cmdline))
976                         index = i;
977
978         com_startupgamemode = gamemode_info[index].mode;
979         com_startupgamegroup = gamemode_info[index].group;
980         COM_SetGameType(index);
981 }
982
983 void COM_ChangeGameTypeForGameDirs(void)
984 {
985         int i;
986         int index = -1;
987         // this will not not change the gamegroup
988         // first check if a base game (single gamedir) matches
989         for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
990         {
991                 if (gamemode_info[i].group == com_startupgamegroup && !(gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]))
992                 {
993                         index = i;
994                         break;
995                 }
996         }
997         // now that we have a base game, see if there is a matching derivative game (two gamedirs)
998         if (fs_numgamedirs)
999         {
1000                 for (i = 0;i < (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]));i++)
1001                 {
1002                         if (gamemode_info[i].group == com_startupgamegroup && (gamemode_info[i].gamedirname2 && gamemode_info[i].gamedirname2[0]) && !strcasecmp(fs_gamedirs[0], gamemode_info[i].gamedirname2))
1003                         {
1004                                 index = i;
1005                                 break;
1006                         }
1007                 }
1008         }
1009         // we now have a good guess at which game this is meant to be...
1010         if (index >= 0 && gamemode != gamemode_info[index].mode)
1011                 COM_SetGameType(index);
1012 }
1013
1014 static void COM_SetGameType(int index)
1015 {
1016         static char gamenetworkfilternamebuffer[64];
1017         int i, t;
1018         if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0])))
1019                 index = 0;
1020         gamemode = gamemode_info[index].mode;
1021         gamename = gamemode_info[index].gamename;
1022         gamenetworkfiltername = gamemode_info[index].gamenetworkfiltername;
1023         gamedirname1 = gamemode_info[index].gamedirname1;
1024         gamedirname2 = gamemode_info[index].gamedirname2;
1025         gamescreenshotname = gamemode_info[index].gamescreenshotname;
1026         gameuserdirname = gamemode_info[index].gameuserdirname;
1027
1028         if (gamemode == com_startupgamemode)
1029         {
1030                 if((t = COM_CheckParm("-customgamename")) && t + 1 < sys.argc)
1031                         gamename = gamenetworkfiltername = sys.argv[t+1];
1032                 if((t = COM_CheckParm("-customgamenetworkfiltername")) && t + 1 < sys.argc)
1033                         gamenetworkfiltername = sys.argv[t+1];
1034                 if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < sys.argc)
1035                         gamedirname1 = sys.argv[t+1];
1036                 if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < sys.argc)
1037                         gamedirname2 = *sys.argv[t+1] ? sys.argv[t+1] : NULL;
1038                 if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < sys.argc)
1039                         gamescreenshotname = sys.argv[t+1];
1040                 if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < sys.argc)
1041                         gameuserdirname = sys.argv[t+1];
1042         }
1043
1044         if (gamedirname2 && gamedirname2[0])
1045                 Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2);
1046         else
1047                 Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1);
1048         for (i = 0;i < fs_numgamedirs;i++)
1049         {
1050                 if (i == 0)
1051                         Con_Printf(", with mod gamedirs");
1052                 Con_Printf(" %s", fs_gamedirs[i]);
1053         }
1054         Con_Printf("\n");
1055
1056         if (strchr(gamenetworkfiltername, ' '))
1057         {
1058                 char *s;
1059                 // if there are spaces in the game's network filter name it would
1060                 // cause parse errors in getservers in dpmaster, so we need to replace
1061                 // them with _ characters
1062                 strlcpy(gamenetworkfilternamebuffer, gamenetworkfiltername, sizeof(gamenetworkfilternamebuffer));
1063                 while ((s = strchr(gamenetworkfilternamebuffer, ' ')) != NULL)
1064                         *s = '_';
1065                 gamenetworkfiltername = gamenetworkfilternamebuffer;
1066         }
1067
1068         Con_Printf("gamename for server filtering: %s\n", gamenetworkfiltername);
1069 }
1070
1071
1072 /*
1073 ================
1074 COM_Init
1075 ================
1076 */
1077 void COM_Init_Commands (void)
1078 {
1079         int i, j, n;
1080         char com_cmdline[MAX_INPUTLINE];
1081
1082         Cvar_RegisterVariable (&registered);
1083         Cvar_RegisterVariable (&cmdline);
1084         Cvar_RegisterVariable(&cl_playermodel);
1085         Cvar_RegisterAlias(&cl_playermodel, "_cl_playermodel");
1086         Cvar_RegisterVariable(&cl_playerskin);
1087         Cvar_RegisterAlias(&cl_playerskin, "_cl_playerskin");
1088
1089         // reconstitute the command line for the cmdline externally visible cvar
1090         n = 0;
1091         for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
1092         {
1093                 i = 0;
1094                 if (strstr(sys.argv[j], " "))
1095                 {
1096                         // arg contains whitespace, store quotes around it
1097                         // This condition checks whether we can allow to put
1098                         // in two quote characters.
1099                         if (n >= ((int)sizeof(com_cmdline) - 2))
1100                                 break;
1101                         com_cmdline[n++] = '\"';
1102                         // This condition checks whether we can allow one
1103                         // more character and a quote character.
1104                         while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
1105                                 // FIXME: Doesn't quote special characters.
1106                                 com_cmdline[n++] = sys.argv[j][i++];
1107                         com_cmdline[n++] = '\"';
1108                 }
1109                 else
1110                 {
1111                         // This condition checks whether we can allow one
1112                         // more character.
1113                         while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
1114                                 com_cmdline[n++] = sys.argv[j][i++];
1115                 }
1116                 if (n < ((int)sizeof(com_cmdline) - 1))
1117                         com_cmdline[n++] = ' ';
1118                 else
1119                         break;
1120         }
1121         com_cmdline[n] = 0;
1122         Cvar_SetQuick(&cmdline, com_cmdline);
1123 }
1124
1125 /*
1126 ============
1127 va
1128
1129 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
1130 ============
1131 */
1132 char *va(char *buf, size_t buflen, const char *format, ...)
1133 {
1134         va_list argptr;
1135
1136         va_start (argptr, format);
1137         dpvsnprintf (buf, buflen, format,argptr);
1138         va_end (argptr);
1139
1140         return buf;
1141 }
1142
1143
1144 //======================================
1145
1146 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
1147
1148 #undef snprintf
1149 #undef vsnprintf
1150
1151 #ifdef WIN32
1152 # define snprintf _snprintf
1153 # define vsnprintf _vsnprintf
1154 #endif
1155
1156
1157 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
1158 {
1159         va_list args;
1160         int result;
1161
1162         va_start (args, format);
1163         result = dpvsnprintf (buffer, buffersize, format, args);
1164         va_end (args);
1165
1166         return result;
1167 }
1168
1169
1170 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
1171 {
1172         int result;
1173
1174 #if _MSC_VER >= 1400
1175         result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
1176 #else
1177         result = vsnprintf (buffer, buffersize, format, args);
1178 #endif
1179         if (result < 0 || (size_t)result >= buffersize)
1180         {
1181                 buffer[buffersize - 1] = '\0';
1182                 return -1;
1183         }
1184
1185         return result;
1186 }
1187
1188
1189 //======================================
1190
1191 void COM_ToLowerString (const char *in, char *out, size_t size_out)
1192 {
1193         if (size_out == 0)
1194                 return;
1195
1196         if(utf8_enable.integer)
1197         {
1198                 *out = 0;
1199                 while(*in && size_out > 1)
1200                 {
1201                         int n;
1202                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1203                         ch = u8_tolower(ch);
1204                         n = u8_fromchar(ch, out, size_out);
1205                         if(n <= 0)
1206                                 break;
1207                         out += n;
1208                         size_out -= n;
1209                 }
1210                 return;
1211         }
1212
1213         while (*in && size_out > 1)
1214         {
1215                 if (*in >= 'A' && *in <= 'Z')
1216                         *out++ = *in++ + 'a' - 'A';
1217                 else
1218                         *out++ = *in++;
1219                 size_out--;
1220         }
1221         *out = '\0';
1222 }
1223
1224 void COM_ToUpperString (const char *in, char *out, size_t size_out)
1225 {
1226         if (size_out == 0)
1227                 return;
1228
1229         if(utf8_enable.integer)
1230         {
1231                 *out = 0;
1232                 while(*in && size_out > 1)
1233                 {
1234                         int n;
1235                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1236                         ch = u8_toupper(ch);
1237                         n = u8_fromchar(ch, out, size_out);
1238                         if(n <= 0)
1239                                 break;
1240                         out += n;
1241                         size_out -= n;
1242                 }
1243                 return;
1244         }
1245
1246         while (*in && size_out > 1)
1247         {
1248                 if (*in >= 'a' && *in <= 'z')
1249                         *out++ = *in++ + 'A' - 'a';
1250                 else
1251                         *out++ = *in++;
1252                 size_out--;
1253         }
1254         *out = '\0';
1255 }
1256
1257 int COM_StringBeginsWith(const char *s, const char *match)
1258 {
1259         for (;*s && *match;s++, match++)
1260                 if (*s != *match)
1261                         return false;
1262         return true;
1263 }
1264
1265 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1266 {
1267         int argc, commentprefixlength;
1268         char *tokenbufend;
1269         const char *l;
1270         argc = 0;
1271         tokenbufend = tokenbuf + tokenbufsize;
1272         l = *text;
1273         commentprefixlength = 0;
1274         if (commentprefix)
1275                 commentprefixlength = (int)strlen(commentprefix);
1276         while (*l && *l != '\n' && *l != '\r')
1277         {
1278                 if (!ISWHITESPACE(*l))
1279                 {
1280                         if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1281                         {
1282                                 while (*l && *l != '\n' && *l != '\r')
1283                                         l++;
1284                                 break;
1285                         }
1286                         if (argc >= maxargc)
1287                                 return -1;
1288                         argv[argc++] = tokenbuf;
1289                         if (*l == '"')
1290                         {
1291                                 l++;
1292                                 while (*l && *l != '"')
1293                                 {
1294                                         if (tokenbuf >= tokenbufend)
1295                                                 return -1;
1296                                         *tokenbuf++ = *l++;
1297                                 }
1298                                 if (*l == '"')
1299                                         l++;
1300                         }
1301                         else
1302                         {
1303                                 while (!ISWHITESPACE(*l))
1304                                 {
1305                                         if (tokenbuf >= tokenbufend)
1306                                                 return -1;
1307                                         *tokenbuf++ = *l++;
1308                                 }
1309                         }
1310                         if (tokenbuf >= tokenbufend)
1311                                 return -1;
1312                         *tokenbuf++ = 0;
1313                 }
1314                 else
1315                         l++;
1316         }
1317         // line endings:
1318         // UNIX: \n
1319         // Mac: \r
1320         // Windows: \r\n
1321         if (*l == '\r')
1322                 l++;
1323         if (*l == '\n')
1324                 l++;
1325         *text = l;
1326         return argc;
1327 }
1328
1329 /*
1330 ============
1331 COM_StringLengthNoColors
1332
1333 calculates the visible width of a color coded string.
1334
1335 *valid is filled with TRUE if the string is a valid colored string (that is, if
1336 it does not end with an unfinished color code). If it gets filled with FALSE, a
1337 fix would be adding a STRING_COLOR_TAG at the end of the string.
1338
1339 valid can be set to NULL if the caller doesn't care.
1340
1341 For size_s, specify the maximum number of characters from s to use, or 0 to use
1342 all characters until the zero terminator.
1343 ============
1344 */
1345 size_t
1346 COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid)
1347 {
1348         const char *end = size_s ? (s + size_s) : NULL;
1349         size_t len = 0;
1350         for(;;)
1351         {
1352                 switch((s == end) ? 0 : *s)
1353                 {
1354                         case 0:
1355                                 if(valid)
1356                                         *valid = true;
1357                                 return len;
1358                         case STRING_COLOR_TAG:
1359                                 ++s;
1360                                 switch((s == end) ? 0 : *s)
1361                                 {
1362                                         case STRING_COLOR_RGB_TAG_CHAR:
1363                                                 if (s+1 != end && isxdigit(s[1]) &&
1364                                                         s+2 != end && isxdigit(s[2]) &&
1365                                                         s+3 != end && isxdigit(s[3]) )
1366                                                 {
1367                                                         s+=3;
1368                                                         break;
1369                                                 }
1370                                                 ++len; // STRING_COLOR_TAG
1371                                                 ++len; // STRING_COLOR_RGB_TAG_CHAR
1372                                                 break;
1373                                         case 0: // ends with unfinished color code!
1374                                                 ++len;
1375                                                 if(valid)
1376                                                         *valid = false;
1377                                                 return len;
1378                                         case STRING_COLOR_TAG: // escaped ^
1379                                                 ++len;
1380                                                 break;
1381                                         case '0': case '1': case '2': case '3': case '4':
1382                                         case '5': case '6': case '7': case '8': case '9': // color code
1383                                                 break;
1384                                         default: // not a color code
1385                                                 ++len; // STRING_COLOR_TAG
1386                                                 ++len; // the character
1387                                                 break;
1388                                 }
1389                                 break;
1390                         default:
1391                                 ++len;
1392                                 break;
1393                 }
1394                 ++s;
1395         }
1396         // never get here
1397 }
1398
1399 /*
1400 ============
1401 COM_StringDecolorize
1402
1403 removes color codes from a string.
1404
1405 If escape_carets is true, the resulting string will be safe for printing. If
1406 escape_carets is false, the function will just strip color codes (for logging
1407 for example).
1408
1409 If the output buffer size did not suffice for converting, the function returns
1410 FALSE. Generally, if escape_carets is false, the output buffer needs
1411 strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
1412 bytes. In any case, the function makes sure that the resulting string is
1413 zero terminated.
1414
1415 For size_in, specify the maximum number of characters from in to use, or 0 to use
1416 all characters until the zero terminator.
1417 ============
1418 */
1419 qboolean
1420 COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets)
1421 {
1422 #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return false; } } while(0)
1423         const char *end = size_in ? (in + size_in) : NULL;
1424         if(size_out < 1)
1425                 return false;
1426         for(;;)
1427         {
1428                 switch((in == end) ? 0 : *in)
1429                 {
1430                         case 0:
1431                                 *out++ = 0;
1432                                 return true;
1433                         case STRING_COLOR_TAG:
1434                                 ++in;
1435                                 switch((in == end) ? 0 : *in)
1436                                 {
1437                                         case STRING_COLOR_RGB_TAG_CHAR:
1438                                                 if (in+1 != end && isxdigit(in[1]) &&
1439                                                         in+2 != end && isxdigit(in[2]) &&
1440                                                         in+3 != end && isxdigit(in[3]) )
1441                                                 {
1442                                                         in+=3;
1443                                                         break;
1444                                                 }
1445                                                 APPEND(STRING_COLOR_TAG);
1446                                                 if(escape_carets)
1447                                                         APPEND(STRING_COLOR_TAG);
1448                                                 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1449                                                 break;
1450                                         case 0: // ends with unfinished color code!
1451                                                 APPEND(STRING_COLOR_TAG);
1452                                                 // finish the code by appending another caret when escaping
1453                                                 if(escape_carets)
1454                                                         APPEND(STRING_COLOR_TAG);
1455                                                 *out++ = 0;
1456                                                 return true;
1457                                         case STRING_COLOR_TAG: // escaped ^
1458                                                 APPEND(STRING_COLOR_TAG);
1459                                                 // append a ^ twice when escaping
1460                                                 if(escape_carets)
1461                                                         APPEND(STRING_COLOR_TAG);
1462                                                 break;
1463                                         case '0': case '1': case '2': case '3': case '4':
1464                                         case '5': case '6': case '7': case '8': case '9': // color code
1465                                                 break;
1466                                         default: // not a color code
1467                                                 APPEND(STRING_COLOR_TAG);
1468                                                 APPEND(*in);
1469                                                 break;
1470                                 }
1471                                 break;
1472                         default:
1473                                 APPEND(*in);
1474                                 break;
1475                 }
1476                 ++in;
1477         }
1478         // never get here
1479 #undef APPEND
1480 }
1481
1482 char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
1483 {
1484         int pos = 0, j;
1485         size_t keylength;
1486         if (!key)
1487                 key = "";
1488         keylength = strlen(key);
1489         if (valuelength < 1 || !value)
1490         {
1491                 Con_Printf("InfoString_GetValue: no room in value\n");
1492                 return NULL;
1493         }
1494         value[0] = 0;
1495         if (strchr(key, '\\'))
1496         {
1497                 Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
1498                 return NULL;
1499         }
1500         if (strchr(key, '\"'))
1501         {
1502                 Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
1503                 return NULL;
1504         }
1505         if (!key[0])
1506         {
1507                 Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
1508                 return NULL;
1509         }
1510         while (buffer[pos] == '\\')
1511         {
1512                 if (!memcmp(buffer + pos+1, key, keylength) &&
1513                                 (buffer[pos+1 + keylength] == 0 ||
1514                                  buffer[pos+1 + keylength] == '\\'))
1515                 {
1516                         pos += 1 + (int)keylength;           // Skip \key
1517                         if (buffer[pos] == '\\') pos++; // Skip \ before value.
1518                         for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
1519                                 value[j] = buffer[pos+j];
1520                         value[j] = 0;
1521                         return value;
1522                 }
1523                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1524                 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1525                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1526                 for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
1527         }
1528         // if we reach this point the key was not found
1529         return NULL;
1530 }
1531
1532 void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value)
1533 {
1534         int pos = 0, pos2;
1535         size_t keylength;
1536         if (!key)
1537                 key = "";
1538         if (!value)
1539                 value = "";
1540         keylength = strlen(key);
1541         if (strchr(key, '\\') || strchr(value, '\\'))
1542         {
1543                 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value);
1544                 return;
1545         }
1546         if (strchr(key, '\"') || strchr(value, '\"'))
1547         {
1548                 Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value);
1549                 return;
1550         }
1551         if (!key[0])
1552         {
1553                 Con_Printf("InfoString_SetValue: can not set a key with no name\n");
1554                 return;
1555         }
1556         while (buffer[pos] == '\\')
1557         {
1558                 if (!memcmp(buffer + pos+1, key, keylength) &&
1559                                 (buffer[pos+1 + keylength] == 0 ||
1560                                  buffer[pos+1 + keylength] == '\\'))
1561                         break;
1562                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1563                 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1564                 if (buffer[pos] == '\\') pos++; // Skip \ before value.
1565                 for (;buffer[pos] && buffer[pos] != '\\';pos++);
1566         }
1567         // if we found the key, find the end of it because we will be replacing it
1568         pos2 = pos;
1569         if (buffer[pos] == '\\')
1570         {
1571                 pos2 += 1 + (int)keylength;  // Skip \key
1572                 if (buffer[pos2] == '\\') pos2++; // Skip \ before value.
1573                 for (;buffer[pos2] && buffer[pos2] != '\\';pos2++);
1574         }
1575         if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2))
1576         {
1577                 Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value);
1578                 return;
1579         }
1580         if (value[0])
1581         {
1582                 // set the key/value and append the remaining text
1583                 char tempbuffer[MAX_INPUTLINE];
1584                 strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer));
1585                 dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer);
1586         }
1587         else
1588         {
1589                 // just remove the key from the text
1590                 strlcpy(buffer + pos, buffer + pos2, bufferlength - pos);
1591         }
1592 }
1593
1594 void InfoString_Print(char *buffer)
1595 {
1596         int i;
1597         char key[MAX_INPUTLINE];
1598         char value[MAX_INPUTLINE];
1599         while (*buffer)
1600         {
1601                 if (*buffer != '\\')
1602                 {
1603                         Con_Printf("InfoString_Print: corrupt string\n");
1604                         return;
1605                 }
1606                 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1607                         if (i < (int)sizeof(key)-1)
1608                                 key[i++] = *buffer;
1609                 key[i] = 0;
1610                 if (*buffer != '\\')
1611                 {
1612                         Con_Printf("InfoString_Print: corrupt string\n");
1613                         return;
1614                 }
1615                 for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
1616                         if (i < (int)sizeof(value)-1)
1617                                 value[i++] = *buffer;
1618                 value[i] = 0;
1619                 // empty value is an error case
1620                 Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE");
1621         }
1622 }
1623
1624 //========================================================
1625 // strlcat and strlcpy, from OpenBSD
1626
1627 /*
1628  * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
1629  *
1630  * Permission to use, copy, modify, and distribute this software for any
1631  * purpose with or without fee is hereby granted, provided that the above
1632  * copyright notice and this permission notice appear in all copies.
1633  *
1634  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1635  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1636  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1637  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1638  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1639  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1640  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1641  */
1642
1643 /*      $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $    */
1644 /*      $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $     */
1645
1646
1647 #ifndef HAVE_STRLCAT
1648 size_t
1649 strlcat(char *dst, const char *src, size_t siz)
1650 {
1651         register char *d = dst;
1652         register const char *s = src;
1653         register size_t n = siz;
1654         size_t dlen;
1655
1656         /* Find the end of dst and adjust bytes left but don't go past end */
1657         while (n-- != 0 && *d != '\0')
1658                 d++;
1659         dlen = d - dst;
1660         n = siz - dlen;
1661
1662         if (n == 0)
1663                 return(dlen + strlen(s));
1664         while (*s != '\0') {
1665                 if (n != 1) {
1666                         *d++ = *s;
1667                         n--;
1668                 }
1669                 s++;
1670         }
1671         *d = '\0';
1672
1673         return(dlen + (s - src));       /* count does not include NUL */
1674 }
1675 #endif  // #ifndef HAVE_STRLCAT
1676
1677
1678 #ifndef HAVE_STRLCPY
1679 size_t
1680 strlcpy(char *dst, const char *src, size_t siz)
1681 {
1682         register char *d = dst;
1683         register const char *s = src;
1684         register size_t n = siz;
1685
1686         /* Copy as many bytes as will fit */
1687         if (n != 0 && --n != 0) {
1688                 do {
1689                         if ((*d++ = *s++) == 0)
1690                                 break;
1691                 } while (--n != 0);
1692         }
1693
1694         /* Not enough room in dst, add NUL and traverse rest of src */
1695         if (n == 0) {
1696                 if (siz != 0)
1697                         *d = '\0';              /* NUL-terminate dst */
1698                 while (*s++)
1699                         ;
1700         }
1701
1702         return(s - src - 1);    /* count does not include NUL */
1703 }
1704
1705 #endif  // #ifndef HAVE_STRLCPY
1706
1707 void FindFraction(double val, int *num, int *denom, int denomMax)
1708 {
1709         int i;
1710         double bestdiff;
1711         // initialize
1712         bestdiff = fabs(val);
1713         *num = 0;
1714         *denom = 1;
1715
1716         for(i = 1; i <= denomMax; ++i)
1717         {
1718                 int inum = (int) floor(0.5 + val * i);
1719                 double diff = fabs(val - inum / (double)i);
1720                 if(diff < bestdiff)
1721                 {
1722                         bestdiff = diff;
1723                         *num = inum;
1724                         *denom = i;
1725                 }
1726         }
1727 }
1728
1729 // decodes an XPM from C syntax
1730 char **XPM_DecodeString(const char *in)
1731 {
1732         static char *tokens[257];
1733         static char lines[257][512];
1734         size_t line = 0;
1735
1736         // skip until "{" token
1737         while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1738
1739         // now, read in succession: string, comma-or-}
1740         while(COM_ParseToken_QuakeC(&in, false))
1741         {
1742                 tokens[line] = lines[line];
1743                 strlcpy(lines[line++], com_token, sizeof(lines[0]));
1744                 if(!COM_ParseToken_QuakeC(&in, false))
1745                         return NULL;
1746                 if(!strcmp(com_token, "}"))
1747                         break;
1748                 if(strcmp(com_token, ","))
1749                         return NULL;
1750                 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1751                         return NULL;
1752         }
1753
1754         return tokens;
1755 }
1756
1757 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1758 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
1759 {
1760         unsigned char i0 = (bytes > 0) ? in[0] : 0;
1761         unsigned char i1 = (bytes > 1) ? in[1] : 0;
1762         unsigned char i2 = (bytes > 2) ? in[2] : 0;
1763         unsigned char o0 = base64[i0 >> 2];
1764         unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
1765         unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
1766         unsigned char o3 = base64[i2 & 077];
1767         out[0] = (bytes > 0) ? o0 : '?';
1768         out[1] = (bytes > 0) ? o1 : '?';
1769         out[2] = (bytes > 1) ? o2 : '=';
1770         out[3] = (bytes > 2) ? o3 : '=';
1771 }
1772
1773 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1774 {
1775         size_t blocks, i;
1776         // expand the out-buffer
1777         blocks = (buflen + 2) / 3;
1778         if(blocks*4 > outbuflen)
1779                 return 0;
1780         for(i = blocks; i > 0; )
1781         {
1782                 --i;
1783                 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));
1784         }
1785         return blocks * 4;
1786 }