*/
// common.c -- misc functions used in client and server
+#include "quakedef.h"
+
#include <stdlib.h>
#include <fcntl.h>
#ifndef WIN32
#include <unistd.h>
#endif
-#include "quakedef.h"
-
-cvar_t registered = {0, "registered","0", "indicates if this is running registered quake (whether gfx/qpop.lmp was found)"};
+cvar_t registered = {0, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"};
cvar_t cmdline = {0, "cmdline","0", "contains commandline the engine was launched with"};
-extern qboolean fs_modified; // set true if using non-id files
-
char com_token[MAX_INPUTLINE];
int com_argc;
const char **com_argv;
-char com_cmdline[MAX_INPUTLINE];
-
gamemode_t gamemode;
const char *gamename;
const char *gamedirname1;
return crc ^ CRC_XOR_VALUE;
}
+unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size)
+{
+ unsigned short crc = CRC_INIT_VALUE;
+ while (size--)
+ crc = (crc << 8) ^ crctable[(crc >> 8) ^ (tolower(*data++))];
+ return crc ^ CRC_XOR_VALUE;
+}
+
+// QuakeWorld
+static unsigned char chktbl[1024 + 4] =
+{
+ 0x78,0xd2,0x94,0xe3,0x41,0xec,0xd6,0xd5,0xcb,0xfc,0xdb,0x8a,0x4b,0xcc,0x85,0x01,
+ 0x23,0xd2,0xe5,0xf2,0x29,0xa7,0x45,0x94,0x4a,0x62,0xe3,0xa5,0x6f,0x3f,0xe1,0x7a,
+ 0x64,0xed,0x5c,0x99,0x29,0x87,0xa8,0x78,0x59,0x0d,0xaa,0x0f,0x25,0x0a,0x5c,0x58,
+ 0xfb,0x00,0xa7,0xa8,0x8a,0x1d,0x86,0x80,0xc5,0x1f,0xd2,0x28,0x69,0x71,0x58,0xc3,
+ 0x51,0x90,0xe1,0xf8,0x6a,0xf3,0x8f,0xb0,0x68,0xdf,0x95,0x40,0x5c,0xe4,0x24,0x6b,
+ 0x29,0x19,0x71,0x3f,0x42,0x63,0x6c,0x48,0xe7,0xad,0xa8,0x4b,0x91,0x8f,0x42,0x36,
+ 0x34,0xe7,0x32,0x55,0x59,0x2d,0x36,0x38,0x38,0x59,0x9b,0x08,0x16,0x4d,0x8d,0xf8,
+ 0x0a,0xa4,0x52,0x01,0xbb,0x52,0xa9,0xfd,0x40,0x18,0x97,0x37,0xff,0xc9,0x82,0x27,
+ 0xb2,0x64,0x60,0xce,0x00,0xd9,0x04,0xf0,0x9e,0x99,0xbd,0xce,0x8f,0x90,0x4a,0xdd,
+ 0xe1,0xec,0x19,0x14,0xb1,0xfb,0xca,0x1e,0x98,0x0f,0xd4,0xcb,0x80,0xd6,0x05,0x63,
+ 0xfd,0xa0,0x74,0xa6,0x86,0xf6,0x19,0x98,0x76,0x27,0x68,0xf7,0xe9,0x09,0x9a,0xf2,
+ 0x2e,0x42,0xe1,0xbe,0x64,0x48,0x2a,0x74,0x30,0xbb,0x07,0xcc,0x1f,0xd4,0x91,0x9d,
+ 0xac,0x55,0x53,0x25,0xb9,0x64,0xf7,0x58,0x4c,0x34,0x16,0xbc,0xf6,0x12,0x2b,0x65,
+ 0x68,0x25,0x2e,0x29,0x1f,0xbb,0xb9,0xee,0x6d,0x0c,0x8e,0xbb,0xd2,0x5f,0x1d,0x8f,
+ 0xc1,0x39,0xf9,0x8d,0xc0,0x39,0x75,0xcf,0x25,0x17,0xbe,0x96,0xaf,0x98,0x9f,0x5f,
+ 0x65,0x15,0xc4,0x62,0xf8,0x55,0xfc,0xab,0x54,0xcf,0xdc,0x14,0x06,0xc8,0xfc,0x42,
+ 0xd3,0xf0,0xad,0x10,0x08,0xcd,0xd4,0x11,0xbb,0xca,0x67,0xc6,0x48,0x5f,0x9d,0x59,
+ 0xe3,0xe8,0x53,0x67,0x27,0x2d,0x34,0x9e,0x9e,0x24,0x29,0xdb,0x69,0x99,0x86,0xf9,
+ 0x20,0xb5,0xbb,0x5b,0xb0,0xf9,0xc3,0x67,0xad,0x1c,0x9c,0xf7,0xcc,0xef,0xce,0x69,
+ 0xe0,0x26,0x8f,0x79,0xbd,0xca,0x10,0x17,0xda,0xa9,0x88,0x57,0x9b,0x15,0x24,0xba,
+ 0x84,0xd0,0xeb,0x4d,0x14,0xf5,0xfc,0xe6,0x51,0x6c,0x6f,0x64,0x6b,0x73,0xec,0x85,
+ 0xf1,0x6f,0xe1,0x67,0x25,0x10,0x77,0x32,0x9e,0x85,0x6e,0x69,0xb1,0x83,0x00,0xe4,
+ 0x13,0xa4,0x45,0x34,0x3b,0x40,0xff,0x41,0x82,0x89,0x79,0x57,0xfd,0xd2,0x8e,0xe8,
+ 0xfc,0x1d,0x19,0x21,0x12,0x00,0xd7,0x66,0xe5,0xc7,0x10,0x1d,0xcb,0x75,0xe8,0xfa,
+ 0xb6,0xee,0x7b,0x2f,0x1a,0x25,0x24,0xb9,0x9f,0x1d,0x78,0xfb,0x84,0xd0,0x17,0x05,
+ 0x71,0xb3,0xc8,0x18,0xff,0x62,0xee,0xed,0x53,0xab,0x78,0xd3,0x65,0x2d,0xbb,0xc7,
+ 0xc1,0xe7,0x70,0xa2,0x43,0x2c,0x7c,0xc7,0x16,0x04,0xd2,0x45,0xd5,0x6b,0x6c,0x7a,
+ 0x5e,0xa1,0x50,0x2e,0x31,0x5b,0xcc,0xe8,0x65,0x8b,0x16,0x85,0xbf,0x82,0x83,0xfb,
+ 0xde,0x9f,0x36,0x48,0x32,0x79,0xd6,0x9b,0xfb,0x52,0x45,0xbf,0x43,0xf7,0x0b,0x0b,
+ 0x19,0x19,0x31,0xc3,0x85,0xec,0x1d,0x8c,0x20,0xf0,0x3a,0xfa,0x80,0x4d,0x2c,0x7d,
+ 0xac,0x60,0x09,0xc0,0x40,0xee,0xb9,0xeb,0x13,0x5b,0xe8,0x2b,0xb1,0x20,0xf0,0xce,
+ 0x4c,0xbd,0xc6,0x04,0x86,0x70,0xc6,0x33,0xc3,0x15,0x0f,0x65,0x19,0xfd,0xc2,0xd3,
+
+ // map checksum goes here
+ 0x00,0x00,0x00,0x00
+};
+
+// QuakeWorld
+unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence)
+{
+ unsigned char *p;
+ unsigned char chkb[60 + 4];
+
+ p = chktbl + (sequence % (sizeof(chktbl) - 8));
+
+ if (length > 60)
+ length = 60;
+ memcpy(chkb, base, length);
+
+ chkb[length] = (sequence & 0xff) ^ p[0];
+ chkb[length+1] = p[1];
+ chkb[length+2] = ((sequence>>8) & 0xff) ^ p[2];
+ chkb[length+3] = p[3];
+
+ return CRC_Block(chkb, length + 4) & 0xff;
+}
/*
==============================================================================
void MSG_WriteString (sizebuf_t *sb, const char *s)
{
- if (!s)
- SZ_Write (sb, (unsigned char *)"", 1);
+ if (!s || !*s)
+ MSG_WriteChar (sb, 0);
else
SZ_Write (sb, (unsigned char *)s, (int)strlen(s)+1);
}
void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s)
{
- if (s)
+ if (s && *s)
SZ_Write (sb, (unsigned char *)s, (int)strlen(s));
}
void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD)
MSG_WriteCoord13i (sb, f);
else if (protocol == PROTOCOL_DARKPLACES1)
MSG_WriteCoord32f (sb, f);
void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD)
MSG_WriteAngle8i (sb, f);
else
MSG_WriteAngle16i (sb, f);
{
static char string[MAX_INPUTLINE];
int l,c;
- for (l = 0;l < (int) sizeof(string) - 1 && (c = MSG_ReadChar()) != -1 && c != 0;l++)
+ for (l = 0;l < (int) sizeof(string) - 1 && (c = MSG_ReadByte()) != -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++)
+ for (l = 0;l < numbytes && (c = MSG_ReadByte()) != -1;l++)
out[l] = c;
return l;
}
float MSG_ReadCoord (protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD)
return MSG_ReadCoord13i();
else if (protocol == PROTOCOL_DARKPLACES1)
return MSG_ReadCoord32f();
float MSG_ReadAngle (protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD)
return MSG_ReadAngle8i ();
else
return MSG_ReadAngle16i ();
{
if (j < n)
{
- if (d[j] >= ' ' && d[j] <= 127)
+ // 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++ = '.';
//============================================================================
+/*
+==============
+COM_Wordwrap
+
+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.
+
+Wrapped lines get the isContinuation flag set and are continuationWidth less wide.
+
+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)
+{
+ // 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.
+
+ qboolean 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);
+ isContinuation = false;
+ goto out;
+ break;
+ 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)
+ {
+ 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:
+
+ return result;
+
+/*
+ qboolean isContinuation = false;
+ float currentWordSpace = 0;
+ const char *currentWord = 0;
+ float minReserve = 0;
+
+ float spaceUsedInLine = 0;
+ const char *currentLine = 0;
+ const char *currentLineEnd = 0;
+ float currentLineFinalWhitespace = 0;
+ const char *p;
+
+ int result = 0;
+ minReserve = charWidth(passthroughCW, 0);
+ minReserve += charWidth(passthroughCW, ' ');
+
+ if(maxWidth < continuationWidth + minReserve)
+ maxWidth = continuationWidth + minReserve;
+
+ charWidth(passthroughCW, 0);
+
+ for(p = string; p < string + length; ++p)
+ {
+ char c = *p;
+ float w = charWidth(passthroughCW, c);
+
+ if(!currentWord)
+ {
+ currentWord = p;
+ currentWordSpace = 0;
+ }
+
+ if(!currentLine)
+ {
+ currentLine = p;
+ spaceUsedInLine = isContinuation ? continuationWidth : 0;
+ currentLineEnd = 0;
+ }
+
+ 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;
+ }
+ }
+ }
+ }
+
+ if(!currentWord)
+ {
+ currentWord = p;
+ currentWordSpace = 0;
+ }
+
+ 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);
+ }
+ }
+
+ return result;
+*/
+}
/*
==============
-COM_ParseToken
+COM_ParseToken_Simple
Parse a token out of a string
==============
*/
-int COM_ParseToken(const char **datapointer, int returnnewline)
+int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash)
{
int len;
+ int c;
const char *data = *datapointer;
len = 0;
// UNIX: \n
// Mac: \r
// Windows: \r\n
- for (;*data <= ' ' && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
+ for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++)
{
if (*data == 0)
{
data++;
while (*data && (data[0] != '*' || data[1] != '/'))
data++;
- data += 2;
+ if (*data)
+ data++;
+ if (*data)
+ data++;
goto skipwhite;
}
else if (*data == '\"')
{
// quoted string
- for (data++;*data != '\"';data++)
+ for (data++;*data && *data != '\"';data++)
{
- if (*data == '\\' && data[1] == '"' )
- data++;
- if (!*data || len >= (int)sizeof(com_token) - 1)
+ c = *data;
+ if (*data == '\\' && parsebackslash)
{
- com_token[0] = 0;
- *datapointer = NULL;
- return false;
+ data++;
+ c = *data;
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
}
- com_token[len++] = *data;
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = c;
}
com_token[len] = 0;
- *datapointer = data+1;
+ if (*data == '\"')
+ data++;
+ *datapointer = data;
return true;
}
- else if (*data == '\'')
+ 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')
+ {
+ // single character
+ com_token[len++] = *data++;
+ com_token[len] = 0;
+ *datapointer = data;
+ return true;
+ }
+ else
+ {
+ // 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
+
+Parse a token out of a string
+==============
+*/
+int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline)
+{
+ int len;
+ int c;
+ const char *data = *datapointer;
+
+ len = 0;
+ com_token[0] = 0;
+
+ if (!data)
+ {
+ *datapointer = NULL;
+ return false;
+ }
+
+// 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;
+ }
+ }
+
+ // handle Windows line ending
+ if (data[0] == '\r' && data[1] == '\n')
+ data++;
+
+ if (data[0] == '/' && data[1] == '/')
+ {
+ // 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
- for (data++;*data != '\'';data++)
+ char quote = *data;
+ for (data++;*data && *data != quote;data++)
{
- if (*data == '\\' && data[1] == '\'' )
- data++;
- if (!*data || len >= (int)sizeof(com_token) - 1)
+ c = *data;
+ if (*data == '\\')
{
- com_token[0] = 0;
- *datapointer = NULL;
- return false;
+ data++;
+ c = *data;
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
}
- com_token[len++] = *data;
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = c;
}
com_token[len] = 0;
- *datapointer = data+1;
+ if (*data == quote)
+ data++;
+ *datapointer = data;
return true;
}
else if (*data == '\r')
{
// translate Mac line ending to UNIX
- com_token[len++] = '\n';
+ 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 == ',' || *data == ';')
+ else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';')
{
// single character
com_token[len++] = *data++;
else
{
// regular word
- for (;*data > ' ' && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != '\'' && *data != ':' && *data != ',' && *data != ';' && *data != '\'' && *data != '"';data++)
+ 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_ParseToken_VM_Tokenize
+
+Parse a token out of a string
+==============
+*/
+int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline)
+{
+ int len;
+ int c;
+ const char *data = *datapointer;
+
+ len = 0;
+ com_token[0] = 0;
+
+ if (!data)
+ {
+ *datapointer = NULL;
+ return false;
+ }
+
+// 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;
+ }
+ }
+
+ // handle Windows line ending
+ if (data[0] == '\r' && data[1] == '\n')
+ data++;
+
+ if (data[0] == '/' && data[1] == '/')
+ {
+ // 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 == '\'')
+ {
+ char quote = *data;
+ // quoted string
+ for (data++;*data && *data != quote;data++)
{
- if (len >= (int)sizeof(com_token) - 1)
+ c = *data;
+ if (*data == '\\')
{
- com_token[0] = 0;
- *datapointer = NULL;
- return false;
+ data++;
+ c = *data;
+ if (c == 'n')
+ c = '\n';
+ else if (c == 't')
+ c = '\t';
}
- com_token[len++] = *data;
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = c;
}
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 != ',' && *data != ';';data++)
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
+ com_token[len] = 0;
*datapointer = data;
return true;
}
/*
==============
-COM_ParseTokenConsole
+COM_ParseToken_Console
Parse a token out of a string, behaving like the qwcl console
==============
*/
-int COM_ParseTokenConsole(const char **datapointer)
+int COM_ParseToken_Console(const char **datapointer)
{
int len;
const char *data = *datapointer;
// skip whitespace
skipwhite:
- for (;*data <= ' ';data++)
+ for (;ISWHITESPACE(*data);data++)
{
if (*data == 0)
{
else if (*data == '\"')
{
// quoted string
- for (data++;*data != '\"';data++)
+ for (data++;*data && *data != '\"';data++)
{
- if (!*data || len >= (int)sizeof(com_token) - 1)
- {
- com_token[0] = 0;
- *datapointer = NULL;
- return false;
- }
- com_token[len++] = *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;
- *datapointer = data+1;
+ if (*data == '\"')
+ data++;
+ *datapointer = data;
}
else
{
// regular word
- for (;*data > ' ';data++)
- {
- if (len >= (int)sizeof(com_token) - 1)
- {
- com_token[0] = 0;
- *datapointer = NULL;
- return false;
- }
- com_token[len++] = *data;
- }
+ for (;!ISWHITESPACE(*data);data++)
+ if (len < (int)sizeof(com_token) - 1)
+ com_token[len++] = *data;
com_token[len] = 0;
*datapointer = data;
}
return 0;
}
-/*
-================
-COM_CheckRegistered
-
-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.
-================
-*/
-void COM_CheckRegistered (void)
-{
- Cvar_Set ("cmdline", com_cmdline);
-
- if (gamemode == GAME_NORMAL && !FS_FileExists("gfx/pop.lmp"))
- {
- if (fs_modified)
- Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
- else
- Con_Print("Playing shareware version.\n");
- return;
- }
-
- Cvar_Set ("registered", "1");
- Con_Print("Playing registered version.\n");
-}
-
-
-/*
-================
-COM_InitArgv
-================
-*/
-void COM_InitArgv (void)
-{
- int i, j, n;
- // reconstitute the command line for the cmdline externally visible cvar
- n = 0;
- for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++)
- {
- i = 0;
- if (strstr(com_argv[j], " "))
- {
- // arg contains whitespace, store quotes around it
- com_cmdline[n++] = '\"';
- while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i])
- com_cmdline[n++] = com_argv[j][i++];
- com_cmdline[n++] = '\"';
- }
- else
- {
- while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i])
- com_cmdline[n++] = com_argv[j][i++];
- }
- if (n < ((int)sizeof(com_cmdline) - 1))
- com_cmdline[n++] = ' ';
- else
- break;
- }
- com_cmdline[n] = 0;
-}
-
-
//===========================================================================
// Game mods
const char* gameuserdirname;
} gamemode_info_t;
-static const gamemode_info_t gamemode_info [] =
-{// prog_name cmdline gamename gamedirname gamescreenshotname
+static const gamemode_info_t gamemode_info [GAME_COUNT] =
+{// prog_name cmdline gamename basegame modgame screenshotprefix userdir
// GAME_NORMAL
// COMMANDLINEOPTION: Game: -quake runs the game Quake (default)
// GAME_ZYMOTIC
// COMMANDLINEOPTION: Game: -zymotic runs the singleplayer game Zymotic
{ "zymotic", "-zymotic", "Zymotic", "basezym", NULL, "zymotic", "zymotic" },
-// GAME_FNIGGIUM
-// COMMANDLINEOPTION: Game: -fniggium runs the post apocalyptic melee RPG Fniggium
-{ "fniggium", "-fniggium", "Fniggium", "data", NULL, "fniggium", "fniggium" },
// GAME_SETHERAL
// COMMANDLINEOPTION: Game: -setheral runs the multiplayer game Setheral
{ "setheral", "-setheral", "Setheral", "data", NULL, "setheral", "setheral" },
// GAME_PRYDON
// COMMANDLINEOPTION: Game: -prydon runs the topdown point and click action-RPG Prydon Gate
{ "prydon", "-prydon", "PrydonGate", "id1", "prydon", "prydon", "darkplaces" },
-// GAME_NETHERWORLD
-// COMMANDLINEOPTION: Game: -netherworld runs the game Netherworld: Dark Master
-{ "netherworld", "-netherworld", "Netherworld: Dark Master", "id1", "netherworld", "nw", "darkplaces" },
+// GAME_DELUXEQUAKE
+// COMMANDLINEOPTION: Game: -dq runs the game Deluxe Quake
+{ "dq", "-dq", "Deluxe Quake", "basedq", "extradq", "basedq", "dq" },
// GAME_THEHUNTED
// COMMANDLINEOPTION: Game: -thehunted runs the game The Hunted
{ "thehunted", "-thehunted", "The Hunted", "thdata", NULL, "th", "thehunted" },
// GAME_DEFEATINDETAIL2
// COMMANDLINEOPTION: Game: -did2 runs the game Defeat In Detail 2
{ "did2", "-did2", "Defeat In Detail 2", "data", NULL, "did2_", "did2" },
+// GAME_DARSANA
+// COMMANDLINEOPTION: Game: -darsana runs the game Darsana
+{ "darsana", "-darsana", "Darsana", "ddata", NULL, "darsana", "darsana" },
+// GAME_CONTAGIONTHEORY
+// COMMANDLINEOPTION: Game: -contagiontheory runs the game Contagion Theory
+{ "contagiontheory", "-contagiontheory", "Contagion Theory", "ctdata", NULL, "ct", "contagiontheory" },
+// GAME_EDU2P
+// COMMANDLINEOPTION: Game: -edu2p runs the game Edu2 prototype
+{ "edu2p", "-edu2p", "EDU2 Prototype", "id1", "edu2", "edu2_p", "edu2prototype" },
+// GAME_BLADEMASTER
+// COMMANDLINEOPTION: Game: -blademaster runs the game Prophecy: Return of the BladeMaster
+{ "blademaster", "-blademaster", "Prophecy: Return of the BladeMaster", "basebm", NULL, "blademaster", "blademaster" },
+// GAME_PROPHECY
+// COMMANDLINEOPTION: Game: -prophecy runs the game Quake (default)
+{ "prophecy", "-prophecy", "Prophecy", "data", NULL, "prophecy", "prophecy" },
};
void COM_InitGameType (void)
*/
void COM_Init_Commands (void)
{
+ int i, j, n;
+ char com_cmdline[MAX_INPUTLINE];
+
Cvar_RegisterVariable (®istered);
Cvar_RegisterVariable (&cmdline);
+
+ // reconstitute the command line for the cmdline externally visible cvar
+ n = 0;
+ for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++)
+ {
+ i = 0;
+ if (strstr(com_argv[j], " "))
+ {
+ // arg contains whitespace, store quotes around it
+ com_cmdline[n++] = '\"';
+ while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i])
+ com_cmdline[n++] = com_argv[j][i++];
+ com_cmdline[n++] = '\"';
+ }
+ else
+ {
+ while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i])
+ com_cmdline[n++] = com_argv[j][i++];
+ }
+ if (n < ((int)sizeof(com_cmdline) - 1))
+ com_cmdline[n++] = ' ';
+ else
+ break;
+ }
+ com_cmdline[n] = 0;
+ Cvar_Set ("cmdline", com_cmdline);
}
/*
{
int result;
+#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';
commentprefixlength = (int)strlen(commentprefix);
while (*l && *l != '\n' && *l != '\r')
{
- if (*l > ' ')
+ if (!ISWHITESPACE(*l))
{
if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength))
{
}
else
{
- while (*l > ' ')
+ while (!ISWHITESPACE(*l))
{
if (tokenbuf >= tokenbufend)
return -1;
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, qboolean *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_DEFAULT:
+ 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_DEFAULT
+ break;
+ /*case 'a':
+ if ( s+1 != end && ( isxdigit(s[1]) || (s[1] == '+' || s[1] == '-') ) )
+ {
+ s++;
+ break;
+ }
+ ++len; // STRING_COLOR_TAG
+ ++len; // STRING_COLOR_RGB_DEFAULT
+ 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.
+============
+*/
+qboolean
+COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean 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_DEFAULT:
+ 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_DEFAULT);
+ break;
+ /*case 'a':
+ if ( in+1 != end && ( isxdigit(in[1]) || (in[1] == '+' || in[1] == '-') ) )
+ {
+ in++;
+ 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
+}
+
// written by Elric, thanks Elric!
char *SearchInfostring(const char *infostring, const char *key)
{
}
}
+void InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength)
+{
+ int pos = 0, j;
+ size_t keylength;
+ if (!key)
+ key = "";
+ if (!value)
+ value = "";
+ keylength = strlen(key);
+ if (valuelength < 1 || !value)
+ {
+ Con_Printf("InfoString_GetValue: no room in value\n");
+ return;
+ }
+ value[0] = 0;
+ if (strchr(key, '\\'))
+ {
+ Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key);
+ return;
+ }
+ if (strchr(key, '\"'))
+ {
+ Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key);
+ return;
+ }
+ if (!key[0])
+ {
+ Con_Printf("InfoString_GetValue: can not look up a key with no name\n");
+ return;
+ }
+ while (buffer[pos] == '\\')
+ {
+ if (!memcmp(buffer + pos+1, key, keylength))
+ {
+ for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
+ pos++;
+ for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++)
+ value[j] = buffer[pos+j];
+ value[j] = 0;
+ return;
+ }
+ for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
+ for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
+ }
+ // if we reach this point the key was not found
+}
+
+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))
+ break;
+ for (pos++;buffer[pos] && buffer[pos] != '\\';pos++);
+ for (pos++;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] == '\\')
+ {
+ for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++);
+ for (pos2++;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 && value[0])
+ {
+ // set the key/value and append the remaining text
+ char tempbuffer[4096];
+ 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[2048];
+ char value[2048];
+ 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