/*
Copyright (C) 1996-1997 Id Software, Inc.
+Copyright (C) 2000-2020 DarkPlaces contributors
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
#endif
#include "quakedef.h"
+#include "utf8lib.h"
-cvar_t registered = {0, "registered","0"};
-cvar_t cmdline = {0, "cmdline","0"};
+cvar_t registered = {CF_CLIENT | CF_SERVER, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
+cvar_t cmdline = {CF_CLIENT | CF_SERVER, "cmdline","0", "contains commandline the engine was launched with"};
-extern qboolean fs_modified; // set true if using non-id files
+// FIXME: Find a better place for these.
+cvar_t cl_playermodel = {CF_CLIENT | CF_SERVER | CF_USERINFO | CF_ARCHIVE, "playermodel", "", "current player model in Nexuiz/Xonotic"};
+cvar_t cl_playerskin = {CF_CLIENT | CF_SERVER | CF_USERINFO | CF_ARCHIVE, "playerskin", "", "current player skin in Nexuiz/Xonotic"};
-char com_token[1024];
-int com_argc;
-const char **com_argv;
+char com_token[MAX_INPUTLINE];
-// LordHavoc: made commandline 1024 characters instead of 256
-#define CMDLINE_LENGTH 1024
-char com_cmdline[CMDLINE_LENGTH];
-
-int gamemode;
-char *gamename;
-char *gamedirname;
-char com_modname[MAX_OSPATH];
-
-
-/*
-============================================================================
-
- BYTE ORDER FUNCTIONS
-
-============================================================================
-*/
-
-#if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
-short (*BigShort) (short l);
-short (*LittleShort) (short l);
-int (*BigLong) (int l);
-int (*LittleLong) (int l);
-float (*BigFloat) (float l);
-float (*LittleFloat) (float l);
-#endif
-
-short ShortSwap (short l)
-{
- qbyte b1,b2;
-
- b1 = l&255;
- b2 = (l>>8)&255;
-
- return (b1<<8) + b2;
-}
+//===========================================================================
-#if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
-short ShortNoSwap (short l)
+void SZ_Clear (sizebuf_t *buf)
{
- return l;
+ buf->cursize = 0;
}
-#endif
-int LongSwap (int l)
+unsigned char *SZ_GetSpace (sizebuf_t *buf, int length)
{
- qbyte b1,b2,b3,b4;
-
- b1 = l&255;
- b2 = (l>>8)&255;
- b3 = (l>>16)&255;
- b4 = (l>>24)&255;
+ unsigned char *data;
- return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4;
-}
+ if (buf->cursize + length > buf->maxsize)
+ {
+ if (!buf->allowoverflow)
+ Host_Error ("SZ_GetSpace: overflow without allowoverflow set");
-#if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
-int LongNoSwap (int l)
-{
- return l;
-}
-#endif
+ if (length > buf->maxsize)
+ Host_Error ("SZ_GetSpace: %i is > full buffer size", length);
-float FloatSwap (float f)
-{
- union
- {
- float f;
- qbyte b[4];
- } dat1, dat2;
+ buf->overflowed = true;
+ Con_Print("SZ_GetSpace: overflow\n");
+ SZ_Clear (buf);
+ }
+ data = buf->data + buf->cursize;
+ buf->cursize += length;
- dat1.f = f;
- dat2.b[0] = dat1.b[3];
- dat2.b[1] = dat1.b[2];
- dat2.b[2] = dat1.b[1];
- dat2.b[3] = dat1.b[0];
- return dat2.f;
+ return data;
}
-#if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
-float FloatNoSwap (float f)
+void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length)
{
- return f;
+ memcpy (SZ_GetSpace(buf,length),data,length);
}
-#endif
-
-// Extract integers from buffers
+// LadyHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
+// attention, it has been eradicated from here, its only (former) use in
+// all of darkplaces.
-unsigned int BuffBigLong (const qbyte *buffer)
+static const char *hexchar = "0123456789ABCDEF";
+void Com_HexDumpToConsole(const unsigned char *data, int size)
{
- return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
-}
-
-unsigned short BuffBigShort (const qbyte *buffer)
-{
- return (buffer[0] << 8) | buffer[1];
+ int i, j, n;
+ char text[1024];
+ char *cur, *flushpointer;
+ const unsigned char *d;
+ cur = text;
+ flushpointer = text + 512;
+ for (i = 0;i < size;)
+ {
+ n = 16;
+ if (n > size - i)
+ n = size - i;
+ d = data + i;
+ // print offset
+ *cur++ = hexchar[(i >> 12) & 15];
+ *cur++ = hexchar[(i >> 8) & 15];
+ *cur++ = hexchar[(i >> 4) & 15];
+ *cur++ = hexchar[(i >> 0) & 15];
+ *cur++ = ':';
+ // print hex
+ for (j = 0;j < 16;j++)
+ {
+ if (j < n)
+ {
+ *cur++ = hexchar[(d[j] >> 4) & 15];
+ *cur++ = hexchar[(d[j] >> 0) & 15];
+ }
+ else
+ {
+ *cur++ = ' ';
+ *cur++ = ' ';
+ }
+ if ((j & 3) == 3)
+ *cur++ = ' ';
+ }
+ // print text
+ for (j = 0;j < 16;j++)
+ {
+ if (j < n)
+ {
+ // color change prefix character has to be treated specially
+ if (d[j] == STRING_COLOR_TAG)
+ {
+ *cur++ = STRING_COLOR_TAG;
+ *cur++ = STRING_COLOR_TAG;
+ }
+ else if (d[j] >= (unsigned char) ' ')
+ *cur++ = d[j];
+ else
+ *cur++ = '.';
+ }
+ else
+ *cur++ = ' ';
+ }
+ *cur++ = '\n';
+ i += n;
+ if (cur >= flushpointer || i >= size)
+ {
+ *cur++ = 0;
+ Con_Print(text);
+ cur = text;
+ }
+ }
}
-unsigned int BuffLittleLong (const qbyte *buffer)
+void SZ_HexDumpToConsole(const sizebuf_t *buf)
{
- return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0];
+ Com_HexDumpToConsole(buf->data, buf->cursize);
}
-unsigned short BuffLittleShort (const qbyte *buffer)
-{
- return (buffer[1] << 8) | buffer[0];
-}
+//============================================================================
/*
-==============================================================================
+==============
+COM_Wordwrap
- MESSAGE IO FUNCTIONS
+Word wraps a string. The wordWidth function is guaranteed to be called exactly
+once for each word in the string, so it may be stateful, no idea what that
+would be good for any more. At the beginning of the string, it will be called
+for the char 0 to initialize a clean state, and then once with the string " "
+(a space) so the routine knows how long a space is.
-Handles byte ordering and avoids alignment errors
-==============================================================================
-*/
+In case no single character fits into the given width, the wordWidth function
+must return the width of exactly one character.
-//
-// writing functions
-//
+Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
-void MSG_WriteChar (sizebuf_t *sb, int c)
+The sum of the return values of the processLine function will be returned.
+==============
+*/
+int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL)
{
- qbyte *buf;
-
- buf = SZ_GetSpace (sb, 1);
- buf[0] = c;
-}
+ // Logic is as follows:
+ //
+ // For each word or whitespace:
+ // Newline found? Output current line, advance to next line. This is not a continuation. Continue.
+ // Space found? Always add it to the current line, no matter if it fits.
+ // Word found? Check if current line + current word fits.
+ // If it fits, append it. Continue.
+ // If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue.
+
+ qbool isContinuation = false;
+ float spaceWidth;
+ const char *startOfLine = string;
+ const char *cursor = string;
+ const char *end = string + length;
+ float spaceUsedInLine = 0;
+ float spaceUsedForWord;
+ int result = 0;
+ size_t wordLen;
+ size_t dummy;
+
+ dummy = 0;
+ wordWidth(passthroughCW, NULL, &dummy, -1);
+ dummy = 1;
+ spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1);
+
+ for(;;)
+ {
+ char ch = (cursor < end) ? *cursor : 0;
+ switch(ch)
+ {
+ case 0: // end of string
+ result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
+ goto out;
+ case '\n': // end of line
+ result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
+ isContinuation = false;
+ ++cursor;
+ startOfLine = cursor;
+ break;
+ case ' ': // space
+ ++cursor;
+ spaceUsedInLine += spaceWidth;
+ break;
+ default: // word
+ wordLen = 1;
+ while(cursor + wordLen < end)
+ {
+ switch(cursor[wordLen])
+ {
+ case 0:
+ case '\n':
+ case ' ':
+ goto out_inner;
+ default:
+ ++wordLen;
+ break;
+ }
+ }
+ out_inner:
+ 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
+ if(wordLen < 1) // cannot happen according to current spec of wordWidth
+ {
+ wordLen = 1;
+ spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself
+ }
+ if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine)
+ {
+ // we can simply append it
+ cursor += wordLen;
+ spaceUsedInLine += spaceUsedForWord;
+ }
+ else
+ {
+ // output current line
+ result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation);
+ isContinuation = true;
+ startOfLine = cursor;
+ cursor += wordLen;
+ spaceUsedInLine = continuationWidth + spaceUsedForWord;
+ }
+ }
+ }
+ out:
-void MSG_WriteByte (sizebuf_t *sb, int c)
-{
- qbyte *buf;
+ return result;
- buf = SZ_GetSpace (sb, 1);
- buf[0] = c;
-}
+/*
+ qbool isContinuation = false;
+ float currentWordSpace = 0;
+ const char *currentWord = 0;
+ float minReserve = 0;
-void MSG_WriteShort (sizebuf_t *sb, int c)
-{
- qbyte *buf;
+ float spaceUsedInLine = 0;
+ const char *currentLine = 0;
+ const char *currentLineEnd = 0;
+ float currentLineFinalWhitespace = 0;
+ const char *p;
- buf = SZ_GetSpace (sb, 2);
- buf[0] = c&0xff;
- buf[1] = c>>8;
-}
+ int result = 0;
+ minReserve = charWidth(passthroughCW, 0);
+ minReserve += charWidth(passthroughCW, ' ');
-void MSG_WriteLong (sizebuf_t *sb, int c)
-{
- qbyte *buf;
+ if(maxWidth < continuationWidth + minReserve)
+ maxWidth = continuationWidth + minReserve;
- buf = SZ_GetSpace (sb, 4);
- buf[0] = c&0xff;
- buf[1] = (c>>8)&0xff;
- buf[2] = (c>>16)&0xff;
- buf[3] = c>>24;
-}
+ charWidth(passthroughCW, 0);
-void MSG_WriteFloat (sizebuf_t *sb, float f)
-{
- union
+ for(p = string; p < string + length; ++p)
{
- float f;
- int l;
- } dat;
-
+ char c = *p;
+ float w = charWidth(passthroughCW, c);
- dat.f = f;
- dat.l = LittleLong (dat.l);
+ if(!currentWord)
+ {
+ currentWord = p;
+ currentWordSpace = 0;
+ }
- SZ_Write (sb, &dat.l, 4);
-}
+ if(!currentLine)
+ {
+ currentLine = p;
+ spaceUsedInLine = isContinuation ? continuationWidth : 0;
+ currentLineEnd = 0;
+ }
-void MSG_WriteString (sizebuf_t *sb, const char *s)
-{
- if (!s)
- SZ_Write (sb, "", 1);
- else
- SZ_Write (sb, s, strlen(s)+1);
-}
+ if(c == ' ')
+ {
+ // 1. I can add the word AND a space - then just append it.
+ if(spaceUsedInLine + currentWordSpace + w <= maxWidth)
+ {
+ currentLineEnd = p; // note: space not included here
+ currentLineFinalWhitespace = w;
+ spaceUsedInLine += currentWordSpace + w;
+ }
+ // 2. I can just add the word - then append it, output current line and go to next one.
+ else if(spaceUsedInLine + currentWordSpace <= maxWidth)
+ {
+ result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
+ currentLine = 0;
+ isContinuation = true;
+ }
+ // 3. Otherwise, output current line and go to next one, where I can add the word.
+ else if(continuationWidth + currentWordSpace + w <= maxWidth)
+ {
+ if(currentLineEnd)
+ result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
+ currentLine = currentWord;
+ spaceUsedInLine = continuationWidth + currentWordSpace + w;
+ currentLineEnd = p;
+ currentLineFinalWhitespace = w;
+ isContinuation = true;
+ }
+ // 4. We can't even do that? Then output both current and next word as new lines.
+ else
+ {
+ if(currentLineEnd)
+ {
+ result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
+ isContinuation = true;
+ }
+ result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
+ currentLine = 0;
+ isContinuation = true;
+ }
+ currentWord = 0;
+ }
+ else if(c == '\n')
+ {
+ // 1. I can add the word - then do it.
+ if(spaceUsedInLine + currentWordSpace <= maxWidth)
+ {
+ result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
+ }
+ // 2. Otherwise, output current line, next one and make tabula rasa.
+ else
+ {
+ if(currentLineEnd)
+ {
+ processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
+ isContinuation = true;
+ }
+ result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
+ }
+ currentWord = 0;
+ currentLine = 0;
+ isContinuation = false;
+ }
+ else
+ {
+ currentWordSpace += w;
+ if(
+ spaceUsedInLine + currentWordSpace > maxWidth // can't join this line...
+ &&
+ continuationWidth + currentWordSpace > maxWidth // can't join any other line...
+ )
+ {
+ // this word cannot join ANY line...
+ // so output the current line...
+ if(currentLineEnd)
+ {
+ result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
+ isContinuation = true;
+ }
+
+ // then this word's beginning...
+ if(isContinuation)
+ {
+ // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces
+ float pieceWidth = maxWidth - continuationWidth;
+ const char *pos = currentWord;
+ currentWordSpace = 0;
+
+ // reset the char width function to a state where no kerning occurs (start of word)
+ charWidth(passthroughCW, ' ');
+ while(pos <= p)
+ {
+ float w = charWidth(passthroughCW, *pos);
+ if(currentWordSpace + w > pieceWidth) // this piece won't fit any more
+ {
+ // print everything until it
+ result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true);
+ // go to here
+ currentWord = pos;
+ currentWordSpace = 0;
+ }
+ currentWordSpace += w;
+ ++pos;
+ }
+ // now we have a currentWord that fits... set up its next line
+ // currentWordSpace has been set
+ // currentWord has been set
+ spaceUsedInLine = continuationWidth;
+ currentLine = currentWord;
+ currentLineEnd = 0;
+ isContinuation = true;
+ }
+ else
+ {
+ // we have a guarantee that it will fix (see if clause)
+ result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation);
+
+ // and use the rest of this word as new start of a line
+ currentWordSpace = w;
+ currentWord = p;
+ spaceUsedInLine = continuationWidth;
+ currentLine = p;
+ currentLineEnd = 0;
+ isContinuation = true;
+ }
+ }
+ }
+ }
-// used by server (always latest dpprotocol)
-void MSG_WriteDPCoord (sizebuf_t *sb, float f)
-{
- if (f >= 0)
- MSG_WriteShort (sb, (int)(f + 0.5f));
- else
- MSG_WriteShort (sb, (int)(f - 0.5f));
-}
+ if(!currentWord)
+ {
+ currentWord = p;
+ currentWordSpace = 0;
+ }
-void MSG_WritePreciseAngle (sizebuf_t *sb, float f)
-{
- if (f >= 0)
- MSG_WriteShort (sb, (int)(f*(65536.0f/360.0f) + 0.5f) & 65535);
- else
- MSG_WriteShort (sb, (int)(f*(65536.0f/360.0f) - 0.5f) & 65535);
-}
+ if(currentLine) // Same procedure as \n
+ {
+ // Can I append the current word?
+ if(spaceUsedInLine + currentWordSpace <= maxWidth)
+ result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation);
+ else
+ {
+ if(currentLineEnd)
+ {
+ result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation);
+ isContinuation = true;
+ }
+ result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation);
+ }
+ }
-// LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem
-void MSG_WriteAngle (sizebuf_t *sb, float f)
-{
- if (f >= 0)
- MSG_WriteByte (sb, (int)(f*(256.0f/360.0f) + 0.5f) & 255);
- else
- MSG_WriteByte (sb, (int)(f*(256.0f/360.0f) - 0.5f) & 255);
+ return result;
+*/
}
-//
-// reading functions
-//
-int msg_readcount;
-qboolean msg_badread;
+/*
+==============
+COM_ParseToken_Simple
-void MSG_BeginReading (void)
+Parse a token out of a string
+==============
+*/
+int COM_ParseToken_Simple(const char **datapointer, qbool returnnewline, qbool parsebackslash, qbool parsecomments)
{
- msg_readcount = 0;
- msg_badread = false;
-}
+ int len;
+ int c;
+ const char *data = *datapointer;
-int MSG_ReadLittleShort (void)
-{
- if (msg_readcount+2 > net_message.cursize)
- {
- msg_badread = true;
- return -1;
- }
- msg_readcount += 2;
- return (short)(net_message.data[msg_readcount-2] | (net_message.data[msg_readcount-1]<<8));
-}
+ len = 0;
+ com_token[0] = 0;
-int MSG_ReadBigShort (void)
-{
- if (msg_readcount+2 > net_message.cursize)
+ if (!data)
{
- msg_badread = true;
- return -1;
+ *datapointer = NULL;
+ return false;
}
- msg_readcount += 2;
- return (short)((net_message.data[msg_readcount-2]<<8) + net_message.data[msg_readcount-1]);
-}
-int MSG_ReadLittleLong (void)
-{
- if (msg_readcount+4 > net_message.cursize)
+// skip whitespace
+skipwhite:
+ // line endings:
+ // UNIX: \n
+ // Mac: \r
+ // Windows: \r\n
+ for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
{
- msg_badread = true;
- return -1;
+ if (*data == 0)
+ {
+ // end of file
+ *datapointer = NULL;
+ return false;
+ }
}
- msg_readcount += 4;
- return net_message.data[msg_readcount-4] | (net_message.data[msg_readcount-3]<<8) | (net_message.data[msg_readcount-2]<<16) | (net_message.data[msg_readcount-1]<<24);
-}
-int MSG_ReadBigLong (void)
-{
- if (msg_readcount+4 > net_message.cursize)
+ // handle Windows line ending
+ if (data[0] == '\r' && data[1] == '\n')
+ data++;
+
+ if (parsecomments && data[0] == '/' && data[1] == '/')
{
- msg_badread = true;
- return -1;
+ // comment
+ while (*data && *data != '\n' && *data != '\r')
+ data++;
+ goto skipwhite;
}
- msg_readcount += 4;
- return (net_message.data[msg_readcount-4]<<24) + (net_message.data[msg_readcount-3]<<16) + (net_message.data[msg_readcount-2]<<8) + net_message.data[msg_readcount-1];
-}
-
-float MSG_ReadLittleFloat (void)
-{
- union
+ else if (parsecomments && data[0] == '/' && data[1] == '*')
{
- float f;
- int l;
- } dat;
- if (msg_readcount+4 > net_message.cursize)
+ // comment
+ data++;
+ while (*data && (data[0] != '*' || data[1] != '/'))
+ data++;
+ if (*data)
+ data++;
+ if (*data)
+ data++;
+ goto skipwhite;
+ }
+ else if (*data == '\"')
{
- msg_badread = true;
- return -1;
+ // quoted string
+ for (data++;*data && *data != '\"';data++)
+ {
+ c = *data;
+ if (*data == '\\' && parsebackslash)
+ {
+ data++;
+ c = *data;
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
+ }
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = c;
+ }
+ com_token[len] = 0;
+ if (*data == '\"')
+ data++;
+ *datapointer = data;
+ return true;
}
- msg_readcount += 4;
- dat.l = net_message.data[msg_readcount-4] | (net_message.data[msg_readcount-3]<<8) | (net_message.data[msg_readcount-2]<<16) | (net_message.data[msg_readcount-1]<<24);
- return dat.f;
-}
-
-float MSG_ReadBigFloat (void)
-{
- union
+ else if (*data == '\r')
{
- float f;
- int l;
- } dat;
- if (msg_readcount+4 > net_message.cursize)
+ // translate Mac line ending to UNIX
+ com_token[len++] = '\n';data++;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
+ else if (*data == '\n')
{
- msg_badread = true;
- return -1;
+ // single character
+ com_token[len++] = *data++;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
}
- msg_readcount += 4;
- dat.l = (net_message.data[msg_readcount-4]<<24) | (net_message.data[msg_readcount-3]<<16) | (net_message.data[msg_readcount-2]<<8) | net_message.data[msg_readcount-1];
- return dat.f;
-}
-
-char *MSG_ReadString (void)
-{
- static char string[2048];
- int l,c;
- for (l = 0;l < (int) sizeof(string) - 1 && (c = MSG_ReadChar()) != -1 && c != 0;l++)
- string[l] = c;
- string[l] = 0;
- return string;
-}
-
-int MSG_ReadBytes (int numbytes, unsigned char *out)
-{
- int l, c;
- for (l = 0;l < numbytes && (c = MSG_ReadChar()) != -1;l++)
- out[l] = c;
- return l;
-}
-
-// used by server (always latest dpprotocol)
-float MSG_ReadDPCoord (void)
-{
- return (signed short) MSG_ReadLittleShort();
-}
-
-// used by client
-float MSG_ReadCoord (void)
-{
- if (dpprotocol == DPPROTOCOL_VERSION2 || dpprotocol == DPPROTOCOL_VERSION3)
- return (signed short) MSG_ReadLittleShort();
- else if (dpprotocol == DPPROTOCOL_VERSION1)
- return MSG_ReadLittleFloat();
else
- return MSG_ReadLittleShort() * (1.0f/8.0f);
-}
-
-
-//===========================================================================
-
-void SZ_Alloc (sizebuf_t *buf, int startsize, const char *name)
-{
- if (startsize < 256)
- startsize = 256;
- buf->mempool = Mem_AllocPool(name);
- buf->data = Mem_Alloc(buf->mempool, startsize);
- buf->maxsize = startsize;
- buf->cursize = 0;
+ {
+ // regular word
+ for (;!ISWHITESPACE(*data);data++)
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
}
+/*
+==============
+COM_ParseToken_QuakeC
-void SZ_Free (sizebuf_t *buf)
-{
- Mem_FreePool(&buf->mempool);
- buf->data = NULL;
- buf->maxsize = 0;
- buf->cursize = 0;
-}
-
-void SZ_Clear (sizebuf_t *buf)
+Parse a token out of a string
+==============
+*/
+int COM_ParseToken_QuakeC(const char **datapointer, qbool returnnewline)
{
- buf->cursize = 0;
-}
+ int len;
+ int c;
+ const char *data = *datapointer;
-void *SZ_GetSpace (sizebuf_t *buf, int length)
-{
- void *data;
+ len = 0;
+ com_token[0] = 0;
- if (buf->cursize + length > buf->maxsize)
+ if (!data)
{
- if (!buf->allowoverflow)
- Host_Error ("SZ_GetSpace: overflow without allowoverflow set\n");
-
- if (length > buf->maxsize)
- Host_Error ("SZ_GetSpace: %i is > full buffer size\n", length);
-
- buf->overflowed = true;
- Con_Printf ("SZ_GetSpace: overflow\n");
- SZ_Clear (buf);
+ *datapointer = NULL;
+ return false;
}
- data = buf->data + buf->cursize;
- buf->cursize += length;
-
- return data;
-}
-
-void SZ_Write (sizebuf_t *buf, const void *data, int length)
-{
- memcpy (SZ_GetSpace(buf,length),data,length);
-}
-
-void SZ_Print (sizebuf_t *buf, const char *data)
-{
- int len;
- len = strlen(data)+1;
+// skip whitespace
+skipwhite:
+ // line endings:
+ // UNIX: \n
+ // Mac: \r
+ // Windows: \r\n
+ for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
+ {
+ if (*data == 0)
+ {
+ // end of file
+ *datapointer = NULL;
+ return false;
+ }
+ }
-// byte * cast to keep VC++ happy
- if (buf->data[buf->cursize-1])
- memcpy ((qbyte *)SZ_GetSpace(buf, len),data,len); // no trailing 0
- else
- memcpy ((qbyte *)SZ_GetSpace(buf, len-1)-1,data,len); // write over trailing 0
-}
+ // handle Windows line ending
+ if (data[0] == '\r' && data[1] == '\n')
+ data++;
-static char *hexchar = "0123456789ABCDEF";
-void Com_HexDumpToConsole(const qbyte *data, int size)
-{
- int i, j, n;
- char text[1024];
- char *cur, *flushpointer;
- const qbyte *d;
- cur = text;
- flushpointer = text + 512;
- for (i = 0;i < size;)
+ if (data[0] == '/' && data[1] == '/')
{
- n = 16;
- if (n > size - i)
- n = size - i;
- d = data + i;
- *cur++ = hexchar[(i >> 12) & 15];
- *cur++ = hexchar[(i >> 8) & 15];
- *cur++ = hexchar[(i >> 4) & 15];
- *cur++ = hexchar[(i >> 0) & 15];
- *cur++ = ':';
- for (j = 0;j < n;j++)
+ // comment
+ while (*data && *data != '\n' && *data != '\r')
+ data++;
+ goto skipwhite;
+ }
+ else if (data[0] == '/' && data[1] == '*')
+ {
+ // comment
+ data++;
+ while (*data && (data[0] != '*' || data[1] != '/'))
+ data++;
+ if (*data)
+ data++;
+ if (*data)
+ data++;
+ goto skipwhite;
+ }
+ else if (*data == '\"' || *data == '\'')
+ {
+ // quoted string
+ char quote = *data;
+ for (data++;*data && *data != quote;data++)
{
- if (j & 1)
- {
- *cur++ = hexchar[(d[j] >> 4) & 15] | 0x80;
- *cur++ = hexchar[(d[j] >> 0) & 15] | 0x80;
- }
- else
+ c = *data;
+ if (*data == '\\')
{
- *cur++ = hexchar[(d[j] >> 4) & 15];
- *cur++ = hexchar[(d[j] >> 0) & 15];
+ data++;
+ c = *data;
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
}
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = c;
}
- for (;j < 16;j++)
- {
- *cur++ = ' ';
- *cur++ = ' ';
- }
- for (j = 0;j < n;j++)
- *cur++ = (d[j] >= ' ' && d[j] <= 0x7E) ? d[j] : '.';
- for (;j < 16;j++)
- *cur++ = ' ';
- *cur++ = '\n';
- i += n;
- if (cur >= flushpointer || i >= size)
- {
- *cur++ = 0;
- Con_Printf("%s", text);
- cur = text;
- }
+ com_token[len] = 0;
+ if (*data == quote)
+ data++;
+ *datapointer = data;
+ return true;
+ }
+ else if (*data == '\r')
+ {
+ // translate Mac line ending to UNIX
+ com_token[len++] = '\n';data++;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
+ else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
+ {
+ // single character
+ com_token[len++] = *data++;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
+ else
+ {
+ // regular word
+ for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
}
}
-void SZ_HexDumpToConsole(const sizebuf_t *buf)
-{
- Com_HexDumpToConsole(buf->data, buf->cursize);
-}
-
-
-//============================================================================
-
-
/*
==============
-COM_ParseToken
+COM_ParseToken_VM_Tokenize
Parse a token out of a string
==============
*/
-int COM_ParseToken (const char **datapointer)
+int COM_ParseToken_VM_Tokenize(const char **datapointer, qbool returnnewline)
{
- int c;
int len;
+ int c;
const char *data = *datapointer;
len = 0;
// skip whitespace
skipwhite:
- while ((c = *data) <= ' ')
+ // line endings:
+ // UNIX: \n
+ // Mac: \r
+ // Windows: \r\n
+ for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
{
- if (c == 0)
+ if (*data == 0)
{
// end of file
*datapointer = NULL;
return false;
}
- data++;
}
-// skip // comments
- if (c=='/' && data[1] == '/')
+ // handle Windows line ending
+ if (data[0] == '\r' && data[1] == '\n')
+ data++;
+
+ if (data[0] == '/' && data[1] == '/')
{
- while (*data && *data != '\n')
+ // comment
+ while (*data && *data != '\n' && *data != '\r')
data++;
goto skipwhite;
}
-
-
-// handle quoted strings specially
- if (c == '\"')
+ else if (data[0] == '/' && data[1] == '*')
{
+ // comment
data++;
- while (1)
+ while (*data && (data[0] != '*' || data[1] != '/'))
+ data++;
+ if (*data)
+ data++;
+ if (*data)
+ data++;
+ goto skipwhite;
+ }
+ else if (*data == '\"' || *data == '\'')
+ {
+ char quote = *data;
+ // quoted string
+ for (data++;*data && *data != quote;data++)
{
- c = *data++;
- if (c=='\"' || !c)
+ c = *data;
+ if (*data == '\\')
{
- com_token[len] = 0;
- *datapointer = data;
- return true;
+ data++;
+ c = *data;
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
}
- com_token[len] = c;
- len++;
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = c;
}
+ com_token[len] = 0;
+ if (*data == quote)
+ data++;
+ *datapointer = data;
+ return true;
}
-
-// parse single characters
- if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
+ else if (*data == '\r')
{
- com_token[len] = c;
- len++;
+ // translate Mac line ending to UNIX
+ com_token[len++] = '\n';data++;
com_token[len] = 0;
- *datapointer = data+1;
+ *datapointer = data;
return true;
}
-
-// parse a regular word
- do
+ else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
{
- com_token[len] = c;
- data++;
- len++;
- c = *data;
- if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
- break;
- } while (c>32);
-
- com_token[len] = 0;
- *datapointer = data;
- return true;
+ // single character
+ com_token[len++] = *data++;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
+ else
+ {
+ // regular word
+ for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++)
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
}
-
/*
-================
-COM_CheckParm
+==============
+COM_ParseToken_Console
-Returns the position (1 to argc-1) in the program's argument list
-where the given parameter apears, or 0 if not present
-================
+Parse a token out of a string, behaving like the qwcl console
+==============
*/
-int COM_CheckParm (const char *parm)
+int COM_ParseToken_Console(const char **datapointer)
{
- int i;
+ int len;
+ const char *data = *datapointer;
+
+ len = 0;
+ com_token[0] = 0;
+
+ if (!data)
+ {
+ *datapointer = NULL;
+ return false;
+ }
- for (i=1 ; i<com_argc ; i++)
+// skip whitespace
+skipwhite:
+ for (;ISWHITESPACE(*data);data++)
+ {
+ if (*data == 0)
+ {
+ // end of file
+ *datapointer = NULL;
+ return false;
+ }
+ }
+
+ if (*data == '/' && data[1] == '/')
+ {
+ // comment
+ while (*data && *data != '\n' && *data != '\r')
+ data++;
+ goto skipwhite;
+ }
+ else if (*data == '\"')
+ {
+ // quoted string
+ for (data++;*data && *data != '\"';data++)
+ {
+ // allow escaped " and \ case
+ if (*data == '\\' && (data[1] == '\"' || data[1] == '\\'))
+ data++;
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
+ }
+ com_token[len] = 0;
+ if (*data == '\"')
+ data++;
+ *datapointer = data;
+ }
+ else
{
- if (!com_argv[i])
- continue; // NEXTSTEP sometimes clears appkit vars.
- if (!strcmp (parm,com_argv[i]))
- return i;
+ // regular word
+ for (;!ISWHITESPACE(*data);data++)
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
+ com_token[len] = 0;
+ *datapointer = data;
}
- return 0;
+ return true;
}
/*
-================
-COM_CheckRegistered
+===============
+Com_CalcRoll
-Looks for the pop.txt file and verifies it.
-Sets the "registered" cvar.
-Immediately exits out if an alternate game was attempted to be started without
-being registered.
-================
+Used by view and sv_user
+===============
*/
-void COM_CheckRegistered (void)
+float Com_CalcRoll (const vec3_t angles, const vec3_t velocity, const vec_t angleval, const vec_t velocityval)
{
- Cvar_Set ("cmdline", com_cmdline);
+ vec3_t forward, right, up;
+ float sign;
+ float side;
- if (!FS_FileExists("gfx/pop.lmp"))
- {
- if (fs_modified)
- Con_Printf ("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
- else
- Con_Printf ("Playing shareware version.\n");
- return;
- }
+ AngleVectors (angles, forward, right, up);
+ side = DotProduct (velocity, right);
+ sign = side < 0 ? -1 : 1;
+ side = fabs(side);
+
+ if (side < velocityval)
+ side = side * angleval / velocityval;
+ else
+ side = angleval;
+
+ return side*sign;
- Cvar_Set ("registered", "1");
- Con_Printf ("Playing registered version.\n");
}
+//===========================================================================
/*
================
-COM_InitArgv
+COM_Init
================
*/
-void COM_InitArgv (void)
+void COM_Init_Commands (void)
{
int i, j, n;
+ char com_cmdline[MAX_INPUTLINE];
+
+ Cvar_RegisterVariable (®istered);
+ Cvar_RegisterVariable (&cmdline);
+ Cvar_RegisterVariable(&cl_playermodel);
+ Cvar_RegisterVirtual(&cl_playermodel, "_cl_playermodel");
+ Cvar_RegisterVariable(&cl_playerskin);
+ Cvar_RegisterVirtual(&cl_playerskin, "_cl_playerskin");
+
// reconstitute the command line for the cmdline externally visible cvar
n = 0;
- for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++)
+ for (j = 0;(j < MAX_NUM_ARGVS) && (j < sys.argc);j++)
{
i = 0;
- while ((n < (CMDLINE_LENGTH - 1)) && com_argv[j][i])
- com_cmdline[n++] = com_argv[j][i++];
- if (n < (CMDLINE_LENGTH - 1))
+ if (strstr(sys.argv[j], " "))
+ {
+ // arg contains whitespace, store quotes around it
+ // This condition checks whether we can allow to put
+ // in two quote characters.
+ if (n >= ((int)sizeof(com_cmdline) - 2))
+ break;
+ com_cmdline[n++] = '\"';
+ // This condition checks whether we can allow one
+ // more character and a quote character.
+ while ((n < ((int)sizeof(com_cmdline) - 2)) && sys.argv[j][i])
+ // FIXME: Doesn't quote special characters.
+ com_cmdline[n++] = sys.argv[j][i++];
+ com_cmdline[n++] = '\"';
+ }
+ else
+ {
+ // This condition checks whether we can allow one
+ // more character.
+ while ((n < ((int)sizeof(com_cmdline) - 1)) && sys.argv[j][i])
+ com_cmdline[n++] = sys.argv[j][i++];
+ }
+ if (n < ((int)sizeof(com_cmdline) - 1))
com_cmdline[n++] = ' ';
else
break;
}
com_cmdline[n] = 0;
+ Cvar_SetQuick(&cmdline, com_cmdline);
}
-void COM_InitGameType (void)
+/*
+============
+va
+
+varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf)
+============
+*/
+char *va(char *buf, size_t buflen, const char *format, ...)
{
- char name[MAX_OSPATH];
- FS_StripExtension(com_argv[0], name);
- COM_ToLowerString(name, name);
-
- if (strstr(name, "transfusion"))
- gamemode = GAME_TRANSFUSION;
- else if (strstr(name, "nexuiz"))
- gamemode = GAME_NEXUIZ;
- else if (strstr(name, "nehahra"))
- gamemode = GAME_NEHAHRA;
- else if (strstr(name, "hipnotic"))
- gamemode = GAME_HIPNOTIC;
- else if (strstr(name, "rogue"))
- gamemode = GAME_ROGUE;
- else
- gamemode = GAME_NORMAL;
-
- if (COM_CheckParm ("-transfusion"))
- gamemode = GAME_TRANSFUSION;
- else if (COM_CheckParm ("-nexuiz"))
- gamemode = GAME_NEXUIZ;
- else if (COM_CheckParm ("-nehahra"))
- gamemode = GAME_NEHAHRA;
- else if (COM_CheckParm ("-hipnotic"))
- gamemode = GAME_HIPNOTIC;
- else if (COM_CheckParm ("-rogue"))
- gamemode = GAME_ROGUE;
- else if (COM_CheckParm ("-quake"))
- gamemode = GAME_NORMAL;
-
- switch(gamemode)
- {
- case GAME_NORMAL:
- gamename = "DarkPlaces-Quake";
- gamedirname = "";
- break;
- case GAME_HIPNOTIC:
- gamename = "Darkplaces-Hipnotic";
- gamedirname = "hipnotic";
- break;
- case GAME_ROGUE:
- gamename = "Darkplaces-Rogue";
- gamedirname = "rogue";
- break;
- case GAME_NEHAHRA:
- gamename = "DarkPlaces-Nehahra";
- gamedirname = "nehahra";
- break;
- case GAME_NEXUIZ:
- gamename = "Nexuiz";
- gamedirname = "data";
- break;
- case GAME_TRANSFUSION:
- gamename = "Transfusion";
- gamedirname = "transfusion";
- break;
- default:
- Sys_Error("COM_InitGameType: unknown gamemode %i\n", gamemode);
- break;
- }
+ va_list argptr;
+
+ va_start (argptr, format);
+ dpvsnprintf (buf, buflen, format,argptr);
+ va_end (argptr);
+
+ return buf;
}
-extern void Mathlib_Init(void);
-extern void FS_Init (void);
+//======================================
-/*
-================
-COM_Init
-================
-*/
-void COM_Init (void)
-{
-#if !defined(ENDIAN_LITTLE) && !defined(ENDIAN_BIG)
- qbyte swaptest[2] = {1,0};
+// snprintf and vsnprintf are NOT portable. Use their DP counterparts instead
-// set the byte swapping variables in a portable manner
- if ( *(short *)swaptest == 1)
- {
- BigShort = ShortSwap;
- LittleShort = ShortNoSwap;
- BigLong = LongSwap;
- LittleLong = LongNoSwap;
- BigFloat = FloatSwap;
- LittleFloat = FloatNoSwap;
- }
- else
- {
- BigShort = ShortNoSwap;
- LittleShort = ShortSwap;
- BigLong = LongNoSwap;
- LittleLong = LongSwap;
- BigFloat = FloatNoSwap;
- LittleFloat = FloatSwap;
- }
+#undef snprintf
+#undef vsnprintf
+
+#ifdef WIN32
+# define snprintf _snprintf
+# define vsnprintf _vsnprintf
#endif
- Cvar_RegisterVariable (®istered);
- Cvar_RegisterVariable (&cmdline);
- Mathlib_Init();
+int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...)
+{
+ va_list args;
+ int result;
- FS_Init ();
- Con_InitLogging();
- COM_CheckRegistered ();
+ va_start (args, format);
+ result = dpvsnprintf (buffer, buffersize, format, args);
+ va_end (args);
- COM_InitGameType();
+ return result;
}
-/*
-============
-va
-
-does a varargs printf into a temp buffer, so I don't need to have
-varargs versions of all text functions.
-FIXME: make this buffer size safe someday
-============
-*/
-char *va(const char *format, ...)
+int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args)
{
- va_list argptr;
- // LordHavoc: now cycles through 8 buffers to avoid problems in most cases
- static char string[8][1024], *s;
- static int stringindex = 0;
+ int result;
- s = string[stringindex];
- stringindex = (stringindex + 1) & 7;
- va_start (argptr, format);
- vsprintf (s, format,argptr);
- va_end (argptr);
+#if _MSC_VER >= 1400
+ result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args);
+#else
+ result = vsnprintf (buffer, buffersize, format, args);
+#endif
+ if (result < 0 || (size_t)result >= buffersize)
+ {
+ buffer[buffersize - 1] = '\0';
+ return -1;
+ }
- return s;
+ return result;
}
//======================================
-// LordHavoc: added these because they are useful
-void COM_ToLowerString(const char *in, char *out)
+void COM_ToLowerString (const char *in, char *out, size_t size_out)
{
- while (*in)
+ if (size_out == 0)
+ return;
+
+ if(utf8_enable.integer)
+ {
+ *out = 0;
+ while(*in && size_out > 1)
+ {
+ int n;
+ Uchar ch = u8_getchar_utf8_enabled(in, &in);
+ ch = u8_tolower(ch);
+ n = u8_fromchar(ch, out, size_out);
+ if(n <= 0)
+ break;
+ out += n;
+ size_out -= n;
+ }
+ return;
+ }
+
+ while (*in && size_out > 1)
{
if (*in >= 'A' && *in <= 'Z')
*out++ = *in++ + 'a' - 'A';
else
*out++ = *in++;
+ size_out--;
}
+ *out = '\0';
}
-void COM_ToUpperString(const char *in, char *out)
+void COM_ToUpperString (const char *in, char *out, size_t size_out)
{
- while (*in)
+ if (size_out == 0)
+ return;
+
+ if(utf8_enable.integer)
+ {
+ *out = 0;
+ while(*in && size_out > 1)
+ {
+ int n;
+ Uchar ch = u8_getchar_utf8_enabled(in, &in);
+ ch = u8_toupper(ch);
+ n = u8_fromchar(ch, out, size_out);
+ if(n <= 0)
+ break;
+ out += n;
+ size_out -= n;
+ }
+ return;
+ }
+
+ while (*in && size_out > 1)
{
if (*in >= 'a' && *in <= 'z')
*out++ = *in++ + 'A' - 'a';
else
*out++ = *in++;
+ size_out--;
}
+ *out = '\0';
}
int COM_StringBeginsWith(const char *s, const char *match)
return false;
return true;
}
+
+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
+}
+
+//========================================================
+// strlcat and strlcpy, from OpenBSD
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ * 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.19 2019/01/25 00:19:25 millert Exp $ */
+/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */
+
+
+#ifndef HAVE_STRLCAT
+size_t
+strlcat(char *dst, const char *src, size_t dsize)
+{
+ const char *odst = dst;
+ const char *osrc = src;
+ size_t n = dsize;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end. */
+ while (n-- != 0 && *dst != '\0')
+ dst++;
+ dlen = dst - odst;
+ n = dsize - dlen;
+
+ if (n-- == 0)
+ return(dlen + strlen(src));
+ while (*src != '\0') {
+ if (n != 0) {
+ *dst++ = *src;
+ n--;
+ }
+ src++;
+ }
+ *dst = '\0';
+
+ return(dlen + (src - osrc)); /* count does not include NUL */
+}
+#endif // #ifndef HAVE_STRLCAT
+
+
+#ifndef HAVE_STRLCPY
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ ;
+ }
+
+ return(src - osrc - 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;
+}