+
+int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix)
+{
+ int argc, commentprefixlength;
+ char *tokenbufend;
+ const char *l;
+ argc = 0;
+ tokenbufend = tokenbuf + tokenbufsize;
+ l = *text;
+ commentprefixlength = 0;
+ if (commentprefix)
+ commentprefixlength = (int)strlen(commentprefix);
+ while (*l && *l != '\n' && *l != '\r')
+ {
+ if (!ISWHITESPACE(*l))
+ {
+ if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
+ {
+ while (*l && *l != '\n' && *l != '\r')
+ l++;
+ break;
+ }
+ if (argc >= maxargc)
+ return -1;
+ argv[argc++] = tokenbuf;
+ if (*l == '"')
+ {
+ l++;
+ while (*l && *l != '"')
+ {
+ if (tokenbuf >= tokenbufend)
+ return -1;
+ *tokenbuf++ = *l++;
+ }
+ if (*l == '"')
+ l++;
+ }
+ else
+ {
+ while (!ISWHITESPACE(*l))
+ {
+ if (tokenbuf >= tokenbufend)
+ return -1;
+ *tokenbuf++ = *l++;
+ }
+ }
+ if (tokenbuf >= tokenbufend)
+ return -1;
+ *tokenbuf++ = 0;
+ }
+ else
+ l++;
+ }
+ // line endings:
+ // UNIX: \n
+ // Mac: \r
+ // Windows: \r\n
+ if (*l == '\r')
+ l++;
+ if (*l == '\n')
+ l++;
+ *text = l;
+ return argc;
+}
+
+/*
+============
+COM_StringLengthNoColors
+
+calculates the visible width of a color coded string.
+
+*valid is filled with TRUE if the string is a valid colored string (that is, if
+it does not end with an unfinished color code). If it gets filled with FALSE, a
+fix would be adding a STRING_COLOR_TAG at the end of the string.
+
+valid can be set to NULL if the caller doesn't care.
+
+For size_s, specify the maximum number of characters from s to use, or 0 to use
+all characters until the zero terminator.
+============
+*/
+size_t
+COM_StringLengthNoColors(const char *s, size_t size_s, qbool *valid)
+{
+ const char *end = size_s ? (s + size_s) : NULL;
+ size_t len = 0;
+ for(;;)
+ {
+ switch((s == end) ? 0 : *s)
+ {
+ case 0:
+ if(valid)
+ *valid = true;
+ return len;
+ case STRING_COLOR_TAG:
+ ++s;
+ switch((s == end) ? 0 : *s)
+ {
+ case STRING_COLOR_RGB_TAG_CHAR:
+ if (s+1 != end && isxdigit(s[1]) &&
+ s+2 != end && isxdigit(s[2]) &&
+ s+3 != end && isxdigit(s[3]) )
+ {
+ s+=3;
+ break;
+ }
+ ++len; // STRING_COLOR_TAG
+ ++len; // STRING_COLOR_RGB_TAG_CHAR
+ break;
+ case 0: // ends with unfinished color code!
+ ++len;
+ if(valid)
+ *valid = false;
+ return len;
+ case STRING_COLOR_TAG: // escaped ^
+ ++len;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': // color code
+ break;
+ default: // not a color code
+ ++len; // STRING_COLOR_TAG
+ ++len; // the character
+ break;
+ }
+ break;
+ default:
+ ++len;
+ break;
+ }
+ ++s;
+ }
+ // never get here
+}
+
+/*
+============
+COM_StringDecolorize
+
+removes color codes from a string.
+
+If escape_carets is true, the resulting string will be safe for printing. If
+escape_carets is false, the function will just strip color codes (for logging
+for example).
+
+If the output buffer size did not suffice for converting, the function returns
+FALSE. Generally, if escape_carets is false, the output buffer needs
+strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2
+bytes. In any case, the function makes sure that the resulting string is
+zero terminated.
+
+For size_in, specify the maximum number of characters from in to use, or 0 to use
+all characters until the zero terminator.
+============
+*/
+qbool
+COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qbool escape_carets)
+{
+#define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return false; } } while(0)
+ const char *end = size_in ? (in + size_in) : NULL;
+ if(size_out < 1)
+ return false;
+ for(;;)
+ {
+ switch((in == end) ? 0 : *in)
+ {
+ case 0:
+ *out++ = 0;
+ return true;
+ case STRING_COLOR_TAG:
+ ++in;
+ switch((in == end) ? 0 : *in)
+ {
+ case STRING_COLOR_RGB_TAG_CHAR:
+ if (in+1 != end && isxdigit(in[1]) &&
+ in+2 != end && isxdigit(in[2]) &&
+ in+3 != end && isxdigit(in[3]) )
+ {
+ in+=3;
+ break;
+ }
+ APPEND(STRING_COLOR_TAG);
+ if(escape_carets)
+ APPEND(STRING_COLOR_TAG);
+ APPEND(STRING_COLOR_RGB_TAG_CHAR);
+ break;
+ case 0: // ends with unfinished color code!
+ APPEND(STRING_COLOR_TAG);
+ // finish the code by appending another caret when escaping
+ if(escape_carets)
+ APPEND(STRING_COLOR_TAG);
+ *out++ = 0;
+ return true;
+ case STRING_COLOR_TAG: // escaped ^
+ APPEND(STRING_COLOR_TAG);
+ // append a ^ twice when escaping
+ if(escape_carets)
+ APPEND(STRING_COLOR_TAG);
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': // color code
+ break;
+ default: // not a color code
+ APPEND(STRING_COLOR_TAG);
+ APPEND(*in);
+ break;
+ }
+ break;
+ default:
+ APPEND(*in);
+ break;
+ }
+ ++in;
+ }
+ // never get here
+#undef APPEND
+}
+
+char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
+{
+ int pos = 0, j;
+ size_t keylength;
+ if (!key)
+ key = "";
+ keylength = strlen(key);
+ if (valuelength < 1 || !value)
+ {
+ Con_Printf("InfoString_GetValue: no room in value\n");
+ return NULL;
+ }
+ value[0] = 0;
+ if (strchr(key, '\\'))
+ {
+ Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
+ return NULL;
+ }
+ if (strchr(key, '\"'))
+ {
+ Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
+ return NULL;
+ }
+ if (!key[0])
+ {
+ Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
+ return NULL;
+ }
+ while (buffer[pos] == '\\')
+ {
+ if (!memcmp(buffer + pos+1, key, keylength) &&
+ (buffer[pos+1 + keylength] == 0 ||
+ buffer[pos+1 + keylength] == '\\'))
+ {
+ pos += 1 + (int)keylength; // Skip \key
+ if (buffer[pos] == '\\') pos++; // Skip \ before value.
+ for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
+ value[j] = buffer[pos+j];
+ value[j] = 0;
+ return value;
+ }
+ if (buffer[pos] == '\\') pos++; // Skip \ before value.
+ for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
+ if (buffer[pos] == '\\') pos++; // Skip \ before value.
+ for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
+ }
+ // if we reach this point the key was not found
+ return NULL;
+}
+
+void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value)
+{
+ int pos = 0, pos2;
+ size_t keylength;
+ if (!key)
+ key = "";
+ if (!value)
+ value = "";
+ keylength = strlen(key);
+ if (strchr(key, '\\') || strchr(value, '\\'))
+ {
+ Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value);
+ return;
+ }
+ if (strchr(key, '\"') || strchr(value, '\"'))
+ {
+ Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value);
+ return;
+ }
+ if (!key[0])
+ {
+ Con_Printf("InfoString_SetValue: can not set a key with no name\n");
+ return;
+ }
+ while (buffer[pos] == '\\')
+ {
+ if (!memcmp(buffer + pos+1, key, keylength) &&
+ (buffer[pos+1 + keylength] == 0 ||
+ buffer[pos+1 + keylength] == '\\'))
+ break;
+ if (buffer[pos] == '\\') pos++; // Skip \ before value.
+ for (;buffer[pos] && buffer[pos] != '\\';pos++);
+ if (buffer[pos] == '\\') pos++; // Skip \ before value.
+ for (;buffer[pos] && buffer[pos] != '\\';pos++);
+ }
+ // if we found the key, find the end of it because we will be replacing it
+ pos2 = pos;
+ if (buffer[pos] == '\\')
+ {
+ pos2 += 1 + (int)keylength; // Skip \key
+ if (buffer[pos2] == '\\') pos2++; // Skip \ before value.
+ for (;buffer[pos2] && buffer[pos2] != '\\';pos2++);
+ }
+ if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2))
+ {
+ Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value);
+ return;
+ }
+ if (value[0])
+ {
+ // set the key/value and append the remaining text
+ char tempbuffer[MAX_INPUTLINE];
+ strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer));
+ dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer);
+ }
+ else
+ {
+ // just remove the key from the text
+ strlcpy(buffer + pos, buffer + pos2, bufferlength - pos);
+ }
+}
+
+void InfoString_Print(char *buffer)
+{
+ int i;
+ char key[MAX_INPUTLINE];
+ char value[MAX_INPUTLINE];
+ while (*buffer)
+ {
+ if (*buffer != '\\')
+ {
+ Con_Printf("InfoString_Print: corrupt string\n");
+ return;
+ }
+ for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
+ if (i < (int)sizeof(key)-1)
+ key[i++] = *buffer;
+ key[i] = 0;
+ if (*buffer != '\\')
+ {
+ Con_Printf("InfoString_Print: corrupt string\n");
+ return;
+ }
+ for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++)
+ if (i < (int)sizeof(value)-1)
+ value[i++] = *buffer;
+ value[i] = 0;
+ // empty value is an error case
+ Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE");
+ }
+}
+
+//========================================================
+// strlcat and strlcpy, from OpenBSD
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */
+/* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */
+
+
+#ifndef HAVE_STRLCAT
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ register char *d = dst;
+ register const char *s = src;
+ register size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+#endif // #ifndef HAVE_STRLCAT
+
+
+#ifndef HAVE_STRLCPY
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ register char *d = dst;
+ register const char *s = src;
+ register size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+#endif // #ifndef HAVE_STRLCPY
+
+void FindFraction(double val, int *num, int *denom, int denomMax)
+{
+ int i;
+ double bestdiff;
+ // initialize
+ bestdiff = fabs(val);
+ *num = 0;
+ *denom = 1;
+
+ for(i = 1; i <= denomMax; ++i)
+ {
+ int inum = (int) floor(0.5 + val * i);
+ double diff = fabs(val - inum / (double)i);
+ if(diff < bestdiff)
+ {
+ bestdiff = diff;
+ *num = inum;
+ *denom = i;
+ }
+ }
+}
+
+// decodes an XPM from C syntax
+char **XPM_DecodeString(const char *in)
+{
+ static char *tokens[257];
+ static char lines[257][512];
+ size_t line = 0;
+
+ // skip until "{" token
+ while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{"));
+
+ // now, read in succession: string, comma-or-}
+ while(COM_ParseToken_QuakeC(&in, false))
+ {
+ tokens[line] = lines[line];
+ strlcpy(lines[line++], com_token, sizeof(lines[0]));
+ if(!COM_ParseToken_QuakeC(&in, false))
+ return NULL;
+ if(!strcmp(com_token, "}"))
+ break;
+ if(strcmp(com_token, ","))
+ return NULL;
+ if(line >= sizeof(tokens) / sizeof(tokens[0]))
+ return NULL;
+ }
+
+ return tokens;
+}
+
+static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes)
+{
+ unsigned char i0 = (bytes > 0) ? in[0] : 0;
+ unsigned char i1 = (bytes > 1) ? in[1] : 0;
+ unsigned char i2 = (bytes > 2) ? in[2] : 0;
+ unsigned char o0 = base64[i0 >> 2];
+ unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077];
+ unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077];
+ unsigned char o3 = base64[i2 & 077];
+ out[0] = (bytes > 0) ? o0 : '?';
+ out[1] = (bytes > 0) ? o1 : '?';
+ out[2] = (bytes > 1) ? o2 : '=';
+ out[3] = (bytes > 2) ? o3 : '=';
+}
+
+size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen)
+{
+ size_t blocks, i;
+ // expand the out-buffer
+ blocks = (buflen + 2) / 3;
+ if(blocks*4 > outbuflen)
+ return 0;
+ for(i = blocks; i > 0; )
+ {
+ --i;
+ base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i));
+ }
+ return blocks * 4;
+}