2 Copyright (C) 1996-1997 Id Software, Inc.
3 Copyright (C) 2000-2020 DarkPlaces contributors
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.
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.
14 See the GNU General Public License for more details.
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.
21 // common.c -- misc functions used in client and server
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"};
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"};
39 char com_token[MAX_INPUTLINE];
41 //===========================================================================
43 void SZ_Clear (sizebuf_t *buf)
48 unsigned char *SZ_GetSpace (sizebuf_t *buf, int length)
52 if (buf->cursize + length > buf->maxsize)
54 if (!buf->allowoverflow)
55 Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
57 if (length > buf->maxsize)
58 Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
60 buf->overflowed = true;
61 Con_Print("SZ_GetSpace: overflow\n");
65 data = buf->data + buf->cursize;
66 buf->cursize += length;
71 void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
73 memcpy (SZ_GetSpace(buf,length),data,length);
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
80 static const char *hexchar = "0123456789ABCDEF";
81 void Com_HexDumpToConsole(const unsigned char *data, int size)
85 char *cur, *flushpointer;
86 const unsigned char *d;
88 flushpointer = text + 512;
96 *cur++ = hexchar[(i >> 12) & 15];
97 *cur++ = hexchar[(i >> 8) & 15];
98 *cur++ = hexchar[(i >> 4) & 15];
99 *cur++ = hexchar[(i >> 0) & 15];
102 for (j = 0;j < 16;j++)
106 *cur++ = hexchar[(d[j] >> 4) & 15];
107 *cur++ = hexchar[(d[j] >> 0) & 15];
118 for (j = 0;j < 16;j++)
122 // color change prefix character has to be treated specially
123 if (d[j] == STRING_COLOR_TAG)
125 *cur++ = STRING_COLOR_TAG;
126 *cur++ = STRING_COLOR_TAG;
128 else if (d[j] >= (unsigned char) ' ')
138 if (cur >= flushpointer || i >= size)
147 void SZ_HexDumpToConsole(const sizebuf_t *buf)
149 Com_HexDumpToConsole(buf->data, buf->cursize);
153 //============================================================================
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.
165 In case no single character fits into the given width, the wordWidth function
166 must return the width of exactly one character.
168 Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
170 The sum of the return values of the processLine function will be returned.
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)
175 // Logic is as follows:
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.
184 qbool isContinuation = false;
186 const char *startOfLine = string;
187 const char *cursor = string;
188 const char *end = string + length;
189 float spaceUsedInLine = 0;
190 float spaceUsedForWord;
196 wordWidth(passthroughCW, NULL, &dummy, -1);
198 spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1);
202 char ch = (cursor < end) ? *cursor : 0;
205 case 0: // end of string
206 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
208 case '\n': // end of line
209 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
210 isContinuation = false;
212 startOfLine = cursor;
216 spaceUsedInLine += spaceWidth;
220 while(cursor + wordLen < end)
222 switch(cursor[wordLen])
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
238 spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself
240 if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine)
242 // we can simply append it
244 spaceUsedInLine += spaceUsedForWord;
248 // output current line
249 result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
250 isContinuation = true;
251 startOfLine = cursor;
253 spaceUsedInLine = continuationWidth + spaceUsedForWord;
262 qbool isContinuation = false;
263 float currentWordSpace = 0;
264 const char *currentWord = 0;
265 float minReserve = 0;
267 float spaceUsedInLine = 0;
268 const char *currentLine = 0;
269 const char *currentLineEnd = 0;
270 float currentLineFinalWhitespace = 0;
274 minReserve = charWidth(passthroughCW, 0);
275 minReserve += charWidth(passthroughCW, ' ');
277 if(maxWidth < continuationWidth + minReserve)
278 maxWidth = continuationWidth + minReserve;
280 charWidth(passthroughCW, 0);
282 for(p = string; p < string + length; ++p)
285 float w = charWidth(passthroughCW, c);
290 currentWordSpace = 0;
296 spaceUsedInLine = isContinuation ? continuationWidth : 0;
302 // 1. I can add the word AND a space - then just append it.
303 if(spaceUsedInLine + currentWordSpace + w <= maxWidth)
305 currentLineEnd = p; // note: space not included here
306 currentLineFinalWhitespace = w;
307 spaceUsedInLine += currentWordSpace + w;
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)
312 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
314 isContinuation = true;
316 // 3. Otherwise, output current line and go to next one, where I can add the word.
317 else if(continuationWidth + currentWordSpace + w <= maxWidth)
320 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
321 currentLine = currentWord;
322 spaceUsedInLine = continuationWidth + currentWordSpace + w;
324 currentLineFinalWhitespace = w;
325 isContinuation = true;
327 // 4. We can't even do that? Then output both current and next word as new lines.
332 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
333 isContinuation = true;
335 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
337 isContinuation = true;
343 // 1. I can add the word - then do it.
344 if(spaceUsedInLine + currentWordSpace <= maxWidth)
346 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
348 // 2. Otherwise, output current line, next one and make tabula rasa.
353 processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
354 isContinuation = true;
356 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
360 isContinuation = false;
364 currentWordSpace += w;
366 spaceUsedInLine + currentWordSpace > maxWidth // can't join this line...
368 continuationWidth + currentWordSpace > maxWidth // can't join any other line...
371 // this word cannot join ANY line...
372 // so output the current line...
375 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
376 isContinuation = true;
379 // then this word's beginning...
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;
387 // reset the char width function to a state where no kerning occurs (start of word)
388 charWidth(passthroughCW, ' ');
391 float w = charWidth(passthroughCW, *pos);
392 if(currentWordSpace + w > pieceWidth) // this piece won't fit any more
394 // print everything until it
395 result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true);
398 currentWordSpace = 0;
400 currentWordSpace += w;
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;
409 isContinuation = true;
413 // we have a guarantee that it will fix (see if clause)
414 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation);
416 // and use the rest of this word as new start of a line
417 currentWordSpace = w;
419 spaceUsedInLine = continuationWidth;
422 isContinuation = true;
431 currentWordSpace = 0;
434 if(currentLine) // Same procedure as \n
436 // Can I append the current word?
437 if(spaceUsedInLine + currentWordSpace <= maxWidth)
438 result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
443 result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
444 isContinuation = true;
446 result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
456 COM_ParseToken_Simple
458 Parse a token out of a string
461 int COM_ParseToken_Simple(const char **datapointer, qbool returnnewline, qbool parsebackslash, qbool parsecomments)
465 const char *data = *datapointer;
482 for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
492 // handle Windows line ending
493 if (data[0] == '\r' && data[1] == '\n')
496 if (parsecomments && data[0] == '/' && data[1] == '/')
499 while (*data && *data != '\n' && *data != '\r')
503 else if (parsecomments && data[0] == '/' && data[1] == '*')
507 while (*data && (data[0] != '*' || data[1] != '/'))
515 else if (*data == '\"')
518 for (data++;*data && *data != '\"';data++)
521 if (*data == '\\' && parsebackslash)
530 if (len < (int)sizeof(com_token) - 1)
531 com_token[len++] = c;
539 else if (*data == '\r')
541 // translate Mac line ending to UNIX
542 com_token[len++] = '\n';data++;
547 else if (*data == '\n')
550 com_token[len++] = *data++;
558 for (;!ISWHITESPACE(*data);data++)
559 if (len < (int)sizeof(com_token) - 1)
560 com_token[len++] = *data;
569 COM_ParseToken_QuakeC
571 Parse a token out of a string
574 int COM_ParseToken_QuakeC(const char **datapointer, qbool returnnewline)
578 const char *data = *datapointer;
595 for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
605 // handle Windows line ending
606 if (data[0] == '\r' && data[1] == '\n')
609 if (data[0] == '/' && data[1] == '/')
612 while (*data && *data != '\n' && *data != '\r')
616 else if (data[0] == '/' && data[1] == '*')
620 while (*data && (data[0] != '*' || data[1] != '/'))
628 else if (*data == '\"' || *data == '\'')
632 for (data++;*data && *data != quote;data++)
644 if (len < (int)sizeof(com_token) - 1)
645 com_token[len++] = c;
653 else if (*data == '\r')
655 // translate Mac line ending to UNIX
656 com_token[len++] = '\n';data++;
661 else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
664 com_token[len++] = *data++;
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;
683 COM_ParseToken_VM_Tokenize
685 Parse a token out of a string
688 int COM_ParseToken_VM_Tokenize(const char **datapointer, qbool returnnewline)
692 const char *data = *datapointer;
709 for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
719 // handle Windows line ending
720 if (data[0] == '\r' && data[1] == '\n')
723 if (data[0] == '/' && data[1] == '/')
726 while (*data && *data != '\n' && *data != '\r')
730 else if (data[0] == '/' && data[1] == '*')
734 while (*data && (data[0] != '*' || data[1] != '/'))
742 else if (*data == '\"' || *data == '\'')
746 for (data++;*data && *data != quote;data++)
758 if (len < (int)sizeof(com_token) - 1)
759 com_token[len++] = c;
767 else if (*data == '\r')
769 // translate Mac line ending to UNIX
770 com_token[len++] = '\n';data++;
775 else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
778 com_token[len++] = *data++;
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;
797 COM_ParseToken_Console
799 Parse a token out of a string, behaving like the qwcl console
802 int COM_ParseToken_Console(const char **datapointer)
805 const char *data = *datapointer;
818 for (;ISWHITESPACE(*data);data++)
828 if (*data == '/' && data[1] == '/')
831 while (*data && *data != '\n' && *data != '\r')
835 else if (*data == '\"')
838 for (data++;*data && *data != '\"';data++)
840 // allow escaped " and \ case
841 if (*data == '\\' && (data[1] == '\"' || data[1] == '\\'))
843 if (len < (int)sizeof(com_token) - 1)
844 com_token[len++] = *data;
854 for (;!ISWHITESPACE(*data);data++)
855 if (len < (int)sizeof(com_token) - 1)
856 com_token[len++] = *data;
868 Used by view and sv_user
871 float Com_CalcRoll (const vec3_t angles, const vec3_t velocity, const vec_t angleval, const vec_t velocityval)
873 vec3_t forward, right, up;
877 AngleVectors (angles, forward, right, up);
878 side = DotProduct (velocity, right);
879 sign = side < 0 ? -1 : 1;
882 if (side < velocityval)
883 side = side * angleval / velocityval;
891 //===========================================================================
898 void COM_Init_Commands (void)
901 char com_cmdline[MAX_INPUTLINE];
903 Cvar_RegisterVariable (®istered);
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");
910 // reconstitute the command line for the cmdline externally visible cvar
912 for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
915 if (strstr(sys.argv[j], " "))
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))
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++] = '\"';
932 // This condition checks whether we can allow one
934 while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
935 com_cmdline[n++] = sys.argv[j][i++];
937 if (n < ((int)sizeof(com_cmdline) - 1))
938 com_cmdline[n++] = ' ';
943 Cvar_SetQuick(&cmdline, com_cmdline);
950 varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
953 char *va(char *buf, size_t buflen, const char *format, ...)
957 va_start (argptr, format);
958 dpvsnprintf (buf, buflen, format,argptr);
965 //======================================
967 // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
973 # define snprintf _snprintf
974 # define vsnprintf _vsnprintf
978 int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
983 va_start (args, format);
984 result = dpvsnprintf (buffer, buffersize, format, args);
991 int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
996 result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
998 result = vsnprintf (buffer, buffersize, format, args);
1000 if (result < 0 || (size_t)result >= buffersize)
1002 buffer[buffersize - 1] = '\0';
1003 // we could be inside Con_Printf
1005 Sys_Printf("dpvsnprintf: output error, buffer size %zu\n", buffersize);
1007 Sys_Printf("dpvsnprintf: truncated to %zu bytes: \"%s\"\n", buffersize - 1, buffer);
1015 //======================================
1017 void COM_ToLowerString (const char *in, char *out, size_t size_out)
1022 if(utf8_enable.integer)
1025 while(*in && size_out > 1)
1028 Uchar ch = u8_getchar_utf8_enabled(in, &in);
1029 ch = u8_tolower(ch);
1030 n = u8_fromchar(ch, out, size_out);
1039 while (*in && size_out > 1)
1041 if (*in >= 'A' && *in <= 'Z')
1042 *out++ = *in++ + 'a' - 'A';
1050 void COM_ToUpperString (const char *in, char *out, size_t size_out)
1055 if(utf8_enable.integer)
1058 while(*in && size_out > 1)
1061 Uchar ch = u8_getchar_utf8_enabled(in, &in);
1062 ch = u8_toupper(ch);
1063 n = u8_fromchar(ch, out, size_out);
1072 while (*in && size_out > 1)
1074 if (*in >= 'a' && *in <= 'z')
1075 *out++ = *in++ + 'A' - 'a';
1083 int COM_StringBeginsWith(const char *s, const char *match)
1085 for (;*s && *match;s++, match++)
1091 int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
1093 int argc, commentprefixlength;
1097 tokenbufend = tokenbuf + tokenbufsize;
1099 commentprefixlength = 0;
1101 commentprefixlength = (int)strlen(commentprefix);
1102 while (*l && *l != '\n' && *l != '\r')
1104 if (!ISWHITESPACE(*l))
1106 if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
1108 while (*l && *l != '\n' && *l != '\r')
1112 if (argc >= maxargc)
1114 argv[argc++] = tokenbuf;
1118 while (*l && *l != '"')
1120 if (tokenbuf >= tokenbufend)
1129 while (!ISWHITESPACE(*l))
1131 if (tokenbuf >= tokenbufend)
1136 if (tokenbuf >= tokenbufend)
1157 COM_StringLengthNoColors
1159 calculates the visible width of a color coded string.
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.
1165 valid can be set to NULL if the caller doesn't care.
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.
1172 COM_StringLengthNoColors(const char *s, size_t size_s, qbool *valid)
1174 const char *end = size_s ? (s + size_s) : NULL;
1178 switch((s == end) ? 0 : *s)
1184 case STRING_COLOR_TAG:
1186 switch((s == end) ? 0 : *s)
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]) )
1196 ++len; // STRING_COLOR_TAG
1197 ++len; // STRING_COLOR_RGB_TAG_CHAR
1199 case 0: // ends with unfinished color code!
1204 case STRING_COLOR_TAG: // escaped ^
1207 case '0': case '1': case '2': case '3': case '4':
1208 case '5': case '6': case '7': case '8': case '9': // color code
1210 default: // not a color code
1211 ++len; // STRING_COLOR_TAG
1212 ++len; // the character
1227 COM_StringDecolorize
1229 removes color codes from a string.
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
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
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.
1246 COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qbool escape_carets)
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;
1254 switch((in == end) ? 0 : *in)
1259 case STRING_COLOR_TAG:
1261 switch((in == end) ? 0 : *in)
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]) )
1271 APPEND(STRING_COLOR_TAG);
1273 APPEND(STRING_COLOR_TAG);
1274 APPEND(STRING_COLOR_RGB_TAG_CHAR);
1276 case 0: // ends with unfinished color code!
1277 APPEND(STRING_COLOR_TAG);
1278 // finish the code by appending another caret when escaping
1280 APPEND(STRING_COLOR_TAG);
1283 case STRING_COLOR_TAG: // escaped ^
1284 APPEND(STRING_COLOR_TAG);
1285 // append a ^ twice when escaping
1287 APPEND(STRING_COLOR_TAG);
1289 case '0': case '1': case '2': case '3': case '4':
1290 case '5': case '6': case '7': case '8': case '9': // color code
1292 default: // not a color code
1293 APPEND(STRING_COLOR_TAG);
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.
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)
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[])
1329 char *dp_stpecpy(char *dst, char *end, const char *src)
1331 char *p = (char *)memccpy(dst, src, '\0', end - dst);
1336 Con_Printf(CON_WARN "%s: src string unterminated or truncated to %zu bytes: \"%s\"\n", __func__, dst == end ? 0 : (end - dst) - 1, dst);
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.
1344 char *dp_ustr2stp(char *dst, size_t dsize, const char *src, size_t ssize)
1349 Con_Printf(CON_WARN "%s: src string truncated to %zu bytes: \"%.*s\"\n", __func__, ssize, (int)ssize, src);
1351 memcpy(dst, src, ssize);
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().
1362 size_t dp__strlcpy(char *dst, const char *src, size_t dsize, const char *func, unsigned line)
1364 char *p = (char *)memccpy(dst, src, '\0', dsize);
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);
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.
1379 size_t dp__strlcat(char *dst, const char *src, size_t dsize, const char *func, unsigned line)
1381 char *p = (char *)memchr(dst, '\0', dsize) ?: dst;
1382 size_t offset = p - dst;
1384 return dp__strlcpy(p, src, dsize - offset, func, line) + offset;
1389 void FindFraction(double val, int *num, int *denom, int denomMax)
1394 bestdiff = fabs(val);
1398 for(i = 1; i <= denomMax; ++i)
1400 int inum = (int) floor(0.5 + val * i);
1401 double diff = fabs(val - inum / (double)i);
1411 // decodes an XPM from C syntax
1412 char **XPM_DecodeString(const char *in)
1414 static char *tokens[257];
1415 static char lines[257][512];
1418 // skip until "{" token
1419 while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
1421 // now, read in succession: string, comma-or-}
1422 while(COM_ParseToken_QuakeC(&in, false))
1424 tokens[line] = lines[line];
1425 dp_strlcpy(lines[line++], com_token, sizeof(lines[0]));
1426 if(!COM_ParseToken_QuakeC(&in, false))
1428 if(!strcmp(com_token, "}"))
1430 if(strcmp(com_token, ","))
1432 if(line >= sizeof(tokens) / sizeof(tokens[0]))
1439 static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1440 static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
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 : '=';
1455 size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
1458 // expand the out-buffer
1459 blocks = (buflen + 2) / 3;
1460 if(blocks*4 > outbuflen)
1462 for(i = blocks; i > 0; )
1465 base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));