diff --git a/qw/include/Makefile.am b/qw/include/Makefile.am index 8a611d61d..0de8bcc30 100644 --- a/qw/include/Makefile.am +++ b/qw/include/Makefile.am @@ -1,7 +1,8 @@ ## Process this file with automake to produce Makefile.in AUTOMAKE_OPTIONS= foreign -EXTRA_DIST = bothdefs.h cl_cam.h cl_demo.h cl_ents.h cl_input.h cl_main.h \ +EXTRA_DIST = \ + bothdefs.h cl_cam.h cl_demo.h cl_ents.h cl_input.h cl_main.h \ cl_parse.h cl_pred.h cl_skin.h cl_slist.h cl_tent.h client.h \ crudefile.h game.h host.h msg_ucmd.h net.h pmove.h \ - protocol.h server.h sv_pr_cmds.h sv_progs.h + protocol.h server.h sv_demo.h sv_pr_cmds.h sv_progs.h diff --git a/qw/include/server.h b/qw/include/server.h index b12600be4..5694f8f93 100644 --- a/qw/include/server.h +++ b/qw/include/server.h @@ -111,6 +111,9 @@ typedef struct int num_signon_buffers; int signon_buffer_size[MAX_SIGNON_BUFFERS]; byte signon_buffers[MAX_SIGNON_BUFFERS][MAX_DATAGRAM]; + + // demo stuff + qboolean demorecording; } server_t; #define NUM_SPAWN_PARMS 16 @@ -162,6 +165,7 @@ typedef struct client_s int userid; // identifying number struct info_s *userinfo; // infostring + const char *team;//FIXME demo usercmd_t lastcmd; // for filling in big drops and partial predictions double localtime; // of last message @@ -287,6 +291,10 @@ typedef struct byte log_buf[2][MAX_DATAGRAM]; challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + + // demo stuff + byte *demomem; + int demomemsize; } server_static_t; //============================================================================= diff --git a/qw/include/sv_demo.h b/qw/include/sv_demo.h new file mode 100644 index 000000000..feed8f5ee --- /dev/null +++ b/qw/include/sv_demo.h @@ -0,0 +1,116 @@ +/* +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 included (GNU.txt) 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. + +*/ + +#ifndef __sv_demo_h +#define __sv_demo_h + +#include "QF/quakeio.h" +#include "QF/sizebuf.h" + +#include "server.h" + +#define MAX_DEMO_PACKET_ENTITIES 64 + +typedef struct { + byte *data; + int start, end, last; + int maxsize; +} dbuffer_t; + +typedef struct { + byte type; + byte full; + int to; + int size; + byte data[1]; // gcc doesn't allow [] (?) +} header_t; + +typedef struct { + vec3_t origin; + vec3_t angles; + int weaponframe; + int skinnum; + int model; + int effects; +} demoinfo_t; + +typedef struct { + demoinfo_t info; + float sec; + int parsecount; + qboolean fixangle; + vec3_t angle; + float cmdtime; + int flags; + int frame; +} demo_client_t; + +typedef struct { + qboolean allowoverflow; // if false, do a Sys_Error + qboolean overflowed; // set to true if the buffer size + // failed + byte *data; + int maxsize; + int cursize; + int bufsize; + header_t *h; +} demobuf_t; + +typedef struct { + demo_client_t clients[MAX_CLIENTS]; + double time; + demobuf_t buf; +} demo_frame_t; + +#define DEMO_FRAMES 64 +#define DEMO_FRAMES_MASK (DEMO_FRAMES - 1) + +typedef struct { + QFile *file; + + demobuf_t *dbuf; + dbuffer_t dbuffer; + sizebuf_t datagram; + byte datagram_data[MAX_DATAGRAM]; + int lastto; + int lasttype; + double time, pingtime; + int stats[MAX_CLIENTS][MAX_CL_STATS]; // ouch! + client_t recorder; + qboolean fixangle[MAX_CLIENTS]; + float fixangletime[MAX_CLIENTS]; + vec3_t angles[MAX_CLIENTS]; + struct dstring_s *name; + struct dstring_s *path; + int parsecount; + int lastwritten; + demo_frame_t frames[DEMO_FRAMES]; + demoinfo_t info[MAX_CLIENTS]; + int size; + qboolean disk; + void *dest; + byte *mfile; + byte buffer[20 * MAX_MSGLEN]; + int bufsize; +} demo_t; + +extern demo_t demo; + +#endif//__sv_demo_h diff --git a/qw/source/Makefile.am b/qw/source/Makefile.am index 25510c768..65399658b 100644 --- a/qw/source/Makefile.am +++ b/qw/source/Makefile.am @@ -77,9 +77,9 @@ syssv_SRC= sv_sys_unix.c endif libserver_a_SOURCES= \ - crudefile.c sv_ccmds.c sv_ents.c sv_init.c sv_main.c sv_move.c \ - sv_nchan.c sv_phys.c sv_pr_cmds.c sv_progs.c sv_send.c sv_user.c \ - world.c $(syssv_SRC) + crudefile.c sv_ccmds.c sv_demo.c sv_ents.c sv_init.c sv_main.c \ + sv_move.c sv_nchan.c sv_phys.c sv_pr_cmds.c sv_progs.c sv_send.c \ + sv_user.c world.c $(syssv_SRC) qf_server_LIBS= \ $(SERVER_PLUGIN_STATIC_LIBS) \ diff --git a/qw/source/sv_demo.c b/qw/source/sv_demo.c new file mode 100644 index 000000000..c2615e4f6 --- /dev/null +++ b/qw/source/sv_demo.c @@ -0,0 +1,1768 @@ +/* +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 included (GNU.txt) 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. + +*/ +static const char rcsid[] = + "$Id$"; + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#ifdef HAVE_STRING_H +# include "string.h" +#endif +#ifdef HAVE_STRINGS_H +# include "strings.h" +#endif +#ifdef HAVE_UNISTD_H +# include "unistd.h" +#endif +#include +#include + +#include "QF/cmd.h" +#include "QF/cvar.h" +#include "QF/dstring.h" +#include "QF/info.h" +#include "QF/msg.h" +#include "QF/qargs.h" +#include "QF/qendian.h" +#include "QF/quakefs.h" +#include "QF/sys.h" +#include "QF/va.h" + +#include "pmove.h" +#include "server.h" +#include "sv_demo.h" +#include "sv_progs.h" + +demo_t demo; + +void DemoWrite_Begin (byte type, int to, int size); +void SV_DemoWritePackets (int num); +void SV_Stop_f (void); + +#define MIN_DEMO_MEMORY 0x100000 +#define USACACHE (sv_demoUseCache->int_val && svs.demomemsize) +#define DWRITE(a,b,d) dwrite((QFile *)d,a,b) +#define MAXSIZE (demobuffer->end < demobuffer->last ? \ + demobuffer->start - demobuffer->end : \ + demobuffer->maxsize - demobuffer->end) + + +cvar_t *sv_demoUseCache; +cvar_t *sv_demoCacheSize; +cvar_t *sv_demoMaxDirSize; +cvar_t *sv_demoDir; +cvar_t *sv_demofps; +cvar_t *sv_demoPings; +cvar_t *sv_demoNoVis; +cvar_t *sv_demoMaxSize; + +static int demo_max_size; +static int demo_size; +cvar_t *sv_demoPrefix; +cvar_t *sv_demoSuffix; +cvar_t *sv_onrecordfinish; +cvar_t *sv_ondemoremove; +cvar_t *sv_demotxt; +cvar_t *serverdemo; + +void SV_WriteDemoMessage (sizebuf_t *msg, int type, int to, float time); + +int (*dwrite) (QFile * file, const void *buf, int count); + +static dbuffer_t *demobuffer; +static int header = (int) &((header_t *) 0)->data; + +entity_state_t demo_entities[UPDATE_MASK + 1][MAX_DEMO_PACKET_ENTITIES]; + +// only one .. is allowed (security) +qboolean +sv_demoDir_OnChange (cvar_t *cvar, char *value) +{ + if (!value[0]) + return true; + + if (value[0] == '.' && value[1] == '.') + value += 2; + if (strstr (value, "/..")) + return true; + + return false; +} + +void +SV_DemoPings (void) +{ + client_t *client; + int j; + + for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { + if (client->state != cs_spawned) + continue; + + DemoWrite_Begin (dem_all, 0, 7); + MSG_WriteByte ((sizebuf_t *) demo.dbuf, svc_updateping); + MSG_WriteByte ((sizebuf_t *) demo.dbuf, j); + MSG_WriteShort ((sizebuf_t *) demo.dbuf, SV_CalcPing (client)); + MSG_WriteByte ((sizebuf_t *) demo.dbuf, svc_updatepl); + MSG_WriteByte ((sizebuf_t *) demo.dbuf, j); + MSG_WriteByte ((sizebuf_t *) demo.dbuf, client->lossage); + } +} + +void +DemoBuffer_Init (dbuffer_t * dbuffer, byte * buf, size_t size) +{ + demobuffer = dbuffer; + + demobuffer->data = buf; + demobuffer->maxsize = size; + demobuffer->start = 0; + demobuffer->end = 0; + demobuffer->last = 0; +} + +/* + Demo_SetMsgBuf + + Sets the frame message buffer +*/ + +void +DemoSetMsgBuf (demobuf_t * prev, demobuf_t * cur) +{ + // fix the maxsize of previous msg buffer, + // we won't be able to write there anymore + if (prev != NULL) + prev->maxsize = prev->bufsize; + + demo.dbuf = cur; + memset (demo.dbuf, 0, sizeof (*demo.dbuf)); + + demo.dbuf->data = demobuffer->data + demobuffer->end; + demo.dbuf->maxsize = MAXSIZE; +} + +/* + DemoWriteToDisk + + Writes to disk a message meant for specifc client + or all messages if type == 0 + Message is cleared from demobuf after that +*/ + +void +SV_DemoWriteToDisk (int type, int to, float time) +{ + int pos = 0, oldm, oldd; + header_t *p; + int size; + sizebuf_t msg; + + (byte *) p = demo.dbuf->data; + demo.dbuf->h = NULL; + + oldm = demo.dbuf->bufsize; + oldd = demobuffer->start; + while (pos < demo.dbuf->bufsize) { + size = p->size; + pos += header + size; + + // no type means we are writing to disk everything + if (!type || (p->type == type && p->to == to)) { + if (size) { + msg.data = p->data; + msg.cursize = size; + + SV_WriteDemoMessage (&msg, p->type, p->to, time); + } + // data is written so it need to be cleard from demobuf + if (demo.dbuf->data != (byte *) p) + memmove (demo.dbuf->data + size + header, demo.dbuf->data, + (byte *) p - demo.dbuf->data); + + demo.dbuf->bufsize -= size + header; + demo.dbuf->data += size + header; + pos -= size + header; + demo.dbuf->maxsize -= size + header; + demobuffer->start += size + header; + } + // move along + (byte *) p = p->data + size; + } + + if (demobuffer->start == demobuffer->last) { + if (demobuffer->start == demobuffer->end) { + demobuffer->end = 0; // demobuffer is empty + demo.dbuf->data = demobuffer->data; + } + // go back to begining of the buffer + demobuffer->last = demobuffer->end; + demobuffer->start = 0; + } +} + +/* + DemoSetBuf + + Sets position in the buf for writing to specific client +*/ + +static void +DemoSetBuf (byte type, int to) +{ + header_t *p; + int pos = 0; + + (byte *) p = demo.dbuf->data; + + while (pos < demo.dbuf->bufsize) { + pos += header + p->size; + + if (type == p->type && to == p->to && !p->full) { + demo.dbuf->cursize = pos; + demo.dbuf->h = p; + return; + } + + (byte *) p = p->data + p->size; + } + // type&&to not exist in the buf, so add it + + p->type = type; + p->to = to; + p->size = 0; + p->full = 0; + + demo.dbuf->bufsize += header; + demo.dbuf->cursize = demo.dbuf->bufsize; + demobuffer->end += header; + demo.dbuf->h = p; +} + +void +DemoMoveBuf (void) +{ + // set the last message mark to the previous frame (i/e begining of this + // one) + demobuffer->last = demobuffer->end - demo.dbuf->bufsize; + + // move buffer to the begining of demo buffer + memmove (demobuffer->data, demo.dbuf->data, demo.dbuf->bufsize); + demo.dbuf->data = demobuffer->data; + demobuffer->end = demo.dbuf->bufsize; + demo.dbuf->h = NULL; // it will be setup again + demo.dbuf->maxsize = MAXSIZE + demo.dbuf->bufsize; +} + +void +DemoWrite_Begin (byte type, int to, int size) +{ + byte *p; + qboolean move = false; + + // will it fit? + while (demo.dbuf->bufsize + size + header > demo.dbuf->maxsize) { + // if we reached the end of buffer move msgbuf to the begining + if (!move && demobuffer->end > demobuffer->start) + move = true; + + SV_DemoWritePackets (1); + if (move && demobuffer->start > demo.dbuf->bufsize + header + size) + DemoMoveBuf (); + } + + if (demo.dbuf->h == NULL || demo.dbuf->h->type != type + || demo.dbuf->h->to != to || demo.dbuf->h->full) { + DemoSetBuf (type, to); + } + + if (demo.dbuf->h->size + size > MAX_MSGLEN) { + demo.dbuf->h->full = 1; + DemoSetBuf (type, to); + } + // we have to make room for new data + if (demo.dbuf->cursize != demo.dbuf->bufsize) { + p = demo.dbuf->data + demo.dbuf->cursize; + memmove (p + size, p, demo.dbuf->bufsize - demo.dbuf->cursize); + } + + demo.dbuf->bufsize += size; + demo.dbuf->h->size += size; + if ((demobuffer->end += size) > demobuffer->last) + demobuffer->last = demobuffer->end; +} + +/* + SV_WriteDemoMessage + + Dumps the current net message, prefixed by the length and view angles +*/ +void +SV_WriteDemoMessage (sizebuf_t *msg, int type, int to, float time) +{ + int len, i, msec; + byte c; + static double prevtime; + + if (!sv.demorecording) + return; + + msec = (time - prevtime) * 1000; + prevtime += msec * 0.001; + if (msec > 255) + msec = 255; + if (msec < 2) + msec = 0; + + c = msec; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + + if (demo.lasttype != type || demo.lastto != to) { + demo.lasttype = type; + demo.lastto = to; + switch (demo.lasttype) { + case dem_all: + c = dem_all; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + break; + case dem_multiple: + c = dem_multiple; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + + i = LittleLong (demo.lastto); + demo.size += DWRITE (&i, sizeof (i), demo.dest); + break; + case dem_single: + case dem_stats: + c = demo.lasttype + (demo.lastto << 3); + demo.size += DWRITE (&c, sizeof (c), demo.dest); + break; + default: + SV_Stop_f (); + Con_Printf ("bad demo message type:%d", type); + return; + } + } else { + c = dem_read; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + } + + + len = LittleLong (msg->cursize); + demo.size += DWRITE (&len, 4, demo.dest); + demo.size += DWRITE (msg->data, msg->cursize, demo.dest); + + if (demo.disk) + Qflush (demo.file); + else if (demo.size - demo_size > demo_max_size) { + demo_size = demo.size; + demo.mfile -= 0x80000; + Qwrite (demo.file, svs.demomem, 0x80000); + Qflush (demo.file); + memmove (svs.demomem, svs.demomem + 0x80000, demo.size - 0x80000); + } +} + + +/* + SV_DemoWritePackets + + Interpolates to get exact players position for current frame + and writes packets to the disk/memory +*/ + +float +adjustangle (float current, float ideal, float fraction) +{ + float move; + + move = ideal - current; + if (ideal > current) { + + if (move >= 180) + move = move - 360; + } else { + if (move <= -180) + move = move + 360; + } + + move *= fraction; + + return (current + move); +} + +#define DF_ORIGIN 1 +#define DF_ANGLES (1<<3) +#define DF_EFFECTS (1<<6) +#define DF_SKINNUM (1<<7) +#define DF_DEAD (1<<8) +#define DF_GIB (1<<9) +#define DF_WEAPONFRAME (1<<10) +#define DF_MODEL (1<<11) + +void +SV_DemoWritePackets (int num) +{ + demo_frame_t *frame, *nextframe; + demo_client_t *cl, *nextcl = 0; + int i, j, flags; + qboolean valid; + double time, playertime, nexttime; + float f; + vec3_t origin, angles; + sizebuf_t msg; + byte msg_buf[MAX_MSGLEN]; + demoinfo_t *demoinfo; + + if (!sv.demorecording) + return; + + msg.data = msg_buf; + msg.maxsize = sizeof (msg_buf); + + if (num > demo.parsecount - demo.lastwritten + 1) + num = demo.parsecount - demo.lastwritten + 1; + + // 'num' frames to write + for (; num; num--, demo.lastwritten++) { + frame = &demo.frames[demo.lastwritten & DEMO_FRAMES_MASK]; + time = frame->time; + nextframe = frame; + msg.cursize = 0; + + demo.dbuf = &frame->buf; + + // find two frames + // one before the exact time (time - msec) and one after, + // then we can interpolte exact position for current frame + for (i = 0, cl = frame->clients, demoinfo = demo.info; i < MAX_CLIENTS; + i++, cl++, demoinfo++) { + if (cl->parsecount != demo.lastwritten) + continue; // not valid + + nexttime = playertime = time - cl->sec; + + for (j = demo.lastwritten + 1, valid = false; + nexttime < time && j < demo.parsecount; j++) { + nextframe = &demo.frames[j & DEMO_FRAMES_MASK]; + nextcl = &nextframe->clients[i]; + + if (nextcl->parsecount != j) + break; // disconnected? + if (nextcl->fixangle) + break; // respawned, or walked into + // teleport, do not interpolate! + if (!(nextcl->flags & DF_DEAD) && (cl->flags & DF_DEAD)) + break; // respawned, do not interpolate + + nexttime = nextframe->time - nextcl->sec; + + if (nexttime >= time) { + // good, found what we were looking for + valid = true; + break; + } + } + + if (valid) { + f = (time - nexttime) / (nexttime - playertime); + for (j = 0; j < 3; j++) { + angles[j] = adjustangle (cl->info.angles[j], + nextcl->info.angles[j], 1.0 + f); + origin[j] = (nextcl->info.origin[j] + + f * (nextcl->info.origin[j] + - cl->info.origin[j])); + } + } else { + VectorCopy (cl->info.origin, origin); + VectorCopy (cl->info.angles, angles); + } + + // now write it to buf + flags = cl->flags; + + if (cl->fixangle) { + demo.fixangletime[i] = cl->cmdtime; + } + + for (j = 0; j < 3; j++) + if (origin[j] != demoinfo->origin[i]) + flags |= DF_ORIGIN << j; + + if (cl->fixangle || demo.fixangletime[i] != cl->cmdtime) { + for (j = 0; j < 3; j++) + if (angles[j] != demoinfo->angles[j]) + flags |= DF_ANGLES << j; + } + + if (cl->info.model != demoinfo->model) + flags |= DF_MODEL; + if (cl->info.effects != demoinfo->effects) + flags |= DF_EFFECTS; + if (cl->info.skinnum != demoinfo->skinnum) + flags |= DF_SKINNUM; + if (cl->info.weaponframe != demoinfo->weaponframe) + flags |= DF_WEAPONFRAME; + + MSG_WriteByte (&msg, svc_playerinfo); + MSG_WriteByte (&msg, i); + MSG_WriteShort (&msg, flags); + + MSG_WriteByte (&msg, cl->frame); + + for (j = 0; j < 3; j++) + if (flags & (DF_ORIGIN << j)) + MSG_WriteCoord (&msg, origin[j]); + + for (j = 0; j < 3; j++) + if (flags & (DF_ANGLES << j)) + MSG_WriteAngle16 (&msg, angles[j]); + + + if (flags & DF_MODEL) + MSG_WriteByte (&msg, cl->info.model); + + if (flags & DF_SKINNUM) + MSG_WriteByte (&msg, cl->info.skinnum); + + if (flags & DF_EFFECTS) + MSG_WriteByte (&msg, cl->info.effects); + + if (flags & DF_WEAPONFRAME) + MSG_WriteByte (&msg, cl->info.weaponframe); + + VectorCopy (cl->info.origin, demoinfo->origin); + VectorCopy (cl->info.angles, demoinfo->angles); + demoinfo->skinnum = cl->info.skinnum; + demoinfo->effects = cl->info.effects; + demoinfo->weaponframe = cl->info.weaponframe; + demoinfo->model = cl->info.model; + } + + // this goes first to reduce demo size a bit + SV_DemoWriteToDisk (demo.lasttype, demo.lastto, (float) time); + SV_DemoWriteToDisk (0, 0, (float) time); // now goes the rest + if (msg.cursize) + SV_WriteDemoMessage (&msg, dem_all, 0, (float) time); + } + + if (demo.lastwritten > demo.parsecount) + demo.lastwritten = demo.parsecount; + + demo.dbuf = &demo.frames[demo.parsecount & DEMO_FRAMES_MASK].buf; + demo.dbuf->maxsize = MAXSIZE + demo.dbuf->bufsize; +} + +int +memwrite (QFile * _mem, const void *buffer, int size) +{ + int i; + byte **mem = (byte **) _mem; + const byte *buf; + + for (i = size, buf = buffer; i; i--) + *(*mem)++ = *buf++; + + return size; +} + +static char chartbl[256]; +void CleanName_Init (); + +void +Demo_Init (void) +{ + int p, size = MIN_DEMO_MEMORY; + + p = COM_CheckParm ("-democache"); + if (p) { + if (p < com_argc - 1) + size = atoi (com_argv[p + 1]) * 1024; + else + Sys_Error ("Memory_Init: you must specify a size in KB after " + "-democache"); + } + + if (size < MIN_DEMO_MEMORY) { + Con_Printf ("Minimum memory size for demo cache is %dk\n", + MIN_DEMO_MEMORY / 1024); + size = MIN_DEMO_MEMORY; + } + + demo.name = dstring_newstr (); + demo.path = dstring_newstr (); + + svs.demomem = Hunk_AllocName (size, "demo"); + svs.demomemsize = size; + demo_max_size = size - 0x80000; + CleanName_Init (); + + serverdemo = Cvar_Get ("serverdemo", "", CVAR_SERVERINFO, Cvar_Info, ""); + sv_demofps = Cvar_Get ("sv_demofps", "20", CVAR_NONE, 0, ""); + sv_demoPings = Cvar_Get ("sv_demoPings", "3", CVAR_NONE, 0, ""); + sv_demoNoVis = Cvar_Get ("sv_demoNoVis", "1", CVAR_NONE, 0, ""); + sv_demoUseCache = Cvar_Get ("sv_demoUseCache", "0", CVAR_NONE, 0, ""); + sv_demoCacheSize = Cvar_Get ("sv_demoCacheSize", va ("%d", size / 1024), + CVAR_ROM, 0, ""); + sv_demoMaxSize = Cvar_Get ("sv_demoMaxSize", "20480", CVAR_NONE, 0, ""); + sv_demoMaxDirSize = Cvar_Get ("sv_demoMaxDirSize", "102400", CVAR_NONE, 0, + ""); + sv_demoDir = Cvar_Get ("sv_demoDir", "demos", CVAR_NONE, 0, ""); + sv_demoPrefix = Cvar_Get ("sv_demoPrefix", "", CVAR_NONE, 0, ""); + sv_demoSuffix = Cvar_Get ("sv_demoSuffix", "", CVAR_NONE, 0, ""); + sv_onrecordfinish = Cvar_Get ("sv_onrecordfinish", "", CVAR_NONE, 0, ""); + sv_ondemoremove = Cvar_Get ("sv_ondemoremove", "", CVAR_NONE, 0, ""); + sv_demotxt = Cvar_Get ("sv_demotxt", "1", CVAR_NONE, 0, ""); +} + +qboolean +SV_InitRecord (void) +{ + if (!USACACHE) { + dwrite = &Qwrite; + demo.dest = demo.file; + demo.disk = true; + } else { + dwrite = &memwrite; + demo.mfile = svs.demomem; + demo.dest = &demo.mfile; + } + + demo_size = 0; + + return true; +} + +/* + SV_Stop + + stop recording a demo +*/ +void +SV_Stop (int reason) +{ + if (!sv.demorecording) { + Con_Printf ("Not recording a demo.\n"); + return; + } + + if (reason == 2) { + char path[MAX_OSPATH]; + + // stop and remove + if (demo.disk) + Qclose (demo.file); + + sprintf (path, "%s/%s/%s", com_gamedir, demo.path->str, demo.name->str); + unlink (path); + + strcpy (path + strlen (path) - 3, "txt"); + unlink (path); + + demo.file = NULL; + sv.demorecording = false; + + SV_BroadcastPrintf (PRINT_CHAT, + "Server recording canceled, demo removed\n"); + + Cvar_Set (serverdemo, ""); + + return; + } + // write a disconnect message to the demo file + + // clearup to be sure message will fit + demo.dbuf->cursize = 0; + demo.dbuf->h = NULL; + demo.dbuf->bufsize = 0; + DemoWrite_Begin (dem_all, 0, 2 + strlen ("EndOfDemo")); + MSG_WriteByte ((sizebuf_t *) demo.dbuf, svc_disconnect); + MSG_WriteString ((sizebuf_t *) demo.dbuf, "EndOfDemo"); + + SV_DemoWritePackets (demo.parsecount - demo.lastwritten + 1); + // finish up + if (!demo.disk) { + Qwrite (demo.file, svs.demomem, demo.size - demo_size); + Qflush (demo.file); + } + + Qclose (demo.file); + + demo.file = NULL; + sv.demorecording = false; + if (!reason) + SV_BroadcastPrintf (PRINT_CHAT, "Server recording completed\n"); + else + SV_BroadcastPrintf (PRINT_CHAT, + "Server recording stoped\nMax demo size exceeded\n"); +/* + if (sv_onrecordfinish->string[0]) { + extern redirect_t sv_redirected; + int old = sv_redirected; + char path[MAX_OSPATH]; + char *p; + + if ((p = strstr (sv_onrecordfinish->string, " ")) != NULL) + *p = 0; // strip parameters + + strcpy (path, demo.name->str); + strcpy (path + strlen (demo.name->str) - 3, "txt"); + + sv_redirected = RD_NONE; // onrecord script is called always + // from the console + Cmd_TokenizeString (va ("script %s \"%s\" \"%s\" \"%s\" %s", + sv_onrecordfinish->string, demo.path->str, + serverdemo->string, + path, p != NULL ? p + 1 : "")); + if (p) + *p = ' '; + SV_Script_f (); + + sv_redirected = old; + } +*/ + Cvar_Set (serverdemo, ""); +} + +void +SV_Stop_f (void) +{ + SV_Stop (0); +} + +/* + SV_Cancel_f + + Stops recording, and removes the demo +*/ +void +SV_Cancel_f (void) +{ + SV_Stop (2); +} + +/* + SV_WriteDemoMessage + + Dumps the current net message, prefixed by the length and view angles +*/ + +void +SV_WriteRecordDemoMessage (sizebuf_t *msg, int seq) +{ + int len; + byte c; + + if (!sv.demorecording) + return; + + c = 0; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + + c = dem_read; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + + len = LittleLong (msg->cursize); + demo.size += DWRITE (&len, 4, demo.dest); + + demo.size += DWRITE (msg->data, msg->cursize, demo.dest); + + if (demo.disk) + Qflush (demo.file); +} + +void +SV_WriteSetDemoMessage (void) +{ + int len; + byte c; + +// Con_Printf("write: %ld bytes, %4.4f\n", msg->cursize, realtime); + + if (!sv.demorecording) + return; + + c = 0; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + + c = dem_set; + demo.size += DWRITE (&c, sizeof (c), demo.dest); + + + len = LittleLong (0); + demo.size += DWRITE (&len, 4, demo.dest); + len = LittleLong (0); + demo.size += DWRITE (&len, 4, demo.dest); + + if (demo.disk) + Qflush (demo.file); +} + +static char * +SV_PrintTeams (void) +{ + const char *teams[MAX_CLIENTS]; + char *p; + int i, j, numcl = 0, numt = 0; + client_t *clients[MAX_CLIENTS]; + char buf[2048] = { 0 }; + extern char chartbl2[]; + + // count teams and players + for (i = 0; i < MAX_CLIENTS; i++) { + if (svs.clients[i].state != cs_spawned) + continue; + if (svs.clients[i].spectator) + continue; + + clients[numcl++] = &svs.clients[i]; + for (j = 0; j < numt; j++) + if (!strcmp (svs.clients[i].team, teams[j])) + break; + if (j != numt) + continue; + + teams[numt++] = svs.clients[i].team; + } + + // create output + + if (numcl == 2) // duel + { + sprintf (buf, "team1 %s\nteam2 %s\n", clients[0]->name, + clients[1]->name); + } else if (!teamplay->int_val) // ffa + { + sprintf (buf, "players:\n"); + for (i = 0; i < numcl; i++) + sprintf (buf + strlen (buf), " %s\n", clients[i]->name); + } else { // teamplay + for (j = 0; j < numt; j++) { + sprintf (buf + strlen (buf), "team %s:\n", teams[j]); + for (i = 0; i < numcl; i++) + if (!strcmp (clients[i]->team, teams[j])) + sprintf (buf + strlen (buf), " %s\n", clients[i]->name); + } + } + + if (!numcl) + return "\n"; + for (p = buf; *p; p++) + *p = chartbl2[(byte) * p]; + return va ("%s", buf); +} + +static int +make_info_string_filter (const char *key) +{ + return *key == '_' || !Info_FilterForKey (key, client_info_filters); +} + +static void +SV_Record (char *name) +{ + sizebuf_t buf; + char buf_data[MAX_MSGLEN]; + int n, i; + char path[MAX_OSPATH]; + char *info; + + client_t *player; + const char *gamedir, *s; + int seq = 1; + + memset (&demo, 0, sizeof (demo)); + /*XXX + for (i = 0; i < UPDATE_BACKUP; i++) + demo.recorder.frames[i].entities.entities = demo_entities[i]; + */ + + DemoBuffer_Init (&demo.dbuffer, demo.buffer, sizeof (demo.buffer)); + DemoSetMsgBuf (NULL, &demo.frames[0].buf); + + demo.datagram.maxsize = sizeof (demo.datagram_data); + demo.datagram.data = demo.datagram_data; + + demo.file = Qopen (name, "wb"); + if (!demo.file) { + Con_Printf ("ERROR: couldn't open %s\n", name); + return; + } + + SV_InitRecord (); + + s = name + strlen (name); + while (*s != '/') + s--; + dstring_clearstr (demo.name); + dstring_clearstr (demo.path); + dstring_appendstr (demo.name, s + 1); + dstring_appendstr (demo.path, sv_demoDir->string); + + if (demo.path->size < 2) + dstring_appendstr (demo.path, "."); + + SV_BroadcastPrintf (PRINT_CHAT, "Server starts recording (%s):\n%s\n", + demo.disk ? "disk" : "memory", demo.name->str); + Cvar_Set (serverdemo, demo.name->str); + + strcpy (path, name); + strcpy (path + strlen (path) - 3, "txt"); + + if (sv_demotxt->int_val) { + QFile *f; + + f = Qopen (path, "w+t"); + if (f != NULL) { + char buf[2000]; + char date[20]; + time_t tim; + + time (&tim); + strftime (date, 19, "%Y-%m-%d-%H-%M", localtime (&tim)); + + sprintf (buf, "date %s\nmap %s\nteamplay %d\ndeathmatch %d\n" + "timelimit %d\n%s", + date, sv.name, teamplay->int_val, + deathmatch->int_val, timelimit->int_val, SV_PrintTeams ()); + Qwrite (f, buf, strlen (buf)); + Qflush (f); + Qclose (f); + } + } else + unlink (path); + + sv.demorecording = true; + demo.pingtime = demo.time = sv.time; + +/*-------------------------------------------------*/ + + // serverdata + // send the info about the new client to all connected clients + memset (&buf, 0, sizeof (buf)); + buf.data = buf_data; + buf.maxsize = sizeof (buf_data); + + // send the serverdata + + gamedir = Info_ValueForKey (svs.info, "*gamedir"); + if (!gamedir[0]) + gamedir = "qw"; + + MSG_WriteByte (&buf, svc_serverdata); + MSG_WriteLong (&buf, PROTOCOL_VERSION); + MSG_WriteLong (&buf, svs.spawncount); + MSG_WriteString (&buf, gamedir); + + + MSG_WriteFloat (&buf, sv.time); + + // send full levelname + MSG_WriteString (&buf, PR_GetString (&sv_pr_state, + SVstring (sv.edicts, message))); + + // send the movevars + MSG_WriteFloat (&buf, movevars.gravity); + MSG_WriteFloat (&buf, movevars.stopspeed); + MSG_WriteFloat (&buf, movevars.maxspeed); + MSG_WriteFloat (&buf, movevars.spectatormaxspeed); + MSG_WriteFloat (&buf, movevars.accelerate); + MSG_WriteFloat (&buf, movevars.airaccelerate); + MSG_WriteFloat (&buf, movevars.wateraccelerate); + MSG_WriteFloat (&buf, movevars.friction); + MSG_WriteFloat (&buf, movevars.waterfriction); + MSG_WriteFloat (&buf, movevars.entgravity); + + // send music + MSG_WriteByte (&buf, svc_cdtrack); + MSG_WriteByte (&buf, 0); // none in demos + + // send server info string + MSG_WriteByte (&buf, svc_stufftext); + MSG_WriteString (&buf, va ("fullserverinfo \"%s\"\n", + Info_MakeString (svs.info, 0))); + + // flush packet + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + + // soundlist + MSG_WriteByte (&buf, svc_soundlist); + MSG_WriteByte (&buf, 0); + + n = 0; + s = sv.sound_precache[n + 1]; + while (s) { + MSG_WriteString (&buf, s); + if (buf.cursize > MAX_MSGLEN / 2) { + MSG_WriteByte (&buf, 0); + MSG_WriteByte (&buf, n); + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + MSG_WriteByte (&buf, svc_soundlist); + MSG_WriteByte (&buf, n + 1); + } + n++; + s = sv.sound_precache[n + 1]; + } + + if (buf.cursize) { + MSG_WriteByte (&buf, 0); + MSG_WriteByte (&buf, 0); + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + // modellist + MSG_WriteByte (&buf, svc_modellist); + MSG_WriteByte (&buf, 0); + + n = 0; + s = sv.model_precache[n + 1]; + while (s) { + MSG_WriteString (&buf, s); + if (buf.cursize > MAX_MSGLEN / 2) { + MSG_WriteByte (&buf, 0); + MSG_WriteByte (&buf, n); + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + MSG_WriteByte (&buf, svc_modellist); + MSG_WriteByte (&buf, n + 1); + } + n++; + s = sv.model_precache[n + 1]; + } + if (buf.cursize) { + MSG_WriteByte (&buf, 0); + MSG_WriteByte (&buf, 0); + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + // prespawn + + for (n = 0; n < sv.num_signon_buffers; n++) { + SZ_Write (&buf, sv.signon_buffers[n], sv.signon_buffer_size[n]); + + if (buf.cursize > MAX_MSGLEN / 2) { + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + } + + MSG_WriteByte (&buf, svc_stufftext); + MSG_WriteString (&buf, va ("cmd spawn %i 0\n", svs.spawncount)); + + if (buf.cursize) { + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + // send current status of all other players + + for (i = 0; i < MAX_CLIENTS; i++) { + player = svs.clients + i; + + MSG_WriteByte (&buf, svc_updatefrags); + MSG_WriteByte (&buf, i); + MSG_WriteShort (&buf, player->old_frags); + + MSG_WriteByte (&buf, svc_updateping); + MSG_WriteByte (&buf, i); + MSG_WriteShort (&buf, SV_CalcPing (player)); + + MSG_WriteByte (&buf, svc_updatepl); + MSG_WriteByte (&buf, i); + MSG_WriteByte (&buf, player->lossage); + + MSG_WriteByte (&buf, svc_updateentertime); + MSG_WriteByte (&buf, i); + MSG_WriteFloat (&buf, realtime - player->connection_started); + + info = player->userinfo ? Info_MakeString (player->userinfo, + make_info_string_filter) + : ""; + + MSG_WriteByte (&buf, svc_updateuserinfo); + MSG_WriteByte (&buf, i); + MSG_WriteLong (&buf, player->userid); + MSG_WriteString (&buf, info); + + if (buf.cursize > MAX_MSGLEN / 2) { + SV_WriteRecordDemoMessage (&buf, seq++); + SZ_Clear (&buf); + } + } + + // send all current light styles + for (i = 0; i < MAX_LIGHTSTYLES; i++) { + MSG_WriteByte (&buf, svc_lightstyle); + MSG_WriteByte (&buf, (char) i); + MSG_WriteString (&buf, sv.lightstyles[i]); + } + + // get the client to check and download skins + // when that is completed, a begin command will be issued + MSG_WriteByte (&buf, svc_stufftext); + MSG_WriteString (&buf, va ("skins\n")); + + SV_WriteRecordDemoMessage (&buf, seq++); + + SV_WriteSetDemoMessage (); + // done +} + +/* + SV_CleanName_Init + + sets chararcter table for quake text->filename translation +*/ + +void +CleanName_Init () +{ + int i; + + for (i = 0; i < 256; i++) + chartbl[i] = (((i & 127) < 'a' || (i & 127) > 'z') + && ((i & 127) < '0' + || (i & 127) > '9')) ? '_' : (i & 127); + + // special cases + + // numbers + for (i = 18; i < 29; i++) + chartbl[i] = chartbl[i + 128] = i + 30; + + // allow lowercase only + for (i = 'A'; i <= 'Z'; i++) + chartbl[i] = chartbl[i + 128] = i + 'a' - 'A'; + + // brackets + chartbl[29] = chartbl[29 + 128] = chartbl[128] = '('; + chartbl[31] = chartbl[31 + 128] = chartbl[130] = ')'; + chartbl[16] = chartbl[16 + 128] = '['; + chartbl[17] = chartbl[17 + 128] = ']'; + + // dot + chartbl[5] = chartbl[14] = chartbl[15] = chartbl[28] = chartbl[46] = '.'; + chartbl[5 + 128] = chartbl[14 + 128] = chartbl[15 + 128] = + chartbl[28 + 128] = chartbl[46 + 128] = '.'; + + // ! + chartbl[33] = chartbl[33 + 128] = '!'; + + // # + chartbl[35] = chartbl[35 + 128] = '#'; + + // % + chartbl[37] = chartbl[37 + 128] = '%'; + + // & + chartbl[38] = chartbl[38 + 128] = '&'; + + // ' + chartbl[39] = chartbl[39 + 128] = '\''; + + // ( + chartbl[40] = chartbl[40 + 128] = '('; + + // ) + chartbl[41] = chartbl[41 + 128] = ')'; + + // + + chartbl[43] = chartbl[43 + 128] = '+'; + + // - + chartbl[45] = chartbl[45 + 128] = '-'; + + // @ + chartbl[64] = chartbl[64 + 128] = '@'; + + // ^ + chartbl[94] = chartbl[94 + 128] = '^'; + + + chartbl[91] = chartbl[91 + 128] = '['; + chartbl[93] = chartbl[93 + 128] = ']'; + + chartbl[16] = chartbl[16 + 128] = '['; + chartbl[17] = chartbl[17 + 128] = ']'; + + chartbl[123] = chartbl[123 + 128] = '{'; + chartbl[125] = chartbl[125 + 128] = '}'; +} + +/* + SV_CleanName + + Cleans the demo name, removes restricted chars, makes name lowercase +*/ + +char * +SV_CleanName (const unsigned char *name) +{ + static char text[1024]; + char *out = text; + + *out = chartbl[*name++]; + + while (*name) + if (*out == '_' && chartbl[*name] == '_') + name++; + else + *++out = chartbl[*name++]; + + *++out = 0; + return text; +} + +#define MAX_DEMO_NAME 64 // FIXME + +/* + SV_Record_f + + record +*/ +void +SV_Record_f (void) +{ + dstring_t *name = dstring_newstr (); + + if (Cmd_Argc () != 2) { + Con_Printf ("record \n"); + return; + } + + if (sv.state != ss_active) { + Con_Printf ("Not active yet.\n"); + return; + } + + if (sv.demorecording) + SV_Stop_f (); + + dsprintf (name, "%s/%s/%s%s%s", com_gamedir, sv_demoDir->string, + sv_demoPrefix->string, SV_CleanName (Cmd_Argv (1)), + sv_demoSuffix->string); + + Sys_mkdir (va ("%s/%s", com_gamedir, sv_demoDir->string)); + + // open the demo file + name->size += 4; + dstring_adjust (name); + COM_DefaultExtension (name->str, ".mvd"); + + SV_Record (name->str); + + dstring_delete (name); +} + +/* + SV_EasyRecord_f + + easyrecord [demoname] +*/ + +int +Dem_CountPlayers () +{ + int i, count; + + count = 0; + for (i = 0; i < MAX_CLIENTS; i++) { + if (svs.clients[i].name[0] && !svs.clients[i].spectator) + count++; + } + + return count; +} + +const char * +Dem_Team (int num) +{ + int i; + static const char *lastteam[2]; + qboolean first = true; + client_t *client; + static int index = 0; + + index = 1 - index; + + for (i = 0, client = svs.clients; num && i < MAX_CLIENTS; i++, client++) { + if (!client->name[0] || client->spectator) + continue; + + if (first || strcmp (lastteam[index], client->team)) { + first = false; + num--; + lastteam[index] = client->team; + } + } + + if (num) + return ""; + + return lastteam[index]; +} + +char * +Dem_PlayerName (int num) +{ + int i; + client_t *client; + + for (i = 0, client = svs.clients; i < MAX_CLIENTS; i++, client++) { + if (!client->name[0] || client->spectator) + continue; + + if (!--num) + return client->name; + } + + return ""; +} + +void +SV_EasyRecord_f (void) +{ + dstring_t *name = dstring_newstr (); + dstring_t *name2 = dstring_newstr (); + int i; + QFile *f; + + if (Cmd_Argc () > 2) { + Con_Printf ("easyrecord [demoname]\n"); + return; + } + + if (sv.demorecording) + SV_Stop_f (); + + if (Cmd_Argc () == 2) + dsprintf (name, "%s", Cmd_Argv (1)); + else { + // guess game type and write demo name + i = Dem_CountPlayers (); + if (teamplay->int_val && i > 2) { + // Teamplay + dsprintf (name, "team_%s_vs_%s_%s", + Dem_Team (1), Dem_Team (2), sv.name); + } else { + if (i == 2) { + // Duel + dsprintf (name, "duel_%s_vs_%s_%s", + Dem_PlayerName (1), Dem_PlayerName (2), sv.name); + } else { + // FFA + dsprintf (name, "ffa_%s(%d)", sv.name, i); + } + } + } + + // Make sure the filename doesn't contain illegal characters + dsprintf (name2, "%s/%s/%s%s%s", com_gamedir, sv_demoDir->string, + sv_demoPrefix->string, SV_CleanName (name->str), + sv_demoSuffix->string); + Sys_mkdir (va ("%s/%s", com_gamedir, sv_demoDir->string)); + + // find a filename that doesn't exist yet + dsprintf (name, "%s", name2->str); + name->size += 4; + dstring_adjust (name); + COM_DefaultExtension (name->str, ".mvd"); + if ((f = Qopen (name->str, "rb")) == 0) + f = Qopen (va ("%s.gz", name->str), "rb"); + + if (f) { + i = 1; + do { + Qclose (f); + dsprintf (name, "%s_%02i", name2->str, i); + name->size += 4; + dstring_adjust (name); + COM_DefaultExtension (name->str, ".mvd"); + if ((f = Qopen (name->str, "rb")) == 0) + f = Qopen (va ("%s.gz", name->str), "rb"); + i++; + } while (f); + } + + + SV_Record (name->str); + + dstring_delete (name); + dstring_delete (name2); +} + +void +SV_DemoList_f (void) +{ +/* + float f; + int i, j, show; + dir_t dir; + file_t *list; + + Con_Printf ("content of %s/%s/ *.mvd\n", com_gamedir, sv_demoDir->string); + dir = Sys_listdir (va ("%s/%s", com_gamedir, sv_demoDir->string), ".mvd"); + list = dir.files; + if (!list->name[0]) { + Con_Printf ("no demos\n"); + } + + for (i = 1; list->name[0]; i++, list++) { + for (j = 1; j < Cmd_Argc (); j++) + if (strstr (list->name, Cmd_Argv (j)) == NULL) + break; + show = Cmd_Argc () == j; + + if (show) { + if (sv.demorecording && !strcmp (list->name, demo.name->str)) + Con_Printf ("*%d: %s %dk\n", i, list->name, demo.size / 1024); + else + Con_Printf ("%d: %s %dk\n", i, list->name, list->size / 1024); + } + } + + if (sv.demorecording) + dir.size += demo.size; + + Con_Printf ("\ndirectory size: %.1fMB\n", (float) dir.size / (1024 * 1024)); + if (sv_demoMaxDirSize->int_val) { + f = (sv_demoMaxDirSize->int_val * 1024 - dir.size) / (1024 * 1024); + if (f < 0) + f = 0; + Con_Printf ("space available: %.1fMB\n", f); + } +*/ +} + +char * +SV_DemoNum (int num) +{ +/* + file_t *list; + dir_t dir; + + dir = Sys_listdir (va ("%s/%s", com_gamedir, sv_demoDir->string), ".mvd"); + list = dir.files; + + if (num <= 0) + return NULL; + + num--; + + while (list->name[0] && num) { + list++; + num--; + }; + + if (list->name[0]) + return list->name; +*/ + return NULL; +} + +char * +SV_DemoName2Txt (char *name) +{ + char s[MAX_OSPATH]; + + if (!name) + return NULL; + + strcpy (s, name); + + if (strstr (s, ".mvd.gz") != NULL) + strcpy (s + strlen (s) - 6, "txt"); + else + strcpy (s + strlen (s) - 3, "txt"); + + return va ("%s", s); +} + +char * +SV_DemoTxTNum (int num) +{ + return SV_DemoName2Txt (SV_DemoNum (num)); +} + +void +SV_DemoRemove_f (void) +{ + dstring_t *name = dstring_newstr (); + const char *ptr; + char path[MAX_OSPATH]; + + if (Cmd_Argc () != 2) { + Con_Printf ("rmdemo - removes the demo\n" + "rmdemo * - removes demo with in " + "the name\n" + "rmdemo * - removes all demos\n"); + return; + } + + ptr = Cmd_Argv (1); +/* + if (*ptr == '*') { + dir_t dir; + file_t *list; + int i; + + // remove all demos with specified token + ptr++; + + dir = + Sys_listdir (va ("%s/%s", com_gamedir, sv_demoDir->string), ".mvd"); + list = dir.files; + for (i = 0; list->name[0]; list++) { + if (strstr (list->name, ptr)) { + if (sv.demorecording && !strcmp (list->name, demo.name->str)) + SV_Stop_f (); + + // stop recording first; + sprintf (path, "%s/%s/%s", com_gamedir, sv_demoDir->string, + list->name); + if (!unlink (path)) { + Con_Printf ("removing %s...\n", list->name); + i++; + } + + unlink (SV_DemoName2Txt (path)); + } + } + + if (i) { + Con_Printf ("%d demos removed\n", i); + } else { + Con_Printf ("no matching found\n"); + } + + return; + } +*/ + dsprintf (name, "%s", Cmd_Argv (1)); + COM_DefaultExtension (name->str, ".mvd"); + + sprintf (path, "%s/%s/%s", com_gamedir, sv_demoDir->string, name->str); + + if (sv.demorecording && !strcmp (name->str, demo.name->str)) + SV_Stop_f (); + + if (!unlink (path)) { + Con_Printf ("demo %s succesfully removed\n", name->str); +/* + if (*sv_ondemoremove->string) { + extern redirect_t sv_redirected; + int old = sv_redirected; + // this script is called always from the console + sv_redirected = RD_NONE; + + Cmd_TokenizeString (va ("script %s \"%s\" \"%s\"", + sv_ondemoremove->string, + sv_demoDir->string, name->str)); + SV_Script_f (); + + sv_redirected = old; + } +*/ + } else + Con_Printf ("unable to remove demo %s\n", name->str); + + unlink (SV_DemoName2Txt (path)); +} + +void +SV_DemoRemoveNum_f (void) +{ + int num; + const char *val, *name; + char path[MAX_OSPATH]; + + if (Cmd_Argc () != 2) { + Con_Printf ("rmdemonum <#>\n"); + return; + } + + val = Cmd_Argv (1); + if ((num = atoi (val)) == 0 && val[0] != '0') { + Con_Printf ("rmdemonum <#>\n"); + return; + } + + name = SV_DemoNum (num); + + if (name != NULL) { + if (sv.demorecording && !strcmp (name, demo.name->str)) + SV_Stop_f (); + + sprintf (path, "%s/%s/%s", com_gamedir, sv_demoDir->string, name); + if (!unlink (path)) { + Con_Printf ("demo %s succesfully removed\n", name); +/* + if (*sv_ondemoremove->string) { + extern redirect_t sv_redirected; + int old = sv_redirected; + // this script is called always from the console + sv_redirected = RD_NONE; + + Cmd_TokenizeString (va ("script %s \"%s\" \"%s\"", + sv_ondemoremove->string, + sv_demoDir->string, name)); + SV_Script_f (); + + sv_redirected = old; + } +*/ + } else + Con_Printf ("unable to remove demo %s\n", name); + + unlink (SV_DemoName2Txt (path)); + } else + Con_Printf ("invalid demo num\n"); +} + +void +SV_DemoInfoAdd_f (void) +{ + const char *name = 0, *args; + char path[MAX_OSPATH]; + QFile *f; + + if (Cmd_Argc () < 3) { + Con_Printf ("usage:demoInfoAdd \n" + " = * for currently recorded demo\n"); + return; + } + + if (!strcmp (Cmd_Argv (1), "*")) { + if (!sv.demorecording) { + Con_Printf ("Not recording demo!\n"); + return; + } + + sprintf (path, "%s/%s/%s", com_gamedir, demo.path->str, + SV_DemoName2Txt (demo.name->str)); + } else { + name = SV_DemoTxTNum (atoi (Cmd_Argv (1))); + + if (!name) { + Con_Printf ("invalid demo num\n"); + return; + } + + sprintf (path, "%s/%s/%s", com_gamedir, sv_demoDir->string, name); + } + + if ((f = Qopen (path, "a+t")) == NULL) { + Con_Printf ("faild to open the file\n"); + return; + } + // skip demonum + args = Cmd_Args (1); + while (*args > 32) + args++; + while (*args && *args <= 32) + args++; + + Qwrite (f, args, strlen (args)); + Qwrite (f, "\n", 1); + Qflush (f); + Qclose (f); +} + +void +SV_DemoInfoRemove_f (void) +{ + char *name = 0, path[MAX_OSPATH]; + + if (Cmd_Argc () < 2) { + Con_Printf ("usage:demoInfoRemove \n" + " = * for currently recorded demo\n"); + return; + } + + if (!strcmp (Cmd_Argv (1), "*")) { + if (!sv.demorecording) { + Con_Printf ("Not recording demo!\n"); + return; + } + + sprintf (path, "%s/%s/%s", com_gamedir, demo.path->str, + SV_DemoName2Txt (demo.name->str)); + } else { + name = SV_DemoTxTNum (atoi (Cmd_Argv (1))); + + if (!name) { + Con_Printf ("invalid demo num\n"); + return; + } + + sprintf (path, "%s/%s/%s", com_gamedir, sv_demoDir->string, name); + } + + if (unlink (path)) + Con_Printf ("failed to remove the file\n"); + else + Con_Printf ("file removed\n"); +} + +void +SV_DemoInfo_f (void) +{ + const char *buf; + QFile *f = NULL; + char *name = 0, path[MAX_OSPATH]; + + if (Cmd_Argc () < 2) { + Con_Printf ("usage:demoinfo \n" + " = * for currently recorded demo\n"); + return; + } + + if (!strcmp (Cmd_Argv (1), "*")) { + if (!sv.demorecording) { + Con_Printf ("Not recording demo!\n"); + return; + } + + sprintf (path, "%s/%s/%s", com_gamedir, demo.path->str, + SV_DemoName2Txt (demo.name->str)); + } else { + name = SV_DemoTxTNum (atoi (Cmd_Argv (1))); + + if (!name) { + Con_Printf ("invalid demo num\n"); + return; + } + + sprintf (path, "%s/%s/%s", com_gamedir, sv_demoDir->string, name); + } + + if ((f = Qopen (path, "rt")) == NULL) { + Con_Printf ("(empty)\n"); + return; + } + + while ((buf = Qgetline (f))) { + Con_Printf ("%s", buf); + } + + Qclose (f); +}