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