libretro-tyrquake/common/common.c
Kevin Shanahan 6798f3e81e common: make com_token a const pointer
Make the buffer for com_token private to common.c and only allow other
files to access it via a const pointer.  Add checks for overflowing
the token buffer; in case this happens we just truncate the token, but
continue to parse from the source until completing the source token.

Signed-off-by: Kevin Shanahan <kmshanah@disenchant.net>
2013-04-15 13:52:18 +09:30

2569 lines
54 KiB
C

/*
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// common.c -- misc functions used in client and server
#include <ctype.h>
#include <dirent.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#ifdef NQ_HACK
#include "quakedef.h"
#include "host.h"
#endif
#ifdef QW_HACK
#ifdef SERVERONLY
#include "qwsvdef.h"
#include "server.h"
#else
#include "quakedef.h"
#endif
#include "protocol.h"
#endif
#include "cmd.h"
#include "common.h"
#include "console.h"
#include "crc.h"
#include "draw.h"
#include "net.h"
#include "shell.h"
#include "sys.h"
#include "zone.h"
#define NUM_SAFE_ARGVS 7
static const char *largv[MAX_NUM_ARGVS + NUM_SAFE_ARGVS + 1];
static const char *argvdummy = " ";
static const char *safeargvs[NUM_SAFE_ARGVS] = {
"-stdvid", "-nolan", "-nosound", "-nocdaudio", "-nojoy", "-nomouse", "-dibonly"
};
cvar_t registered = { "registered", "0" };
#ifdef NQ_HACK
static cvar_t cmdline = { "cmdline", "0", false, true };
#endif
static qboolean com_modified; // set true if using non-id files
static int static_registered = 1; // only for startup check, then set
qboolean msg_suppress_1 = 0;
static void COM_InitFilesystem(void);
static void COM_Path_f(void);
static void *SZ_GetSpace(sizebuf_t *buf, int length);
/* Checksums for the original id pak directory structures */
#define ID1_PAK0_COUNT 339 /* id1/pak0.pak - v1.0x */
#define ID1_PAK0_CRC_V100 13900 /* id1/pak0.pak - v1.00 */
#define ID1_PAK0_CRC_V101 62751 /* id1/pak0.pak - v1.01 */
#define ID1_PAK0_CRC_V106 32981 /* id1/pak0.pak - v1.06 */
#define ID1_PAK0_COUNT_V091 308 /* id1/pak0.pak - v0.91/0.92, not supported */
#define ID1_PAK0_CRC_V091 28804 /* id1/pak0.pak - v0.91/0.92, not supported */
#define QW_PAK0_CRC 52883
#ifdef NQ_HACK
#define CMDLINE_LENGTH 256
static char com_cmdline[CMDLINE_LENGTH];
#endif
qboolean standard_quake = true, rogue, hipnotic;
#ifdef QW_HACK
char gamedirfile[MAX_OSPATH];
#endif
/* FIXME - remove this and checks like it; no need to be registered... */
// this graphic needs to be in the pak file to use registered features
static unsigned short pop[] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x6600, 0x0000, 0x0000, 0x0000, 0x6600, 0x0000,
0x0000, 0x0066, 0x0000, 0x0000, 0x0000, 0x0000, 0x0067, 0x0000,
0x0000, 0x6665, 0x0000, 0x0000, 0x0000, 0x0000, 0x0065, 0x6600,
0x0063, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6563,
0x0064, 0x6561, 0x0000, 0x0000, 0x0000, 0x0000, 0x0061, 0x6564,
0x0064, 0x6564, 0x0000, 0x6469, 0x6969, 0x6400, 0x0064, 0x6564,
0x0063, 0x6568, 0x6200, 0x0064, 0x6864, 0x0000, 0x6268, 0x6563,
0x0000, 0x6567, 0x6963, 0x0064, 0x6764, 0x0063, 0x6967, 0x6500,
0x0000, 0x6266, 0x6769, 0x6a68, 0x6768, 0x6a69, 0x6766, 0x6200,
0x0000, 0x0062, 0x6566, 0x6666, 0x6666, 0x6666, 0x6562, 0x0000,
0x0000, 0x0000, 0x0062, 0x6364, 0x6664, 0x6362, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0062, 0x6662, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0061, 0x6661, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x6500, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x6400, 0x0000, 0x0000, 0x0000
};
/*
All of Quake's data access is through a hierchal file system, but the contents
of the file system can be transparently merged from several sources.
The "base directory" is the path to the directory holding the quake.exe and
all game directories. The sys_* files pass this to host_init in
quakeparms_t->basedir. This can be overridden with the "-basedir" command
line parm to allow code debugging in a different directory. The base
directory is only used during filesystem initialization.
The "game directory" is the first tree on the search path and directory that
all generated files (savegames, screenshots, demos, config files) will be
saved to. This can be overridden with the "-game" command line parameter.
The game directory can never be changed while quake is executing. This is a
precacution against having a malicious server instruct clients to write files
over areas they shouldn't.
*/
//============================================================================
// ClearLink is used for new headnodes
void
ClearLink(link_t *l)
{
l->prev = l->next = l;
}
void
RemoveLink(link_t *l)
{
l->next->prev = l->prev;
l->prev->next = l->next;
}
void
InsertLinkBefore(link_t *l, link_t *before)
{
l->next = before;
l->prev = before->prev;
l->prev->next = l;
l->next->prev = l;
}
/* Unused */
#if 0
void
InsertLinkAfter(link_t *l, link_t *after)
{
l->next = after->next;
l->prev = after;
l->prev->next = l;
l->next->prev = l;
}
#endif
/*
============================================================================
LIBRARY REPLACEMENT FUNCTIONS
============================================================================
*/
/*
* Use this function to share static string buffers
* between different text processing functions.
* Try to avoid fixed-size intermediate buffers like this if possible
*/
#define COM_STRBUF_LEN 2048
static char *
COM_GetStrBuf(void)
{
static char buffers[4][COM_STRBUF_LEN];
static int index;
return buffers[3 & ++index];
}
int
Q_atoi(const char *str)
{
int val;
int sign;
int c;
if (*str == '-') {
sign = -1;
str++;
} else
sign = 1;
val = 0;
//
// check for hex
//
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
str += 2;
while (1) {
c = *str++;
if (c >= '0' && c <= '9')
val = (val << 4) + c - '0';
else if (c >= 'a' && c <= 'f')
val = (val << 4) + c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
val = (val << 4) + c - 'A' + 10;
else
return val * sign;
}
}
//
// check for character
//
if (str[0] == '\'') {
return sign * str[1];
}
//
// assume decimal
//
while (1) {
c = *str++;
if (c < '0' || c > '9')
return val * sign;
val = val * 10 + c - '0';
}
return 0;
}
float
Q_atof(const char *str)
{
double val;
int sign;
int c;
int decimal, total;
if (*str == '-') {
sign = -1;
str++;
} else
sign = 1;
val = 0;
//
// check for hex
//
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
str += 2;
while (1) {
c = *str++;
if (c >= '0' && c <= '9')
val = (val * 16) + c - '0';
else if (c >= 'a' && c <= 'f')
val = (val * 16) + c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
val = (val * 16) + c - 'A' + 10;
else
return val * sign;
}
}
//
// check for character
//
if (str[0] == '\'') {
return sign * str[1];
}
//
// assume decimal
//
decimal = -1;
total = 0;
while (1) {
c = *str++;
if (c == '.') {
decimal = total;
continue;
}
if (c < '0' || c > '9')
break;
val = val * 10 + c - '0';
total++;
}
if (decimal == -1)
return val * sign;
while (total > decimal) {
val /= 10;
total--;
}
return val * sign;
}
/*
============================================================================
BYTE ORDER FUNCTIONS
============================================================================
*/
qboolean bigendien;
short (*BigShort) (short l);
short (*LittleShort) (short l);
int (*BigLong) (int l);
int (*LittleLong) (int l);
float (*BigFloat) (float l);
float (*LittleFloat) (float l);
short
ShortSwap(short l)
{
byte b1, b2;
b1 = l & 255;
b2 = (l >> 8) & 255;
return (b1 << 8) + b2;
}
short
ShortNoSwap(short l)
{
return l;
}
int
LongSwap(int l)
{
byte b1, b2, b3, b4;
b1 = l & 255;
b2 = (l >> 8) & 255;
b3 = (l >> 16) & 255;
b4 = (l >> 24) & 255;
return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
}
int
LongNoSwap(int l)
{
return l;
}
float
FloatSwap(float f)
{
union {
float f;
byte b[4];
} dat1, dat2;
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;
}
float
FloatNoSwap(float f)
{
return f;
}
/*
==============================================================================
MESSAGE IO FUNCTIONS
Handles byte ordering and avoids alignment errors
==============================================================================
*/
#ifdef QW_HACK
usercmd_t nullcmd; // guarenteed to be zero
#endif
//
// writing functions
//
void
MSG_WriteChar(sizebuf_t *sb, int c)
{
byte *buf;
#ifdef PARANOID
if (c < -128 || c > 127)
Sys_Error("%s: range error", __func__);
#endif
buf = SZ_GetSpace(sb, 1);
buf[0] = c;
}
void
MSG_WriteByte(sizebuf_t *sb, int c)
{
byte *buf;
#ifdef PARANOID
if (c < 0 || c > 255)
Sys_Error("%s: range error", __func__);
#endif
buf = SZ_GetSpace(sb, 1);
buf[0] = c;
}
void
MSG_WriteShort(sizebuf_t *sb, int c)
{
byte *buf;
#ifdef PARANOID
if (c < ((short)0x8000) || c > (short)0x7fff)
Sys_Error("%s: range error", __func__);
#endif
buf = SZ_GetSpace(sb, 2);
buf[0] = c & 0xff;
buf[1] = c >> 8;
}
void
MSG_WriteLong(sizebuf_t *sb, int c)
{
byte *buf;
buf = SZ_GetSpace(sb, 4);
buf[0] = c & 0xff;
buf[1] = (c >> 8) & 0xff;
buf[2] = (c >> 16) & 0xff;
buf[3] = c >> 24;
}
void
MSG_WriteFloat(sizebuf_t *sb, float f)
{
union {
float f;
int l;
} dat;
dat.f = f;
dat.l = LittleLong(dat.l);
SZ_Write(sb, &dat.l, 4);
}
void
MSG_WriteString(sizebuf_t *sb, const char *s)
{
if (!s)
SZ_Write(sb, "", 1);
else
SZ_Write(sb, s, strlen(s) + 1);
}
void
MSG_WriteStringvf(sizebuf_t *sb, const char *fmt, va_list ap)
{
int maxlen, len;
/*
* FIXME - Kind of ugly to check space first then do getspace
* afterwards, but we don't know how much we'll need before
* hand. Update the SZ interface?
*/
maxlen = sb->maxsize - sb->cursize;
len = vsnprintf((char *)sb->data + sb->cursize, maxlen, fmt, ap);
/* Use SZ_GetSpace to check for overflow */
SZ_GetSpace(sb, len + 1);
}
void
MSG_WriteStringf(sizebuf_t *sb, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
MSG_WriteStringvf(sb, fmt, ap);
va_end(ap);
}
void
MSG_WriteCoord(sizebuf_t *sb, float f)
{
/*
* Co-ords are send as shorts, with the low 3 bits being the fractional
* component
*/
MSG_WriteShort(sb, (int)(f * (1 << 3)));
}
void
MSG_WriteAngle(sizebuf_t *sb, float f)
{
MSG_WriteByte(sb, (int)floorf((f * 256 / 360) + 0.5f) & 255);
}
void
MSG_WriteAngle16(sizebuf_t *sb, float f)
{
MSG_WriteShort(sb, (int)floorf((f * 65536 / 360) + 0.5f) & 65535);
}
#ifdef QW_HACK
void
MSG_WriteDeltaUsercmd(sizebuf_t *buf, const usercmd_t *from,
const usercmd_t *cmd)
{
int bits;
//
// send the movement message
//
bits = 0;
if (cmd->angles[0] != from->angles[0])
bits |= CM_ANGLE1;
if (cmd->angles[1] != from->angles[1])
bits |= CM_ANGLE2;
if (cmd->angles[2] != from->angles[2])
bits |= CM_ANGLE3;
if (cmd->forwardmove != from->forwardmove)
bits |= CM_FORWARD;
if (cmd->sidemove != from->sidemove)
bits |= CM_SIDE;
if (cmd->upmove != from->upmove)
bits |= CM_UP;
if (cmd->buttons != from->buttons)
bits |= CM_BUTTONS;
if (cmd->impulse != from->impulse)
bits |= CM_IMPULSE;
MSG_WriteByte(buf, bits);
if (bits & CM_ANGLE1)
MSG_WriteAngle16(buf, cmd->angles[0]);
if (bits & CM_ANGLE2)
MSG_WriteAngle16(buf, cmd->angles[1]);
if (bits & CM_ANGLE3)
MSG_WriteAngle16(buf, cmd->angles[2]);
if (bits & CM_FORWARD)
MSG_WriteShort(buf, cmd->forwardmove);
if (bits & CM_SIDE)
MSG_WriteShort(buf, cmd->sidemove);
if (bits & CM_UP)
MSG_WriteShort(buf, cmd->upmove);
if (bits & CM_BUTTONS)
MSG_WriteByte(buf, cmd->buttons);
if (bits & CM_IMPULSE)
MSG_WriteByte(buf, cmd->impulse);
MSG_WriteByte(buf, cmd->msec);
}
#endif /* QW_HACK */
#ifdef NQ_HACK
/*
* Write the current message length to the start of the buffer (in big
* endian format) with the control flag set.
*/
void
MSG_WriteControlHeader(sizebuf_t *sb)
{
int c = NETFLAG_CTL | (sb->cursize & NETFLAG_LENGTH_MASK);
sb->data[0] = c >> 24;
sb->data[1] = (c >> 16) & 0xff;
sb->data[2] = (c >> 8) & 0xff;
sb->data[3] = c & 0xff;
}
#endif
//
// reading functions
//
int msg_readcount;
qboolean msg_badread;
void
MSG_BeginReading(void)
{
msg_readcount = 0;
msg_badread = false;
}
#ifdef QW_HACK
int
MSG_GetReadCount(void)
{
return msg_readcount;
}
#endif
// returns -1 and sets msg_badread if no more characters are available
int
MSG_ReadChar(void)
{
int c;
if (msg_readcount + 1 > net_message.cursize) {
msg_badread = true;
return -1;
}
c = (signed char)net_message.data[msg_readcount];
msg_readcount++;
return c;
}
int
MSG_ReadByte(void)
{
int c;
if (msg_readcount + 1 > net_message.cursize) {
msg_badread = true;
return -1;
}
c = (unsigned char)net_message.data[msg_readcount];
msg_readcount++;
return c;
}
int
MSG_ReadShort(void)
{
int c;
if (msg_readcount + 2 > net_message.cursize) {
msg_badread = true;
return -1;
}
c = (short)(net_message.data[msg_readcount]
+ (net_message.data[msg_readcount + 1] << 8));
msg_readcount += 2;
return c;
}
int
MSG_ReadLong(void)
{
int c;
if (msg_readcount + 4 > net_message.cursize) {
msg_badread = true;
return -1;
}
c = net_message.data[msg_readcount]
+ (net_message.data[msg_readcount + 1] << 8)
+ (net_message.data[msg_readcount + 2] << 16)
+ (net_message.data[msg_readcount + 3] << 24);
msg_readcount += 4;
return c;
}
float
MSG_ReadFloat(void)
{
union {
byte b[4];
float f;
int l;
} dat;
dat.b[0] = net_message.data[msg_readcount];
dat.b[1] = net_message.data[msg_readcount + 1];
dat.b[2] = net_message.data[msg_readcount + 2];
dat.b[3] = net_message.data[msg_readcount + 3];
msg_readcount += 4;
dat.l = LittleLong(dat.l);
return dat.f;
}
char *
MSG_ReadString(void)
{
char *buf;
int len, c;
buf = COM_GetStrBuf();
len = 0;
do {
c = MSG_ReadChar();
if (c == -1 || c == 0)
break;
buf[len++] = c;
} while (len < COM_STRBUF_LEN - 1);
buf[len] = 0;
return buf;
}
#ifdef QW_HACK
char *
MSG_ReadStringLine(void)
{
char *buf;
int len, c;
buf = COM_GetStrBuf();
len = 0;
do {
c = MSG_ReadChar();
if (c == -1 || c == 0 || c == '\n')
break;
buf[len++] = c;
} while (len < COM_STRBUF_LEN - 1);
buf[len] = 0;
return buf;
}
#endif
float
MSG_ReadCoord(void)
{
/*
* Co-ords are send as shorts, with the low 3 bits being the fractional
* component
*/
return MSG_ReadShort() * (1.0 / (1 << 3));
}
float
MSG_ReadAngle(void)
{
return MSG_ReadChar() * (360.0 / 256);
}
float
MSG_ReadAngle16(void)
{
return MSG_ReadShort() * (360.0 / 65536);
}
#ifdef QW_HACK
void
MSG_ReadDeltaUsercmd(const usercmd_t *from, usercmd_t *move)
{
int bits;
memcpy(move, from, sizeof(*move));
bits = MSG_ReadByte();
// read current angles
if (bits & CM_ANGLE1)
move->angles[0] = MSG_ReadAngle16();
if (bits & CM_ANGLE2)
move->angles[1] = MSG_ReadAngle16();
if (bits & CM_ANGLE3)
move->angles[2] = MSG_ReadAngle16();
// read movement
if (bits & CM_FORWARD)
move->forwardmove = MSG_ReadShort();
if (bits & CM_SIDE)
move->sidemove = MSG_ReadShort();
if (bits & CM_UP)
move->upmove = MSG_ReadShort();
// read buttons
if (bits & CM_BUTTONS)
move->buttons = MSG_ReadByte();
if (bits & CM_IMPULSE)
move->impulse = MSG_ReadByte();
// read time to run command
move->msec = MSG_ReadByte();
}
#endif /* QW_HACK */
#ifdef NQ_HACK
/*
* Read back the message control header
* Essentially this is MSG_ReadLong, but big-endian byte order.
*/
int
MSG_ReadControlHeader(void)
{
int c;
if (msg_readcount + 4 > net_message.cursize) {
msg_badread = true;
return -1;
}
c = (net_message.data[msg_readcount] << 24)
+ (net_message.data[msg_readcount + 1] << 16)
+ (net_message.data[msg_readcount + 2] << 8)
+ net_message.data[msg_readcount + 3];
msg_readcount += 4;
return c;
}
#endif
//===========================================================================
#ifdef NQ_HACK
void
SZ_Alloc(sizebuf_t *buf, int startsize)
{
if (startsize < 256)
startsize = 256;
buf->data = Hunk_AllocName(startsize, "sizebuf");
buf->maxsize = startsize;
buf->cursize = 0;
}
void
SZ_Free(sizebuf_t *buf)
{
// Z_Free (buf->data);
// buf->data = NULL;
// buf->maxsize = 0;
buf->cursize = 0;
}
#endif
void
SZ_Clear(sizebuf_t *buf)
{
buf->cursize = 0;
buf->overflowed = false;
}
static void *
SZ_GetSpace(sizebuf_t *buf, int length)
{
void *data;
if (buf->cursize + length > buf->maxsize) {
if (!buf->allowoverflow)
Sys_Error("%s: overflow without allowoverflow set (%d > %d)",
__func__, buf->cursize + length, buf->maxsize);
if (length > buf->maxsize)
Sys_Error("%s: %d is > full buffer size", __func__, length);
if (developer.value)
/* Con_Printf may be redirected */
Sys_Printf("%s: overflow\n", __func__);
SZ_Clear(buf);
buf->overflowed = true;
}
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)
{
size_t len = strlen(data);
/* If buf->data has a trailing zero, overwrite it */
if (!buf->cursize || buf->data[buf->cursize - 1])
memcpy(SZ_GetSpace(buf, len + 1), data, len + 1);
else
memcpy(SZ_GetSpace(buf, len) - 1, data, len + 1);
}
//============================================================================
/*
============
COM_SkipPath
============
*/
const char *
COM_SkipPath(const char *pathname)
{
const char *last;
last = pathname;
while (*pathname) {
if (*pathname == '/')
last = pathname + 1;
pathname++;
}
return last;
}
/*
============
COM_StripExtension
============
*/
void
COM_StripExtension(const char *filename, char *out, size_t buflen)
{
const char *start, *pos;
size_t copylen;
start = COM_SkipPath(filename);
pos = strrchr(start, '.');
if (out == filename) {
if (pos && *pos)
out[pos - filename] = 0;
return;
}
copylen = qmin((size_t)(pos - filename), buflen - 1);
memcpy(out, filename, copylen);
out[copylen] = 0;
}
/*
============
COM_FileExtension
============
*/
#ifdef NQ_HACK
static const char *
COM_FileExtension(const char *in)
{
static char exten[8];
const char *dot;
int i;
in = COM_SkipPath(in);
dot = strrchr(in, '.');
if (!dot)
return "";
dot++;
for (i = 0; i < sizeof(exten) - 1 && *dot; i++, dot++)
exten[i] = *dot;
exten[i] = 0;
return exten;
}
#endif
/*
============
COM_FileBase
============
*/
void
COM_FileBase(const char *in, char *out, size_t buflen)
{
const char *dot;
int copylen;
in = COM_SkipPath(in);
dot = strrchr(in, '.');
copylen = dot ? dot - in : strlen(in);
if (copylen < 2) {
in = "?model?";
copylen = strlen(in);
}
snprintf(out, buflen, "%.*s", copylen, in);
}
/*
==================
COM_DefaultExtension
Returns non-zero if the extension wouldn't fit in the output buffer
==================
*/
int
COM_DefaultExtension(const char *path, const char *extension, char *out,
size_t buflen)
{
COM_StripExtension(path, out, buflen);
if (strlen(out) + strlen(extension) + 1 > buflen)
return -1;
/* Copy extension, including terminating null */
memcpy(out + strlen(out), extension, strlen(extension) + 1);
return 0;
}
int
COM_CheckExtension(const char *path, const char *extn)
{
char *pos;
int ret = 0;
pos = strrchr(path, '.');
if (pos) {
if (extn[0] != '.')
pos++;
ret = pos && !strcasecmp(pos, extn);
}
return ret;
}
//============================================================================
static char com_tokenbuf[1024];
const char *com_token = com_tokenbuf;
unsigned com_argc;
const char **com_argv;
/*
==============
COM_Parse
Parse a token out of a string
==============
*/
static const char single_chars[] = "{})(':";
static const char *
COM_Parse_(const char *data, qboolean split_single_chars)
{
int c;
int len;
len = 0;
com_tokenbuf[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ((c = *data) <= ' ') {
if (c == 0)
return NULL; // end of file;
data++;
}
// skip // comments
if (c == '/' && data[1] == '/') {
while (*data && *data != '\n')
data++;
goto skipwhite;
}
// skip /*..*/ comments
if (c == '/' && data[1] == '*') {
data += 2;
while (*data && !(*data == '*' && data[1] == '/'))
data++;
if (*data)
data += 2;
goto skipwhite;
}
// handle quoted strings specially
if (c == '\"') {
data++;
while (1) {
c = *data;
if (c)
data++;
if (c == '\"' || !c) {
com_tokenbuf[len] = 0;
return data;
}
if (len < sizeof(com_tokenbuf) - 1)
com_tokenbuf[len++] = c;
}
}
// parse single characters
if (split_single_chars && strchr(single_chars, c)) {
if (len < sizeof(com_tokenbuf) - 1)
com_tokenbuf[len++] = c;
com_tokenbuf[len] = 0;
return data + 1;
}
// parse a regular word
do {
if (len < sizeof(com_tokenbuf) - 1)
com_tokenbuf[len++] = c;
data++;
c = *data;
if (split_single_chars && strchr(single_chars, c))
break;
} while (c > 32);
com_tokenbuf[len] = 0;
return data;
}
const char *
COM_Parse(const char *data)
{
#ifdef NQ_HACK
return COM_Parse_(data, true);
#endif
#ifdef QW_HACK
return COM_Parse_(data, false);
#endif
}
/*
================
COM_CheckParm
Returns the position (1 to argc-1) in the program's argument list
where the given parameter apears, or 0 if not present
================
*/
unsigned
COM_CheckParm(const char *parm)
{
unsigned i;
for (i = 1; i < com_argc; i++) {
if (!com_argv[i])
continue; // NEXTSTEP sometimes clears appkit vars.
if (!strcmp(parm, com_argv[i]))
return i;
}
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)
{
FILE *f;
unsigned short check[128];
int i;
COM_FOpenFile("gfx/pop.lmp", &f);
static_registered = 0;
if (!f) {
Con_Printf("Playing shareware version.\n");
#ifndef SERVERONLY
if (com_modified)
#ifdef NQ_HACK
Sys_Error("You must have the registered version "
"to use modified games");
#endif
#ifdef QW_HACK
Sys_Error("You must have the registered version "
"to play QuakeWorld");
#endif
#endif /* SERVERONLY */
return;
}
fread(check, 1, sizeof(check), f);
fclose(f);
for (i = 0; i < 128; i++)
if (pop[i] != (unsigned short)BigShort(check[i]))
Sys_Error("Corrupted data file.");
#ifdef NQ_HACK
Cvar_Set("cmdline", com_cmdline);
#endif
Cvar_Set("registered", "1");
static_registered = 1;
Con_Printf("Playing registered version.\n");
}
/*
================
COM_InitArgv
================
*/
void
COM_InitArgv(int argc, const char **argv)
{
qboolean safe;
int i;
#ifdef NQ_HACK
int j, n;
// reconstitute the command line for the cmdline externally visible cvar
n = 0;
for (j = 0; (j < MAX_NUM_ARGVS) && (j < argc); j++) {
i = 0;
while ((n < (CMDLINE_LENGTH - 1)) && argv[j][i]) {
com_cmdline[n++] = argv[j][i++];
}
if (n < (CMDLINE_LENGTH - 1))
com_cmdline[n++] = ' ';
else
break;
}
com_cmdline[n] = 0;
#endif
safe = false;
for (com_argc = 0; (com_argc < MAX_NUM_ARGVS) && (com_argc < argc);
com_argc++) {
largv[com_argc] = argv[com_argc];
if (!strcmp("-safe", argv[com_argc]))
safe = true;
}
if (safe) {
// force all the safe-mode switches. Note that we reserved extra space in
// case we need to add these, so we don't need an overflow check
for (i = 0; i < NUM_SAFE_ARGVS; i++) {
largv[com_argc] = safeargvs[i];
com_argc++;
}
}
largv[com_argc] = argvdummy;
com_argv = largv;
#ifdef NQ_HACK
if (COM_CheckParm("-rogue")) {
rogue = true;
standard_quake = false;
}
if (COM_CheckParm("-hipnotic")) {
hipnotic = true;
standard_quake = false;
}
#endif
}
/*
================
COM_AddParm
Adds the given string at the end of the current argument list
================
*/
#ifdef QW_HACK
void
COM_AddParm(const char *parm)
{
largv[com_argc++] = parm;
}
#endif
/*
================
COM_Init
================
*/
void
COM_Init(void)
{
union {
byte b[2];
short s;
} swaptest = {
.b = { 1, 0 }
};
// set the byte swapping variables in a portable manner
if (swaptest.s == 1) {
bigendien = false;
BigShort = ShortSwap;
LittleShort = ShortNoSwap;
BigLong = LongSwap;
LittleLong = LongNoSwap;
BigFloat = FloatSwap;
LittleFloat = FloatNoSwap;
} else {
bigendien = true;
BigShort = ShortNoSwap;
LittleShort = ShortSwap;
BigLong = LongNoSwap;
LittleLong = LongSwap;
BigFloat = FloatNoSwap;
LittleFloat = FloatSwap;
}
Cvar_RegisterVariable(&registered);
#ifdef NQ_HACK
Cvar_RegisterVariable(&cmdline);
#endif
Cmd_AddCommand("path", COM_Path_f);
COM_InitFilesystem();
COM_CheckRegistered();
}
/*
============
va
does a varargs printf into a temp buffer, so I don't need to have
varargs versions of all text functions.
============
*/
char *
va(const char *format, ...)
{
va_list argptr;
char *buf;
int len;
buf = COM_GetStrBuf();
va_start(argptr, format);
len = vsnprintf(buf, COM_STRBUF_LEN, format, argptr);
va_end(argptr);
if (len > COM_STRBUF_LEN - 1)
Con_DPrintf("%s: overflow (string truncated)\n", __func__);
return buf;
}
/*
=============================================================================
QUAKE FILESYSTEM
=============================================================================
*/
int com_filesize;
//
// in memory
//
typedef struct {
char name[MAX_QPATH];
int filepos, filelen;
} packfile_t;
typedef struct pack_s {
char filename[MAX_OSPATH];
int numfiles;
packfile_t *files;
} pack_t;
//
// on disk
//
#define MAX_PACKPATH 56
typedef struct {
char name[MAX_PACKPATH];
int filepos, filelen;
} dpackfile_t;
typedef struct {
char id[4];
int dirofs;
int dirlen;
} dpackheader_t;
#define MAX_FILES_IN_PACK 2048
char com_gamedir[MAX_OSPATH];
char com_basedir[MAX_OSPATH];
typedef struct searchpath_s {
char filename[MAX_OSPATH];
pack_t *pack; // only one of filename / pack will be used
struct searchpath_s *next;
} searchpath_t;
static searchpath_t *com_searchpaths;
#ifdef QW_HACK
static searchpath_t *com_base_searchpaths; // without gamedirs
#endif
/*
================
COM_filelength
================
*/
static int
COM_filelength(FILE *f)
{
int pos;
int end;
pos = ftell(f);
fseek(f, 0, SEEK_END);
end = ftell(f);
fseek(f, pos, SEEK_SET);
return end;
}
static int
COM_FileOpenRead(const char *path, FILE **hndl)
{
FILE *f;
f = fopen(path, "rb");
if (!f) {
*hndl = NULL;
return -1;
}
*hndl = f;
return COM_filelength(f);
}
/*
============
COM_Path_f
============
*/
static void
COM_Path_f(void)
{
searchpath_t *s;
Con_Printf("Current search path:\n");
for (s = com_searchpaths; s; s = s->next) {
#ifdef QW_HACK
if (s == com_base_searchpaths)
Con_Printf("----------\n");
#endif
if (s->pack)
Con_Printf("%s (%i files)\n", s->pack->filename,
s->pack->numfiles);
else
Con_Printf("%s\n", s->filename);
}
}
/*
============
COM_WriteFile
The filename will be prefixed by the current game directory
============
*/
void
COM_WriteFile(const char *filename, const void *data, int len)
{
FILE *f;
char name[MAX_OSPATH];
snprintf(name, sizeof(name), "%s/%s", com_gamedir, filename);
f = fopen(name, "wb");
if (!f) {
Sys_mkdir(com_gamedir);
f = fopen(name, "wb");
if (!f)
Sys_Error("Error opening %s", filename);
}
fwrite(data, 1, len, f);
fclose(f);
}
/*
============
COM_CreatePath
============
*/
void
COM_CreatePath(const char *path)
{
char part[MAX_OSPATH];
char *ofs;
if (!path || !path[0])
return;
strncpy(part, path, sizeof(part));
part[MAX_OSPATH - 1] = 0;
for (ofs = part + 1; *ofs; ofs++) {
if (*ofs == '/') { // create the directory
*ofs = 0;
Sys_mkdir(part);
*ofs = '/';
}
}
}
/*
===========
COM_FOpenFile
Finds the file in the search path.
Sets com_filesize
If the requested file is inside a packfile, a new FILE * will be opened
into the file.
===========
*/
int file_from_pak; // global indicating file came from pack file
int
COM_FOpenFile(const char *filename, FILE **file)
{
searchpath_t *search;
char path[MAX_OSPATH];
pack_t *pak;
int i;
int findtime;
file_from_pak = 0;
//
// search through the path, one element at a time
//
for (search = com_searchpaths; search; search = search->next) {
// is the element a pak file?
if (search->pack) {
// look through all the pak file elements
pak = search->pack;
for (i = 0; i < pak->numfiles; i++)
if (!strcmp(pak->files[i].name, filename)) { // found it!
// open a new file on the pakfile
*file = fopen(pak->filename, "rb");
if (!*file)
Sys_Error("Couldn't reopen %s", pak->filename);
fseek(*file, pak->files[i].filepos, SEEK_SET);
com_filesize = pak->files[i].filelen;
file_from_pak = 1;
return com_filesize;
}
} else {
// check a file in the directory tree
if (!static_registered) {
// if not a registered version, don't ever go beyond base
if (strchr(filename, '/') || strchr(filename, '\\'))
continue;
}
snprintf(path, sizeof(path), "%s/%s", search->filename, filename);
findtime = Sys_FileTime(path);
if (findtime == -1)
continue;
*file = fopen(path, "rb");
com_filesize = COM_filelength(*file);
return com_filesize;
}
}
Sys_Printf("FindFile: can't find %s\n", filename);
*file = NULL;
com_filesize = -1;
return -1;
}
static void
COM_ScanDirDir(struct stree_root *root, DIR *dir, const char *pfx,
const char *ext, qboolean stripext)
{
int pfx_len, ext_len;
struct dirent *d;
char *fname;
pfx_len = pfx ? strlen(pfx) : 0;
ext_len = ext ? strlen(ext) : 0;
while ((d = readdir(dir))) {
if ((!pfx || !strncasecmp(d->d_name, pfx, pfx_len)) &&
(!ext || COM_CheckExtension(d->d_name, ext))) {
int len = strlen(d->d_name);
if (ext && stripext)
len -= ext_len;
fname = Z_Malloc(len + 1);
if (fname) {
strncpy(fname, d->d_name, len);
fname[len] = '\0';
STree_InsertAlloc(root, fname, true);
Z_Free(fname);
}
}
}
}
static void
COM_ScanDirPak(struct stree_root *root, pack_t *pak, const char *path,
const char *pfx, const char *ext, qboolean stripext)
{
int i, path_len, pfx_len, ext_len, len;
char *pak_f, *fname;
path_len = path ? strlen(path) : 0;
pfx_len = pfx ? strlen(pfx) : 0;
ext_len = ext ? strlen(ext) : 0;
for (i = 0; i < pak->numfiles; i++) {
/* Check the path prefix */
pak_f = pak->files[i].name;
if (path && path_len) {
if (strncasecmp(pak_f, path, path_len))
continue;
if (pak_f[path_len] != '/')
continue;
pak_f += path_len + 1;
}
/* Don't match sub-directories */
if (strchr(pak_f, '/'))
continue;
/* Check the prefix and extension, if set */
if (pfx && pfx_len && strncasecmp(pak_f, pfx, pfx_len))
continue;
if (ext && ext_len && !COM_CheckExtension(pak_f, ext))
continue;
/* Ok, we have a match. Add it */
len = strlen(pak_f);
if (ext && stripext)
len -= ext_len;
fname = Z_Malloc(len + 1);
if (fname) {
strncpy(fname, pak_f, len);
fname[len] = '\0';
STree_InsertAlloc(root, fname, true);
Z_Free(fname);
}
}
}
/*
============
COM_ScanDir
Scan the contents of a the given directory. Any filenames that match
both the given prefix and extension are added to the string tree.
Caller MUST have already called STree_AllocInit()
============
*/
void
COM_ScanDir(struct stree_root *root, const char *path, const char *pfx,
const char *ext, qboolean stripext)
{
searchpath_t *search;
char fullpath[MAX_OSPATH];
DIR *dir;
for (search = com_searchpaths; search; search = search->next) {
if (search->pack) {
COM_ScanDirPak(root, search->pack, path, pfx, ext, stripext);
} else {
snprintf(fullpath, MAX_OSPATH, "%s/%s", search->filename, path);
fullpath[MAX_OSPATH - 1] = '\0';
dir = opendir(fullpath);
if (dir) {
COM_ScanDirDir(root, dir, pfx, ext, stripext);
closedir(dir);
}
}
}
}
/*
============
COM_LoadFile
Filename are reletive to the quake directory.
Optionally return length; set null if you don't want it.
Always appends a 0 byte to the loaded data.
============
*/
static cache_user_t *loadcache;
static byte *loadbuf;
static int loadsize;
static void *
COM_LoadFile(const char *path, int usehunk, size_t *size)
{
FILE *f;
byte *buf;
char base[32];
int len;
buf = NULL; // quiet compiler warning
// look for it in the filesystem or pack files
len = com_filesize = COM_FOpenFile(path, &f);
if (!f)
return NULL;
if (size)
*size = len;
// extract the filename base name for hunk tag
COM_FileBase(path, base, sizeof(base));
if (usehunk == 1)
buf = Hunk_AllocName(len + 1, base);
else if (usehunk == 2)
buf = Hunk_TempAlloc(len + 1);
else if (usehunk == 0)
buf = Z_Malloc(len + 1);
else if (usehunk == 3)
buf = Cache_Alloc(loadcache, len + 1, base);
else if (usehunk == 4) {
if (len + 1 > loadsize)
buf = Hunk_TempAlloc(len + 1);
else
buf = loadbuf;
} else
Sys_Error("%s: bad usehunk", __func__);
if (!buf)
Sys_Error("%s: not enough space for %s", __func__, path);
buf[len] = 0;
#ifndef SERVERONLY
Draw_BeginDisc();
#endif
fread(buf, 1, len, f);
fclose(f);
#ifndef SERVERONLY
Draw_EndDisc();
#endif
return buf;
}
void *
COM_LoadHunkFile(const char *path)
{
return COM_LoadFile(path, 1, NULL);
}
void *
COM_LoadTempFile(const char *path)
{
return COM_LoadFile(path, 2, NULL);
}
void
COM_LoadCacheFile(const char *path, struct cache_user_s *cu)
{
loadcache = cu;
COM_LoadFile(path, 3, NULL);
}
// uses temp hunk if larger than bufsize
// size is size of loaded file in bytes
void *
COM_LoadStackFile(const char *path, void *buffer, int bufsize, size_t *size)
{
byte *buf;
loadbuf = (byte *)buffer;
loadsize = bufsize;
buf = COM_LoadFile(path, 4, size);
return buf;
}
/*
=================
COM_LoadPackFile
Takes an explicit (not game tree related) path to a pak file.
Loads the header and directory, adding the files at the beginning
of the list so they override previous pack files.
=================
*/
static pack_t *
COM_LoadPackFile(const char *packfile)
{
dpackheader_t header;
int i;
packfile_t *newfiles;
int numpackfiles;
pack_t *pack;
FILE *packhandle;
dpackfile_t info[MAX_FILES_IN_PACK];
unsigned short crc;
if (COM_FileOpenRead(packfile, &packhandle) == -1)
return NULL;
fread(&header, 1, sizeof(header), packhandle);
if (header.id[0] != 'P' || header.id[1] != 'A'
|| header.id[2] != 'C' || header.id[3] != 'K')
Sys_Error("%s is not a packfile", packfile);
header.dirofs = LittleLong(header.dirofs);
header.dirlen = LittleLong(header.dirlen);
numpackfiles = header.dirlen / sizeof(dpackfile_t);
if (numpackfiles > MAX_FILES_IN_PACK)
Sys_Error("%s has %i files", packfile, numpackfiles);
if (numpackfiles != ID1_PAK0_COUNT)
com_modified = true; // not the original file
#ifdef NQ_HACK
newfiles = Hunk_AllocName(numpackfiles * sizeof(packfile_t), "packfile");
#endif
#ifdef QW_HACK
newfiles = Z_Malloc(numpackfiles * sizeof(packfile_t));
#endif
fseek(packhandle, header.dirofs, SEEK_SET);
fread(&info, 1, header.dirlen, packhandle);
// crc the directory to check for modifications
crc = CRC_Block((byte *)info, header.dirlen);
switch (crc) {
#ifdef NQ_HACK
case ID1_PAK0_CRC_V100:
case ID1_PAK0_CRC_V101:
case ID1_PAK0_CRC_V106:
#endif
#ifdef QW_HACK
case QW_PAK0_CRC:
#endif
com_modified = false;
break;
default:
com_modified = true;
break;
}
// parse the directory
for (i = 0; i < numpackfiles; i++) {
strcpy(newfiles[i].name, info[i].name);
newfiles[i].filepos = LittleLong(info[i].filepos);
newfiles[i].filelen = LittleLong(info[i].filelen);
}
#ifdef NQ_HACK
pack = Hunk_Alloc(sizeof(pack_t));
#endif
#ifdef QW_HACK
pack = Z_Malloc(sizeof(pack_t));
#endif
strcpy(pack->filename, packfile);
pack->numfiles = numpackfiles;
pack->files = newfiles;
Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
return pack;
}
/*
================
COM_AddGameDirectory
Sets com_gamedir, adds the directory to the head of the path,
then loads and adds pak1.pak pak2.pak ...
================
*/
static void
COM_AddGameDirectory(const char *base, const char *dir)
{
int i;
searchpath_t *search;
pack_t *pak;
char pakfile[MAX_OSPATH];
if (!base)
return;
strcpy(com_gamedir, va("%s/%s", base, dir));
#ifdef QW_HACK
{
char *p;
p = strrchr(com_gamedir, '/');
strcpy(gamedirfile, ++p);
}
#endif
//
// add the directory to the search path
//
search = Hunk_Alloc(sizeof(searchpath_t));
strcpy(search->filename, com_gamedir);
search->next = com_searchpaths;
com_searchpaths = search;
//
// add any pak files in the format pak0.pak pak1.pak, ...
//
for (i = 0;; i++) {
snprintf(pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i);
pak = COM_LoadPackFile(pakfile);
if (!pak)
break;
search = Hunk_Alloc(sizeof(searchpath_t));
search->pack = pak;
search->next = com_searchpaths;
com_searchpaths = search;
}
}
/*
================
COM_Gamedir
Sets the gamedir and path to a different directory.
FIXME - if home dir is available, should we create ~/.tyrquake/gamedir ??
================
*/
#ifdef QW_HACK
void
COM_Gamedir(const char *dir)
{
searchpath_t *search, *next;
int i;
pack_t *pak;
char pakfile[MAX_OSPATH];
if (strstr(dir, "..") || strstr(dir, "/")
|| strstr(dir, "\\") || strstr(dir, ":")) {
Con_Printf("Gamedir should be a single filename, not a path\n");
return;
}
if (!strcmp(gamedirfile, dir))
return; // still the same
strcpy(gamedirfile, dir);
//
// free up any current game dir info
//
while (com_searchpaths != com_base_searchpaths) {
if (com_searchpaths->pack) {
Z_Free(com_searchpaths->pack->files);
Z_Free(com_searchpaths->pack);
}
next = com_searchpaths->next;
Z_Free(com_searchpaths);
com_searchpaths = next;
}
//
// flush all data, so it will be forced to reload
//
Cache_Flush();
if (!strcmp(dir, "id1") || !strcmp(dir, "qw"))
return;
snprintf(com_gamedir, sizeof(com_gamedir), "%s/%s", com_basedir, dir);
//
// add the directory to the search path
//
search = Z_Malloc(sizeof(searchpath_t));
strcpy(search->filename, com_gamedir);
search->next = com_searchpaths;
com_searchpaths = search;
//
// add any pak files in the format pak0.pak pak1.pak, ...
//
for (i = 0;; i++) {
snprintf(pakfile, sizeof(pakfile), "%s/pak%i.pak", com_gamedir, i);
pak = COM_LoadPackFile(pakfile);
if (!pak)
break;
search = Z_Malloc(sizeof(searchpath_t));
search->pack = pak;
search->next = com_searchpaths;
com_searchpaths = search;
}
}
#endif
/*
================
COM_InitFilesystem
================
*/
static void
COM_InitFilesystem(void)
{
int i;
char *home;
#ifdef NQ_HACK
searchpath_t *search;
#endif
home = getenv("HOME");
//
// -basedir <path>
// Overrides the system supplied base directory (under id1)
//
i = COM_CheckParm("-basedir");
if (i && i < com_argc - 1)
strcpy(com_basedir, com_argv[i + 1]);
else
strcpy(com_basedir, host_parms.basedir);
//
// start up with id1 by default
//
COM_AddGameDirectory(com_basedir, "id1");
COM_AddGameDirectory(home, ".tyrquake/id1");
#ifdef NQ_HACK
if (COM_CheckParm("-rogue")) {
COM_AddGameDirectory(com_basedir, "rogue");
COM_AddGameDirectory(home, ".tyrquake/rogue");
}
if (COM_CheckParm("-hipnotic")) {
COM_AddGameDirectory(com_basedir, "hipnotic");
COM_AddGameDirectory(home, ".tyrquake/hipnotic");
}
//
// -game <gamedir>
// Adds basedir/gamedir as an override game
//
i = COM_CheckParm("-game");
if (i && i < com_argc - 1) {
com_modified = true;
COM_AddGameDirectory(com_basedir, com_argv[i + 1]);
COM_AddGameDirectory(home, va(".tyrquake/%s", com_argv[i + 1]));
}
#endif
#ifdef QW_HACK
COM_AddGameDirectory(com_basedir, "qw");
COM_AddGameDirectory(home, ".tyrquake/qw");
#endif
/* If home is available, create the game directory */
if (home) {
COM_CreatePath(com_gamedir);
Sys_mkdir(com_gamedir);
}
//
// -path <dir or packfile> [<dir or packfile>] ...
// Fully specifies the exact search path, overriding the generated one
//
#ifdef NQ_HACK
i = COM_CheckParm("-path");
if (i) {
com_modified = true;
com_searchpaths = NULL;
while (++i < com_argc) {
if (!com_argv[i] || com_argv[i][0] == '+'
|| com_argv[i][0] == '-')
break;
search = Hunk_Alloc(sizeof(searchpath_t));
if (!strcmp(COM_FileExtension(com_argv[i]), "pak")) {
search->pack = COM_LoadPackFile(com_argv[i]);
if (!search->pack)
Sys_Error("Couldn't load packfile: %s", com_argv[i]);
} else
strcpy(search->filename, com_argv[i]);
search->next = com_searchpaths;
com_searchpaths = search;
}
}
#endif
#ifdef QW_HACK
// any set gamedirs will be freed up to here
com_base_searchpaths = com_searchpaths;
#endif
}
// FIXME - everything below is QW only... move it?
#ifdef QW_HACK
/*
=====================================================================
INFO STRINGS
=====================================================================
*/
/*
* INFO HELPER FUNCTIONS
*
* Helper fuction to copy off the next string in the info string
* Copies the next '\\' separated string into the buffer of size buflen
* Always zero terminates the buffer
* Returns a pointer to the char following the returned string.
*
* Key/Value checks are consolidated into Info_ReadKey and Info_ReadValue
*/
static const char *
Info_ReadString(const char *infostring, char *buffer, int buflen)
{
char *out = buffer;
while (out - buffer < buflen - 1) {
if (!*infostring || *infostring == '\\')
break;
*out++ = *infostring++;
}
*out = 0;
return infostring;
}
static const char *
Info_ReadKey(const char *infostring, char *buffer, int buflen)
{
const char *pkey;
if (*infostring == '\\')
infostring++;
pkey = infostring;
infostring = Info_ReadString(infostring, buffer, buflen);
/* If we aren't at a separator, then the key was too long */
if (*buffer && *infostring != '\\')
Con_DPrintf("WARNING: No separator after info key (%s)\n", pkey);
return infostring;
}
static const char *
Info_ReadValue(const char *infostring, char *buffer, int buflen)
{
infostring = Info_ReadString(infostring, buffer, buflen);
/* If we aren't at a separator, then the value was too long */
if (*infostring && *infostring != '\\')
Con_DPrintf("WARNING: info value too long? (%s)\n", buffer);
return infostring;
}
/*
===============
Info_ValueForKey
Searches the string for the given
key and returns the associated value, or an empty string.
===============
*/
char *
Info_ValueForKey(const char *infostring, const char *key)
{
/* use multiple buffers so compares work without stomping on each other */
static char buffers[4][MAX_INFO_STRING];
static int buffer_index;
char pkey[MAX_INFO_STRING];
char *buf;
buffer_index = (buffer_index + 1) & 3;
buf = buffers[buffer_index];
while (1) {
/* Retrieve the key */
infostring = Info_ReadKey(infostring, pkey, sizeof(pkey));
if (*infostring != '\\') {
*buf = 0;
return buf;
}
infostring++;
/* Retrieve the value */
infostring = Info_ReadString(infostring, buf, sizeof(buffers[0]));
if (*infostring && *infostring != '\\') {
*buf = 0;
return buf;
}
/* If the keys match, return the value in the buffer */
if (!strcmp(key, pkey))
return buf;
/* Return if we've reached the end of the infostring */
if (!*infostring) {
*buf = 0;
return buf;
}
infostring++;
}
}
void
Info_RemoveKey(char *infostring, const char *key)
{
char *start;
char pkey[MAX_INFO_STRING];
char value[MAX_INFO_STRING];
if (strstr(key, "\\")) {
Con_Printf("Can't use a key with a \\\n");
return;
}
while (1) {
start = infostring;
infostring = (char *)Info_ReadKey(infostring, pkey, sizeof(pkey));
if (*infostring)
infostring++;
infostring = (char *)Info_ReadValue(infostring, value, sizeof(value));
if (*infostring)
infostring++;
/* If the keys match, remove this part of the string */
if (!strcmp(key, pkey)) {
memmove(start, infostring, strlen(infostring) + 1);
return;
}
if (!*infostring)
return;
}
}
void
Info_RemovePrefixedKeys(char *infostring, char prefix)
{
char *start;
char pkey[MAX_INFO_STRING];
char value[MAX_INFO_STRING];
start = infostring;
while (1) {
infostring = (char *)Info_ReadKey(infostring, pkey, sizeof(pkey));
if (*infostring)
infostring++;
infostring = (char *)Info_ReadValue(infostring, value, sizeof(value));
if (*infostring)
infostring++;
/* If the prefix matches, remove the key */
if (pkey[0] == prefix) {
Info_RemoveKey(start, pkey);
infostring = start;
}
if (!*infostring)
return;
}
}
void
Info_SetValueForStarKey(char *infostring, const char *key, const char *value,
int maxsize)
{
char buffer[MAX_INFO_STRING * 2];
char *oldvalue, *info;
int c, len;
if (strstr(key, "\\") || strstr(value, "\\")) {
Con_Printf("Can't use keys or values with a \\\n");
return;
}
if (strstr(key, "\"") || strstr(value, "\"")) {
Con_Printf("Can't use keys or values with a \"\n");
return;
}
if (strlen(key) > 63 || strlen(value) > 63) {
Con_Printf("Keys and values must be < 64 characters.\n");
return;
}
oldvalue = Info_ValueForKey(infostring, key);
if (*oldvalue) {
/*
* Key exists. Make sure we have enough room for new value.
* If we don't, don't change it!
*/
len = strlen(infostring) - strlen(oldvalue) + strlen(value);
if (len > maxsize - 1)
goto ErrTooLong;
}
Info_RemoveKey(infostring, key);
if (!value || !strlen(value))
return;
len = snprintf(buffer, sizeof(buffer), "\\%s\\%s", key, value);
if (len > sizeof(buffer) - 1)
goto ErrTooLong;
len += strlen(infostring);
if (len > maxsize - 1)
goto ErrTooLong;
/* Append the new key/value pair to the info string */
infostring += strlen(infostring);
info = buffer;
while (*info) {
c = (unsigned char)*info++;
#ifndef SERVERONLY
/* client only allows highbits on name */
if (strcasecmp(key, "name")) {
c &= 127;
if (c < 32 || c > 127)
continue;
/* auto lowercase team */
if (!strcasecmp(key, "team"))
c = tolower(c);
}
#else
if (!sv_highchars.value) {
c &= 127;
if (c < 32 || c > 127)
continue;
}
#endif
if (c > 13)
*infostring++ = c;
}
*infostring = 0;
return;
ErrTooLong:
Con_Printf("Info string length exceeded\n");
}
void
Info_SetValueForKey(char *infostring, const char *key, const char *value,
int maxsize)
{
if (key[0] == '*') {
Con_Printf("Can't set * keys\n");
return;
}
Info_SetValueForStarKey(infostring, key, value, maxsize);
}
void
Info_Print(const char *infostring)
{
char key[MAX_INFO_STRING];
char value[MAX_INFO_STRING];
while (*infostring) {
infostring = Info_ReadKey(infostring, key, sizeof(key));
if (*infostring)
infostring++;
infostring = Info_ReadValue(infostring, value, sizeof(value));
if (*infostring)
infostring++;
Con_Printf("%-20.20s %s\n", key, *value ? value : "MISSING VALUE");
}
}
static byte 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
};
#if 0
/*
====================
COM_BlockSequenceCheckByte
For proxy protecting
====================
*/
byte
COM_BlockSequenceCheckByte(const byte *base, int length, int sequence,
unsigned mapchecksum)
{
static unsigned last_mapchecksum;
unsigned char chkbuf[16 + 60 + 4];
int checksum;
byte *p;
if (last_mapchecksum != mapchecksum) {
last_mapchecksum = mapchecksum;
chktbl[1024] = (mapchecksum & 0xff000000) >> 24;
chktbl[1025] = (mapchecksum & 0x00ff0000) >> 16;
chktbl[1026] = (mapchecksum & 0x0000ff00) >> 8;
chktbl[1027] = (mapchecksum & 0x000000ff);
Com_BlockFullChecksum(chktbl, sizeof(chktbl), chkbuf);
}
p = chktbl + (sequence % (sizeof(chktbl) - 8));
if (length > 60)
length = 60;
memcpy(chkbuf + 16, base, length);
length += 16;
chkbuf[length] = (sequence & 0xff) ^ p[0];
chkbuf[length + 1] = p[1];
chkbuf[length + 2] = ((sequence >> 8) & 0xff) ^ p[2];
chkbuf[length + 3] = p[3];
length += 4;
checksum = LittleLong(Com_BlockChecksum(chkbuf, length));
checksum &= 0xff;
return checksum;
}
#endif
/*
====================
COM_BlockSequenceCRCByte
For proxy protecting
====================
*/
byte
COM_BlockSequenceCRCByte(const byte *base, int length, int sequence)
{
unsigned short crc;
const byte *p;
byte 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];
length += 4;
crc = CRC_Block(chkb, length);
crc &= 0xff;
return crc;
}
// char *date = "Oct 24 1996";
static const char *date = __DATE__;
static const char *mon[12] =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"
};
static char mond[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// returns days since Oct 24 1996
int
build_number(void)
{
int m = 0;
int d = 0;
int y = 0;
static int b = 0;
if (b != 0)
return b;
for (m = 0; m < 11; m++) {
if (strncasecmp(&date[0], mon[m], 3) == 0)
break;
d += mond[m];
}
d += atoi(&date[4]) - 1;
y = atoi(&date[7]) - 1900;
b = d + (int)((y - 1) * 365.25);
if (((y % 4) == 0) && m > 1) {
b += 1;
}
b -= 35778; // Dec 16 1998
return b;
}
#endif /* QW_HACK */