]> git.xonotic.org Git - xonotic/darkplaces.git/blob - common.c
PRVM: optimise string arguments
[xonotic/darkplaces.git] / common.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2000-2020 DarkPlaces contributors
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
14 See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 */
21 // common.c -- misc functions used in client and server
22
23 #include <stdlib.h>
24 #include <fcntl.h>
25 #ifndef WIN32
26 #include <unistd.h>
27 #endif
28
29 #include "quakedef.h"
30 #include "utf8lib.h"
31
32 cvar_t registered = {CF_CLIENT | CF_SERVER, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
33 cvar_t cmdline = {CF_CLIENT | CF_SERVER, "cmdline","0", "contains commandline the engine was launched with"};
34
35 // FIXME: Find a better place for these.
36 cvar_t cl_playermodel = {CF_CLIENT | CF_SERVER | CF_USERINFO | CF_ARCHIVE, "playermodel", "", "current player model in Nexuiz/Xonotic"};
37 cvar_t cl_playerskin = {CF_CLIENT | CF_SERVER | CF_USERINFO | CF_ARCHIVE, "playerskin", "", "current player skin in Nexuiz/Xonotic"};
38
39 char com_token[MAX_INPUTLINE];
40
41 //===========================================================================
42
43 void SZ_Clear (sizebuf_t *buf)
44 {
45         buf->cursize = 0;
46 }
47
48 unsigned char *SZ_GetSpace (sizebuf_t *buf, int length)
49 {
50         unsigned char *data;
51
52         if (buf->cursize + length > buf->maxsize)
53         {
54                 if (!buf->allowoverflow)
55                         Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
56
57                 if (length > buf->maxsize)
58                         Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
59
60                 buf->overflowed = true;
61                 Con_Print("SZ_GetSpace: overflow\n");
62                 SZ_Clear (buf);
63         }
64
65         data = buf->data + buf->cursize;
66         buf->cursize += length;
67
68         return data;
69 }
70
71 void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
72 {
73         memcpy (SZ_GetSpace(buf,length),data,length);
74 }
75
76 // LadyHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
77 // attention, it has been eradicated from here, its only (former) use in
78 // all of darkplaces.
79
80 static const char *hexchar = "0123456789ABCDEF";
81 void Com_HexDumpToConsole(const unsigned char *data, int size)
82 {
83         int i, j, n;
84         char text[1024];
85         char *cur, *flushpointer;
86         const unsigned char *d;
87         cur = text;
88         flushpointer = text + 512;
89         for (i = 0;i < size;)
90         {
91                 n = 16;
92                 if (n > size - i)
93                         n = size - i;
94                 d = data + i;
95                 // print offset
96                 *cur++ = hexchar[(i >> 12) & 15];
97                 *cur++ = hexchar[(i >>  8) & 15];
98                 *cur++ = hexchar[(i >>  4) & 15];
99                 *cur++ = hexchar[(i >>  0) & 15];
100                 *cur++ = ':';
101                 // print hex
102                 for (j = 0;j < 16;j++)
103                 {
104                         if (j < n)
105                         {
106                                 *cur++ = hexchar[(d[j] >> 4) & 15];
107                                 *cur++ = hexchar[(d[j] >> 0) & 15];
108                         }
109                         else
110                         {
111                                 *cur++ = ' ';
112                                 *cur++ = ' ';
113                         }
114                         if ((j & 3) == 3)
115                                 *cur++ = ' ';
116                 }
117                 // print text
118                 for (j = 0;j < 16;j++)
119                 {
120                         if (j < n)
121                         {
122                                 // color change prefix character has to be treated specially
123                                 if (d[j] == STRING_COLOR_TAG)
124                                 {
125                                         *cur++ = STRING_COLOR_TAG;
126                                         *cur++ = STRING_COLOR_TAG;
127                                 }
128                                 else if (d[j] >= (unsigned char) ' ')
129                                         *cur++ = d[j];
130                                 else
131                                         *cur++ = '.';
132                         }
133                         else
134                                 *cur++ = ' ';
135                 }
136                 *cur++ = '\n';
137                 i += n;
138                 if (cur >= flushpointer || i >= size)
139                 {
140                         *cur++ = 0;
141                         Con_Print(text);
142                         cur = text;
143                 }
144         }
145 }
146
147 void SZ_HexDumpToConsole(const sizebuf_t *buf)
148 {
149         Com_HexDumpToConsole(buf->data, buf->cursize);
150 }
151
152
153 //============================================================================
154
155 /*
156 ==============
157 COM_Wordwrap
158
159 Word wraps a string. The wordWidth function is guaranteed to be called exactly
160 once for each word in the string, so it may be stateful, no idea what that
161 would be good for any more. At the beginning of the string, it will be called
162 for the char 0 to initialize a clean state, and then once with the string " "
163 (a space) so the routine knows how long a space is.
164
165 In case no single character fits into the given width, the wordWidth function
166 must return the width of exactly one character.
167
168 Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
169
170 The sum of the return values of the processLine function will be returned.
171 ==============
172 */
173 int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL)
174 {
175         // Logic is as follows:
176         //
177         // For each word or whitespace:
178         //   Newline found? Output current line, advance to next line. This is not a continuation. Continue.
179         //   Space found? Always add it to the current line, no matter if it fits.
180         //   Word found? Check if current line + current word fits.
181         //     If it fits, append it. Continue.
182         //     If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue.
183
184         qbool isContinuation = false;
185         float spaceWidth;
186         const char *startOfLine = string;
187         const char *cursor = string;
188         const char *end = string + length;
189         float spaceUsedInLine = 0;
190         float spaceUsedForWord;
191         int result = 0;
192         size_t wordLen;
193         size_t dummy;
194
195         dummy = 0;
196         wordWidth(passthroughCW, NULL, &dummy, -1);
197         dummy = 1;
198         spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1);
199
200         for(;;)
201         {
202                 char ch = (cursor < end) ? *cursor : 0;
203                 switch(ch)
204                 {
205                         case 0: // end of string
206                                 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
207                                 goto out;
208                         case '\n': // end of line
209                                 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
210                                 isContinuation = false;
211                                 ++cursor;
212                                 startOfLine = cursor;
213                                 break;
214                         case ' ': // space
215                                 ++cursor;
216                                 spaceUsedInLine += spaceWidth;
217                                 break;
218                         default: // word
219                                 wordLen = 1;
220                                 while(cursor + wordLen < end)
221                                 {
222                                         switch(cursor[wordLen])
223                                         {
224                                                 case 0:
225                                                 case '\n':
226                                                 case ' ':
227                                                         goto out_inner;
228                                                 default:
229                                                         ++wordLen;
230                                                         break;
231                                         }
232                                 }
233                                 out_inner:
234                                 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
235                                 if(wordLen < 1) // cannot happen according to current spec of wordWidth
236                                 {
237                                         wordLen = 1;
238                                         spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself
239                                 }
240                                 if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine)
241                                 {
242                                         // we can simply append it
243                                         cursor += wordLen;
244                                         spaceUsedInLine += spaceUsedForWord;
245                                 }
246                                 else
247                                 {
248                                         // output current line
249                                         result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
250                                         isContinuation = true;
251                                         startOfLine = cursor;
252                                         cursor += wordLen;
253                                         spaceUsedInLine = continuationWidth + spaceUsedForWord;
254                                 }
255                 }
256         }
257         out:
258
259         return result;
260
261 /*
262         qbool isContinuation = false;
263         float currentWordSpace = 0;
264         const char *currentWord = 0;
265         float minReserve = 0;
266
267         float spaceUsedInLine = 0;
268         const char *currentLine = 0;
269         const char *currentLineEnd = 0;
270         float currentLineFinalWhitespace = 0;
271         const char *p;
272
273         int result = 0;
274         minReserve = charWidth(passthroughCW, 0);
275         minReserve += charWidth(passthroughCW, ' ');
276
277         if(maxWidth < continuationWidth + minReserve)
278                 maxWidth = continuationWidth + minReserve;
279
280         charWidth(passthroughCW, 0);
281
282         for(p = string; p < string + length; ++p)
283         {
284                 char c = *p;
285                 float w = charWidth(passthroughCW, c);
286
287                 if(!currentWord)
288                 {
289                         currentWord = p;
290                         currentWordSpace = 0;
291                 }
292
293                 if(!currentLine)
294                 {
295                         currentLine = p;
296                         spaceUsedInLine = isContinuation ? continuationWidth : 0;
297                         currentLineEnd = 0;
298                 }
299
300                 if(c == ' ')
301                 {
302                         // 1. I can add the word AND a space - then just append it.
303                         if(spaceUsedInLine + currentWordSpace + w <= maxWidth)
304                         {
305                                 currentLineEnd = p; // note: space not included here
306                                 currentLineFinalWhitespace = w;
307                                 spaceUsedInLine += currentWordSpace + w;
308                         }
309                         // 2. I can just add the word - then append it, output current line and go to next one.
310                         else if(spaceUsedInLine + currentWordSpace <= maxWidth)
311                         {
312                                 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
313                                 currentLine = 0;
314                                 isContinuation = true;
315                         }
316                         // 3. Otherwise, output current line and go to next one, where I can add the word.
317                         else if(continuationWidth + currentWordSpace + w <= maxWidth)
318                         {
319                                 if(currentLineEnd)
320                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
321                                 currentLine = currentWord;
322                                 spaceUsedInLine = continuationWidth + currentWordSpace + w;
323                                 currentLineEnd = p;
324                                 currentLineFinalWhitespace = w;
325                                 isContinuation = true;
326                         }
327                         // 4. We can't even do that? Then output both current and next word as new lines.
328                         else
329                         {
330                                 if(currentLineEnd)
331                                 {
332                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
333                                         isContinuation = true;
334                                 }
335                                 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
336                                 currentLine = 0;
337                                 isContinuation = true;
338                         }
339                         currentWord = 0;
340                 }
341                 else if(c == '\n')
342                 {
343                         // 1. I can add the word - then do it.
344                         if(spaceUsedInLine + currentWordSpace <= maxWidth)
345                         {
346                                 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
347                         }
348                         // 2. Otherwise, output current line, next one and make tabula rasa.
349                         else
350                         {
351                                 if(currentLineEnd)
352                                 {
353                                         processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
354                                         isContinuation = true;
355                                 }
356                                 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
357                         }
358                         currentWord = 0;
359                         currentLine = 0;
360                         isContinuation = false;
361                 }
362                 else
363                 {
364                         currentWordSpace += w;
365                         if(
366                                 spaceUsedInLine + currentWordSpace > maxWidth // can't join this line...
367                                 &&
368                                 continuationWidth + currentWordSpace > maxWidth // can't join any other line...
369                         )
370                         {
371                                 // this word cannot join ANY line...
372                                 // so output the current line...
373                                 if(currentLineEnd)
374                                 {
375                                         result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
376                                         isContinuation = true;
377                                 }
378
379                                 // then this word's beginning...
380                                 if(isContinuation)
381                                 {
382                                         // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces
383                                         float pieceWidth = maxWidth - continuationWidth;
384                                         const char *pos = currentWord;
385                                         currentWordSpace = 0;
386
387                                         // reset the char width function to a state where no kerning occurs (start of word)
388                                         charWidth(passthroughCW, ' ');
389                                         while(pos <= p)
390                                         {
391                                                 float w = charWidth(passthroughCW, *pos);
392                                                 if(currentWordSpace + w > pieceWidth) // this piece won't fit any more
393                                                 {
394                                                         // print everything until it
395                                                         result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true);
396                                                         // go to here
397                                                         currentWord = pos;
398                                                         currentWordSpace = 0;
399                                                 }
400                                                 currentWordSpace += w;
401                                                 ++pos;
402                                         }
403                                         // now we have a currentWord that fits... set up its next line
404                                         // currentWordSpace has been set
405                                         // currentWord has been set
406                                         spaceUsedInLine = continuationWidth;
407                                         currentLine = currentWord;
408                                         currentLineEnd = 0;
409                                         isContinuation = true;
410                                 }
411                                 else
412                                 {
413                                         // we have a guarantee that it will fix (see if clause)
414                                         result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation);
415
416                                         // and use the rest of this word as new start of a line
417                                         currentWordSpace = w;
418                                         currentWord = p;
419                                         spaceUsedInLine = continuationWidth;
420                                         currentLine = p;
421                                         currentLineEnd = 0;
422                                         isContinuation = true;
423                                 }
424                         }
425                 }
426         }
427
428         if(!currentWord)
429         {
430                 currentWord = p;
431                 currentWordSpace = 0;
432         }
433
434         if(currentLine) // Same procedure as \n
435         {
436                 // Can I append the current word?
437                 if(spaceUsedInLine + currentWordSpace <= maxWidth)
438                         result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
439                 else
440                 {
441                         if(currentLineEnd)
442                         {
443                                 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
444                                 isContinuation = true;
445                         }
446                         result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
447                 }
448         }
449
450         return result;
451 */
452 }
453
454 /*
455 ==============
456 COM_ParseToken_Simple
457
458 Parse a token out of a string
459 ==============
460 */
461 int COM_ParseToken_Simple(const char **datapointer, qbool returnnewline, qbool parsebackslash, qbool parsecomments)
462 {
463         int len;
464         int c;
465         const char *data = *datapointer;
466
467         len = 0;
468         com_token[0] = 0;
469
470         if (!data)
471         {
472                 *datapointer = NULL;
473                 return false;
474         }
475
476 // skip whitespace
477 skipwhite:
478         // line endings:
479         // UNIX: \n
480         // Mac: \r
481         // Windows: \r\n
482         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
483         {
484                 if (*data == 0)
485                 {
486                         // end of file
487                         *datapointer = NULL;
488                         return false;
489                 }
490         }
491
492         // handle Windows line ending
493         if (data[0] == '\r' && data[1] == '\n')
494                 data++;
495
496         if (parsecomments && data[0] == '/' && data[1] == '/')
497         {
498                 // comment
499                 while (*data && *data != '\n' && *data != '\r')
500                         data++;
501                 goto skipwhite;
502         }
503         else if (parsecomments && data[0] == '/' && data[1] == '*')
504         {
505                 // comment
506                 data++;
507                 while (*data && (data[0] != '*' || data[1] != '/'))
508                         data++;
509                 if (*data)
510                         data++;
511                 if (*data)
512                         data++;
513                 goto skipwhite;
514         }
515         else if (*data == '\"')
516         {
517                 // quoted string
518                 for (data++;*data && *data != '\"';data++)
519                 {
520                         c = *data;
521                         if (*data == '\\' && parsebackslash)
522                         {
523                                 data++;
524                                 c = *data;
525                                 if (c == 'n')
526                                         c = '\n';
527                                 else if (c == 't')
528                                         c = '\t';
529                         }
530                         if (len < (int)sizeof(com_token) - 1)
531                                 com_token[len++] = c;
532                 }
533                 com_token[len] = 0;
534                 if (*data == '\"')
535                         data++;
536                 *datapointer = data;
537                 return true;
538         }
539         else if (*data == '\r')
540         {
541                 // translate Mac line ending to UNIX
542                 com_token[len++] = '\n';data++;
543                 com_token[len] = 0;
544                 *datapointer = data;
545                 return true;
546         }
547         else if (*data == '\n')
548         {
549                 // single character
550                 com_token[len++] = *data++;
551                 com_token[len] = 0;
552                 *datapointer = data;
553                 return true;
554         }
555         else
556         {
557                 // regular word
558                 for (;!ISWHITESPACE(*data);data++)
559                         if (len < (int)sizeof(com_token) - 1)
560                                 com_token[len++] = *data;
561                 com_token[len] = 0;
562                 *datapointer = data;
563                 return true;
564         }
565 }
566
567 /*
568 ==============
569 COM_ParseToken_QuakeC
570
571 Parse a token out of a string
572 ==============
573 */
574 int COM_ParseToken_QuakeC(const char **datapointer, qbool returnnewline)
575 {
576         int len;
577         int c;
578         const char *data = *datapointer;
579
580         len = 0;
581         com_token[0] = 0;
582
583         if (!data)
584         {
585                 *datapointer = NULL;
586                 return false;
587         }
588
589 // skip whitespace
590 skipwhite:
591         // line endings:
592         // UNIX: \n
593         // Mac: \r
594         // Windows: \r\n
595         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
596         {
597                 if (*data == 0)
598                 {
599                         // end of file
600                         *datapointer = NULL;
601                         return false;
602                 }
603         }
604
605         // handle Windows line ending
606         if (data[0] == '\r' && data[1] == '\n')
607                 data++;
608
609         if (data[0] == '/' && data[1] == '/')
610         {
611                 // comment
612                 while (*data && *data != '\n' && *data != '\r')
613                         data++;
614                 goto skipwhite;
615         }
616         else if (data[0] == '/' && data[1] == '*')
617         {
618                 // comment
619                 data++;
620                 while (*data && (data[0] != '*' || data[1] != '/'))
621                         data++;
622                 if (*data)
623                         data++;
624                 if (*data)
625                         data++;
626                 goto skipwhite;
627         }
628         else if (*data == '\"' || *data == '\'')
629         {
630                 // quoted string
631                 char quote = *data;
632                 for (data++;*data && *data != quote;data++)
633                 {
634                         c = *data;
635                         if (*data == '\\')
636                         {
637                                 data++;
638                                 c = *data;
639                                 if (c == 'n')
640                                         c = '\n';
641                                 else if (c == 't')
642                                         c = '\t';
643                         }
644                         if (len < (int)sizeof(com_token) - 1)
645                                 com_token[len++] = c;
646                 }
647                 com_token[len] = 0;
648                 if (*data == quote)
649                         data++;
650                 *datapointer = data;
651                 return true;
652         }
653         else if (*data == '\r')
654         {
655                 // translate Mac line ending to UNIX
656                 com_token[len++] = '\n';data++;
657                 com_token[len] = 0;
658                 *datapointer = data;
659                 return true;
660         }
661         else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
662         {
663                 // single character
664                 com_token[len++] = *data++;
665                 com_token[len] = 0;
666                 *datapointer = data;
667                 return true;
668         }
669         else
670         {
671                 // regular word
672                 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
673                         if (len < (int)sizeof(com_token) - 1)
674                                 com_token[len++] = *data;
675                 com_token[len] = 0;
676                 *datapointer = data;
677                 return true;
678         }
679 }
680
681 /*
682 ==============
683 COM_ParseToken_VM_Tokenize
684
685 Parse a token out of a string
686 ==============
687 */
688 int COM_ParseToken_VM_Tokenize(const char **datapointer, qbool returnnewline)
689 {
690         int len;
691         int c;
692         const char *data = *datapointer;
693
694         len = 0;
695         com_token[0] = 0;
696
697         if (!data)
698         {
699                 *datapointer = NULL;
700                 return false;
701         }
702
703 // skip whitespace
704 skipwhite:
705         // line endings:
706         // UNIX: \n
707         // Mac: \r
708         // Windows: \r\n
709         for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
710         {
711                 if (*data == 0)
712                 {
713                         // end of file
714                         *datapointer = NULL;
715                         return false;
716                 }
717         }
718
719         // handle Windows line ending
720         if (data[0] == '\r' && data[1] == '\n')
721                 data++;
722
723         if (data[0] == '/' && data[1] == '/')
724         {
725                 // comment
726                 while (*data && *data != '\n' && *data != '\r')
727                         data++;
728                 goto skipwhite;
729         }
730         else if (data[0] == '/' && data[1] == '*')
731         {
732                 // comment
733                 data++;
734                 while (*data && (data[0] != '*' || data[1] != '/'))
735                         data++;
736                 if (*data)
737                         data++;
738                 if (*data)
739                         data++;
740                 goto skipwhite;
741         }
742         else if (*data == '\"' || *data == '\'')
743         {
744                 char quote = *data;
745                 // quoted string
746                 for (data++;*data && *data != quote;data++)
747                 {
748                         c = *data;
749                         if (*data == '\\')
750                         {
751                                 data++;
752                                 c = *data;
753                                 if (c == 'n')
754                                         c = '\n';
755                                 else if (c == 't')
756                                         c = '\t';
757                         }
758                         if (len < (int)sizeof(com_token) - 1)
759                                 com_token[len++] = c;
760                 }
761                 com_token[len] = 0;
762                 if (*data == quote)
763                         data++;
764                 *datapointer = data;
765                 return true;
766         }
767         else if (*data == '\r')
768         {
769                 // translate Mac line ending to UNIX
770                 com_token[len++] = '\n';data++;
771                 com_token[len] = 0;
772                 *datapointer = data;
773                 return true;
774         }
775         else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
776         {
777                 // single character
778                 com_token[len++] = *data++;
779                 com_token[len] = 0;
780                 *datapointer = data;
781                 return true;
782         }
783         else
784         {
785                 // regular word
786                 for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
787                         if (len < (int)sizeof(com_token) - 1)
788                                 com_token[len++] = *data;
789                 com_token[len] = 0;
790                 *datapointer = data;
791                 return true;
792         }
793 }
794
795 /*
796 ==============
797 COM_ParseToken_Console
798
799 Parse a token out of a string, behaving like the qwcl console
800 ==============
801 */
802 int COM_ParseToken_Console(const char **datapointer)
803 {
804         int len;
805         const char *data = *datapointer;
806
807         len = 0;
808         com_token[0] = 0;
809
810         if (!data)
811         {
812                 *datapointer = NULL;
813                 return false;
814         }
815
816 // skip whitespace
817 skipwhite:
818         for (;ISWHITESPACE(*data);data++)
819         {
820                 if (*data == 0)
821                 {
822                         // end of file
823                         *datapointer = NULL;
824                         return false;
825                 }
826         }
827
828         if (*data == '/' && data[1] == '/')
829         {
830                 // comment
831                 while (*data && *data != '\n' && *data != '\r')
832                         data++;
833                 goto skipwhite;
834         }
835         else if (*data == '\"')
836         {
837                 // quoted string
838                 for (data++;*data && *data != '\"';data++)
839                 {
840                         // allow escaped " and \ case
841                         if (*data == '\\' && (data[1] == '\"' || data[1] == '\\'))
842                                 data++;
843                         if (len < (int)sizeof(com_token) - 1)
844                                 com_token[len++] = *data;
845                 }
846                 com_token[len] = 0;
847                 if (*data == '\"')
848                         data++;
849                 *datapointer = data;
850         }
851         else
852         {
853                 // regular word
854                 for (;!ISWHITESPACE(*data);data++)
855                         if (len < (int)sizeof(com_token) - 1)
856                                 com_token[len++] = *data;
857                 com_token[len] = 0;
858                 *datapointer = data;
859         }
860
861         return true;
862 }
863
864 /*
865 ===============
866 Com_CalcRoll
867
868 Used by view and sv_user
869 ===============
870 */
871 float Com_CalcRoll (const vec3_t angles, const vec3_t velocity, const vec_t angleval, const vec_t velocityval)
872 {
873         vec3_t  forward, right, up;
874         float   sign;
875         float   side;
876
877         AngleVectors (angles, forward, right, up);
878         side = DotProduct (velocity, right);
879         sign = side < 0 ? -1 : 1;
880         side = fabs(side);
881
882         if (side < velocityval)
883                 side = side * angleval / velocityval;
884         else
885                 side = angleval;
886
887         return side*sign;
888
889 }
890
891 //===========================================================================
892
893 /*
894 ================
895 COM_Init
896 ================
897 */
898 void COM_Init_Commands (void)
899 {
900         int i, j, n;
901         char com_cmdline[MAX_INPUTLINE];
902
903         Cvar_RegisterVariable (&registered);
904         Cvar_RegisterVariable (&cmdline);
905         Cvar_RegisterVariable(&cl_playermodel);
906         Cvar_RegisterVirtual(&cl_playermodel, "_cl_playermodel");
907         Cvar_RegisterVariable(&cl_playerskin);
908         Cvar_RegisterVirtual(&cl_playerskin, "_cl_playerskin");
909
910         // reconstitute the command line for the cmdline externally visible cvar
911         n = 0;
912         for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
913         {
914                 i = 0;
915                 if (strstr(sys.argv[j], " "))
916                 {
917                         // arg contains whitespace, store quotes around it
918                         // This condition checks whether we can allow to put
919                         // in two quote characters.
920                         if (n >= ((int)sizeof(com_cmdline) - 2))
921                                 break;
922                         com_cmdline[n++] = '\"';
923                         // This condition checks whether we can allow one
924                         // more character and a quote character.
925                         while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
926                                 // FIXME: Doesn't quote special characters.
927                                 com_cmdline[n++] = sys.argv[j][i++];
928                         com_cmdline[n++] = '\"';
929                 }
930                 else
931                 {
932                         // This condition checks whether we can allow one
933                         // more character.
934                         while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
935                                 com_cmdline[n++] = sys.argv[j][i++];
936                 }
937                 if (n < ((int)sizeof(com_cmdline) - 1))
938                         com_cmdline[n++] = ' ';
939                 else
940                         break;
941         }
942         com_cmdline[n] = 0;
943         Cvar_SetQuick(&cmdline, com_cmdline);
944 }
945
946 /*
947 ============
948 va
949
950 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
951 ============
952 */
953 char *va(char *buf, size_t buflen, const char *format, ...)
954 {
955         va_list argptr;
956
957         va_start (argptr, format);
958         dpvsnprintf (buf, buflen, format,argptr);
959         va_end (argptr);
960
961         return buf;
962 }
963
964
965 //======================================
966
967 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
968
969 #undef snprintf
970 #undef vsnprintf
971
972 #ifdef WIN32
973 # define snprintf _snprintf
974 # define vsnprintf _vsnprintf
975 #endif
976
977
978 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
979 {
980         va_list args;
981         int result;
982
983         va_start (args, format);
984         result = dpvsnprintf (buffer, buffersize, format, args);
985         va_end (args);
986
987         return result;
988 }
989
990
991 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
992 {
993         int result;
994
995 #if _MSC_VER >= 1400
996         result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
997 #else
998         result = vsnprintf (buffer, buffersize, format, args);
999 #endif
1000         if (result < 0 || (size_t)result >= buffersize)
1001         {
1002                 buffer[buffersize - 1] = '\0';
1003                 // we could be inside Con_Printf
1004                 if (result < 0)
1005                         Sys_Printf("dpvsnprintf: output error, buffer size %zu\n", buffersize);
1006                 else
1007                         Sys_Printf("dpvsnprintf: truncated to %zu bytes: \"%s\"\n", buffersize - 1, buffer);
1008                 return -1;
1009         }
1010
1011         return result;
1012 }
1013
1014
1015 //======================================
1016
1017 void COM_ToLowerString (const char *in, char *out, size_t size_out)
1018 {
1019         if (size_out == 0)
1020                 return;
1021
1022         if(utf8_enable.integer)
1023         {
1024                 *out = 0;
1025                 while(*in && size_out > 1)
1026                 {
1027                         int n;
1028                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1029                         ch = u8_tolower(ch);
1030                         n = u8_fromchar(ch, out, size_out);
1031                         if(n <= 0)
1032                                 break;
1033                         out += n;
1034                         size_out -= n;
1035                 }
1036                 return;
1037         }
1038
1039         while (*in && size_out > 1)
1040         {
1041                 if (*in >= 'A' && *in <= 'Z')
1042                         *out++ = *in++ + 'a' - 'A';
1043                 else
1044                         *out++ = *in++;
1045                 size_out--;
1046         }
1047         *out = '\0';
1048 }
1049
1050 void COM_ToUpperString (const char *in, char *out, size_t size_out)
1051 {
1052         if (size_out == 0)
1053                 return;
1054
1055         if(utf8_enable.integer)
1056         {
1057                 *out = 0;
1058                 while(*in && size_out > 1)
1059                 {
1060                         int n;
1061                         Uchar ch = u8_getchar_utf8_enabled(in, &in);
1062                         ch = u8_toupper(ch);
1063                         n = u8_fromchar(ch, out, size_out);
1064                         if(n <= 0)
1065                                 break;
1066                         out += n;
1067                         size_out -= n;
1068                 }
1069                 return;
1070         }
1071
1072         while (*in && size_out > 1)
1073         {
1074                 if (*in >= 'a' && *in <= 'z')
1075                         *out++ = *in++ + 'A' - 'a';
1076                 else
1077                         *out++ = *in++;
1078                 size_out--;
1079         }
1080         *out = '\0';
1081 }
1082
1083 int COM_StringBeginsWith(const char *s, const char *match)
1084 {
1085         for (;*s && *match;s++, match++)
1086                 if (*s != *match)
1087                         return false;
1088         return true;
1089 }
1090
1091 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1092 {
1093         int argc, commentprefixlength;
1094         char *tokenbufend;
1095         const char *l;
1096         argc = 0;
1097         tokenbufend = tokenbuf + tokenbufsize;
1098         l = *text;
1099         commentprefixlength = 0;
1100         if (commentprefix)
1101                 commentprefixlength = (int)strlen(commentprefix);
1102         while (*l && *l != '\n' && *l != '\r')
1103         {
1104                 if (!ISWHITESPACE(*l))
1105                 {
1106                         if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1107                         {
1108                                 while (*l && *l != '\n' && *l != '\r')
1109                                         l++;
1110                                 break;
1111                         }
1112                         if (argc >= maxargc)
1113                                 return -1;
1114                         argv[argc++] = tokenbuf;
1115                         if (*l == '"')
1116                         {
1117                                 l++;
1118                                 while (*l && *l != '"')
1119                                 {
1120                                         if (tokenbuf >= tokenbufend)
1121                                                 return -1;
1122                                         *tokenbuf++ = *l++;
1123                                 }
1124                                 if (*l == '"')
1125                                         l++;
1126                         }
1127                         else
1128                         {
1129                                 while (!ISWHITESPACE(*l))
1130                                 {
1131                                         if (tokenbuf >= tokenbufend)
1132                                                 return -1;
1133                                         *tokenbuf++ = *l++;
1134                                 }
1135                         }
1136                         if (tokenbuf >= tokenbufend)
1137                                 return -1;
1138                         *tokenbuf++ = 0;
1139                 }
1140                 else
1141                         l++;
1142         }
1143         // line endings:
1144         // UNIX: \n
1145         // Mac: \r
1146         // Windows: \r\n
1147         if (*l == '\r')
1148                 l++;
1149         if (*l == '\n')
1150                 l++;
1151         *text = l;
1152         return argc;
1153 }
1154
1155 /*
1156 ============
1157 COM_StringLengthNoColors
1158
1159 calculates the visible width of a color coded string.
1160
1161 *valid is filled with TRUE if the string is a valid colored string (that is, if
1162 it does not end with an unfinished color code). If it gets filled with FALSE, a
1163 fix would be adding a STRING_COLOR_TAG at the end of the string.
1164
1165 valid can be set to NULL if the caller doesn't care.
1166
1167 For size_s, specify the maximum number of characters from s to use, or 0 to use
1168 all characters until the zero terminator.
1169 ============
1170 */
1171 size_t
1172 COM_StringLengthNoColors(const char *s, size_t size_s, qbool *valid)
1173 {
1174         const char *end = size_s ? (s + size_s) : NULL;
1175         size_t len = 0;
1176         for(;;)
1177         {
1178                 switch((s == end) ? 0 : *s)
1179                 {
1180                         case 0:
1181                                 if(valid)
1182                                         *valid = true;
1183                                 return len;
1184                         case STRING_COLOR_TAG:
1185                                 ++s;
1186                                 switch((s == end) ? 0 : *s)
1187                                 {
1188                                         case STRING_COLOR_RGB_TAG_CHAR:
1189                                                 if (s+1 != end && isxdigit(s[1]) &&
1190                                                         s+2 != end && isxdigit(s[2]) &&
1191                                                         s+3 != end && isxdigit(s[3]) )
1192                                                 {
1193                                                         s+=3;
1194                                                         break;
1195                                                 }
1196                                                 ++len; // STRING_COLOR_TAG
1197                                                 ++len; // STRING_COLOR_RGB_TAG_CHAR
1198                                                 break;
1199                                         case 0: // ends with unfinished color code!
1200                                                 ++len;
1201                                                 if(valid)
1202                                                         *valid = false;
1203                                                 return len;
1204                                         case STRING_COLOR_TAG: // escaped ^
1205                                                 ++len;
1206                                                 break;
1207                                         case '0': case '1': case '2': case '3': case '4':
1208                                         case '5': case '6': case '7': case '8': case '9': // color code
1209                                                 break;
1210                                         default: // not a color code
1211                                                 ++len; // STRING_COLOR_TAG
1212                                                 ++len; // the character
1213                                                 break;
1214                                 }
1215                                 break;
1216                         default:
1217                                 ++len;
1218                                 break;
1219                 }
1220                 ++s;
1221         }
1222         // never get here
1223 }
1224
1225 /*
1226 ============
1227 COM_StringDecolorize
1228
1229 removes color codes from a string.
1230
1231 If escape_carets is true, the resulting string will be safe for printing. If
1232 escape_carets is false, the function will just strip color codes (for logging
1233 for example).
1234
1235 If the output buffer size did not suffice for converting, the function returns
1236 FALSE. Generally, if escape_carets is false, the output buffer needs
1237 strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
1238 bytes. In any case, the function makes sure that the resulting string is
1239 zero terminated.
1240
1241 For size_in, specify the maximum number of characters from in to use, or 0 to use
1242 all characters until the zero terminator.
1243 ============
1244 */
1245 qbool
1246 COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qbool escape_carets)
1247 {
1248 #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return false; } } while(0)
1249         const char *end = size_in ? (in + size_in) : NULL;
1250         if(size_out < 1)
1251                 return false;
1252         for(;;)
1253         {
1254                 switch((in == end) ? 0 : *in)
1255                 {
1256                         case 0:
1257                                 *out++ = 0;
1258                                 return true;
1259                         case STRING_COLOR_TAG:
1260                                 ++in;
1261                                 switch((in == end) ? 0 : *in)
1262                                 {
1263                                         case STRING_COLOR_RGB_TAG_CHAR:
1264                                                 if (in+1 != end && isxdigit(in[1]) &&
1265                                                         in+2 != end && isxdigit(in[2]) &&
1266                                                         in+3 != end && isxdigit(in[3]) )
1267                                                 {
1268                                                         in+=3;
1269                                                         break;
1270                                                 }
1271                                                 APPEND(STRING_COLOR_TAG);
1272                                                 if(escape_carets)
1273                                                         APPEND(STRING_COLOR_TAG);
1274                                                 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1275                                                 break;
1276                                         case 0: // ends with unfinished color code!
1277                                                 APPEND(STRING_COLOR_TAG);
1278                                                 // finish the code by appending another caret when escaping
1279                                                 if(escape_carets)
1280                                                         APPEND(STRING_COLOR_TAG);
1281                                                 *out++ = 0;
1282                                                 return true;
1283                                         case STRING_COLOR_TAG: // escaped ^
1284                                                 APPEND(STRING_COLOR_TAG);
1285                                                 // append a ^ twice when escaping
1286                                                 if(escape_carets)
1287                                                         APPEND(STRING_COLOR_TAG);
1288                                                 break;
1289                                         case '0': case '1': case '2': case '3': case '4':
1290                                         case '5': case '6': case '7': case '8': case '9': // color code
1291                                                 break;
1292                                         default: // not a color code
1293                                                 APPEND(STRING_COLOR_TAG);
1294                                                 APPEND(*in);
1295                                                 break;
1296                                 }
1297                                 break;
1298                         default:
1299                                 APPEND(*in);
1300                                 break;
1301                 }
1302                 ++in;
1303         }
1304         // never get here
1305 #undef APPEND
1306 }
1307
1308
1309 /*
1310 ============
1311 String Copying
1312
1313 The glibc implementation of memccpy is several times faster than old functions that
1314 copy one byte at a time (even at -O3) and its advantage increases with string length.
1315 ============
1316 */
1317 #ifdef WIN32
1318         // memccpy() is standard in POSIX.1-2001, POSIX.1-2008, SVr4, 4.3BSD, C23.
1319         // Microsoft supports it, but apparently complains if we use it.
1320         #pragma warning(disable : 4996)
1321 #endif
1322
1323 /** Chain-copies a string with truncation and efficiency (compared to strlcat()).
1324  * The destination ends at an absolute pointer instead of a relative offset
1325  * and a pointer to the \0 terminator is returned on success.
1326  * Truncates, warns, and returns the end pointer on overflow or unterminated source.
1327  * Guarantees \0 termination.    end = dst + sizeof(src[])
1328  */
1329 char *dp_stpecpy(char *dst, char *end, const char *src)
1330 {
1331         char *p = (char *)memccpy(dst, src, '\0', end - dst);
1332
1333         if (p)
1334                 return p - 1;
1335         end[-1] = '\0';
1336         Con_Printf(CON_WARN "%s: src string unterminated or truncated to %zu bytes: \"%s\"\n", __func__, dst == end ? 0 : (end - dst) - 1, dst);
1337         return end;
1338 }
1339
1340 /** Copies a measured byte sequence (unterminated string) to a null-terminated string.
1341  * Returns a pointer to the \0 terminator. Guarantees \0 termination.
1342  * Compared to ustr2stp(): truncates and warns on overflow.
1343  */
1344 char *dp_ustr2stp(char *dst, size_t dsize, const char *src, size_t ssize)
1345 {
1346         if (ssize >= dsize)
1347         {
1348                 ssize = dsize - 1;
1349                 Con_Printf(CON_WARN "%s: src string truncated to %zu bytes: \"%.*s\"\n", __func__, ssize, (int)ssize, src);
1350         }
1351         memcpy(dst, src, ssize);
1352         dst[ssize] = '\0';
1353         return &dst[ssize];
1354 }
1355
1356 /** Copies a string, like strlcpy() but with a better return: the number of bytes copied
1357  * excluding the \0 terminator. Truncates and warns on overflow or unterminated source,
1358  * whereas strlcpy() truncates silently and overreads (possibly segfaulting).
1359  * Guarantees \0 termination.
1360  * See also: dp_stpecpy() and dp_ustr2stp().
1361  */
1362 size_t dp__strlcpy(char *dst, const char *src, size_t dsize, const char *func, unsigned line)
1363 {
1364         char *p = (char *)memccpy(dst, src, '\0', dsize);
1365
1366         if (p)
1367                 return (p - 1) - dst;
1368         dst[dsize - 1] = '\0';
1369         Con_Printf(CON_WARN "%s:%u: src string unterminated or truncated to %zu bytes: \"%s\"\n", func, line, dsize - 1, dst);
1370         return dsize - 1;
1371 }
1372
1373 /** Catenates a string, like strlcat() but with a better return: the number of bytes copied
1374  * excluding the \0 terminator. Truncates and warns on overflow or unterminated source,
1375  * whereas strlcat() truncates silently and overreads (possibly segfaulting).
1376  * Guarantees \0 termination.
1377  * Inefficient like any strcat(), please use memcpy(), dp_stpecpy() or dp_strlcpy() instead.
1378  */
1379 size_t dp__strlcat(char *dst, const char *src, size_t dsize, const char *func, unsigned line)
1380 {
1381         char *p = (char *)memchr(dst, '\0', dsize) ?: dst;
1382         size_t offset = p - dst;
1383
1384         return dp__strlcpy(p, src, dsize - offset, func, line) + offset;
1385 }
1386
1387
1388
1389 void FindFraction(double val, int *num, int *denom, int denomMax)
1390 {
1391         int i;
1392         double bestdiff;
1393         // initialize
1394         bestdiff = fabs(val);
1395         *num = 0;
1396         *denom = 1;
1397
1398         for(i = 1; i <= denomMax; ++i)
1399         {
1400                 int inum = (int) floor(0.5 + val * i);
1401                 double diff = fabs(val - inum / (double)i);
1402                 if(diff < bestdiff)
1403                 {
1404                         bestdiff = diff;
1405                         *num = inum;
1406                         *denom = i;
1407                 }
1408         }
1409 }
1410
1411 // decodes an XPM from C syntax
1412 char **XPM_DecodeString(const char *in)
1413 {
1414         static char *tokens[257];
1415         static char lines[257][512];
1416         size_t line = 0;
1417
1418         // skip until "{" token
1419         while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1420
1421         // now, read in succession: string, comma-or-}
1422         while(COM_ParseToken_QuakeC(&in, false))
1423         {
1424                 tokens[line] = lines[line];
1425                 dp_strlcpy(lines[line++], com_token, sizeof(lines[0]));
1426                 if(!COM_ParseToken_QuakeC(&in, false))
1427                         return NULL;
1428                 if(!strcmp(com_token, "}"))
1429                         break;
1430                 if(strcmp(com_token, ","))
1431                         return NULL;
1432                 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1433                         return NULL;
1434         }
1435
1436         return tokens;
1437 }
1438
1439 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1440 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
1441 {
1442         unsigned char i0 = (bytes > 0) ? in[0] : 0;
1443         unsigned char i1 = (bytes > 1) ? in[1] : 0;
1444         unsigned char i2 = (bytes > 2) ? in[2] : 0;
1445         unsigned char o0 = base64[i0 >> 2];
1446         unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
1447         unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
1448         unsigned char o3 = base64[i2 & 077];
1449         out[0] = (bytes > 0) ? o0 : '?';
1450         out[1] = (bytes > 0) ? o1 : '?';
1451         out[2] = (bytes > 1) ? o2 : '=';
1452         out[3] = (bytes > 2) ? o3 : '=';
1453 }
1454
1455 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1456 {
1457         size_t blocks, i;
1458         // expand the out-buffer
1459         blocks = (buflen + 2) / 3;
1460         if(blocks*4 > outbuflen)
1461                 return 0;
1462         for(i = blocks; i > 0; )
1463         {
1464                 --i;
1465                 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));
1466         }
1467         return blocks * 4;
1468 }