/* 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; #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) static int demo_max_size; static int demo_size; 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; cvar_t *sv_demoPrefix; cvar_t *sv_demoSuffix; cvar_t *sv_onrecordfinish; cvar_t *sv_ondemoremove; cvar_t *sv_demotxt; cvar_t *serverdemo; 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->sz.maxsize = prev->bufsize; demo.dbuf = cur; memset (demo.dbuf, 0, sizeof (*demo.dbuf)); demo.dbuf->sz.data = demobuffer->data + demobuffer->end; demo.dbuf->sz.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->sz.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->sz.data != (byte *) p) memmove (demo.dbuf->sz.data + size + header, demo.dbuf->sz.data, (byte *) p - demo.dbuf->sz.data); demo.dbuf->bufsize -= size + header; demo.dbuf->sz.data += size + header; pos -= size + header; demo.dbuf->sz.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->sz.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->sz.data; while (pos < demo.dbuf->bufsize) { pos += header + p->size; if (type == p->type && to == p->to && !p->full) { demo.dbuf->sz.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->sz.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->sz.data, demo.dbuf->bufsize); demo.dbuf->sz.data = demobuffer->data; demobuffer->end = demo.dbuf->bufsize; demo.dbuf->h = NULL; // it will be setup again demo.dbuf->sz.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->sz.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->sz.cursize != demo.dbuf->bufsize) { p = demo.dbuf->sz.data + demo.dbuf->sz.cursize; memmove (p + size, p, demo.dbuf->bufsize - demo.dbuf->sz.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->sz.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; } 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->sz.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) { char teams[MAX_CLIENTS][128]; char *p; int i, j, numcl = 0, numt = 0; client_t *clients[MAX_CLIENTS]; char buf[2048] = { 0 }; const char *team; // 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; team = Info_ValueForKey (svs.clients[i].userinfo, "team"); clients[numcl++] = &svs.clients[i]; for (j = 0; j < numt; j++) if (!strcmp (team, teams[j])) break; if (j != numt) continue; strcpy (teams[numt++], 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++) { team = Info_ValueForKey (svs.clients[i].userinfo, "team"); if (!strcmp (team, teams[j])) sprintf (buf + strlen (buf), " %s\n", clients[i]->name); } } } if (!numcl) return "\n"; for (p = buf; *p; p++) *p = sys_char_map[(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; dstring_t *tn = demo.name, *tp = demo.path; client_t *player; const char *gamedir, *s; int seq = 1; memset (&demo, 0, sizeof (demo)); for (i = 0; i < UPDATE_BACKUP; i++) demo.recorder.frames[i].entities.entities = demo_entities[i]; dstring_clearstr (demo.name = tn); dstring_clearstr (demo.path = tp); 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 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 = sys_char_map[*name++]; while (*name) if (*out == '_' && sys_char_map[*name] == '_') name++; else *++out = sys_char_map[*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; const char *team; index = 1 - index; for (i = 0, client = svs.clients; num && i < MAX_CLIENTS; i++, client++) { if (!client->name[0] || client->spectator) continue; team = Info_ValueForKey (svs.clients[i].userinfo, "team"); if (first || strcmp (lastteam[index], team)) { first = false; num--; lastteam[index] = 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); } 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; serverdemo = Cvar_Get ("serverdemo", "", CVAR_SERVERINFO, Cvar_Info, "FIXME"); sv_demofps = Cvar_Get ("sv_demofps", "20", CVAR_NONE, 0, "FIXME"); sv_demoPings = Cvar_Get ("sv_demoPings", "3", CVAR_NONE, 0, "FIXME"); sv_demoNoVis = Cvar_Get ("sv_demoNoVis", "1", CVAR_NONE, 0, "FIXME"); sv_demoUseCache = Cvar_Get ("sv_demoUseCache", "0", CVAR_NONE, 0, "FIXME"); sv_demoCacheSize = Cvar_Get ("sv_demoCacheSize", va ("%d", size / 1024), CVAR_ROM, 0, "FIXME"); sv_demoMaxSize = Cvar_Get ("sv_demoMaxSize", "20480", CVAR_NONE, 0, "FIXME"); sv_demoMaxDirSize = Cvar_Get ("sv_demoMaxDirSize", "102400", CVAR_NONE, 0, "FIXME"); sv_demoDir = Cvar_Get ("sv_demoDir", "demos", CVAR_NONE, 0, "FIXME"); sv_demoPrefix = Cvar_Get ("sv_demoPrefix", "", CVAR_NONE, 0, "FIXME"); sv_demoSuffix = Cvar_Get ("sv_demoSuffix", "", CVAR_NONE, 0, "FIXME"); sv_onrecordfinish = Cvar_Get ("sv_onrecordfinish", "", CVAR_NONE, 0, "FIXME"); sv_ondemoremove = Cvar_Get ("sv_ondemoremove", "", CVAR_NONE, 0, "FIXME"); sv_demotxt = Cvar_Get ("sv_demotxt", "1", CVAR_NONE, 0, "FIXME"); Cmd_AddCommand ("record", SV_Record_f, "FIXME"); Cmd_AddCommand ("easyrecord", SV_EasyRecord_f, "FIXME"); Cmd_AddCommand ("stop", SV_Stop_f, "FIXME"); Cmd_AddCommand ("cancel", SV_Cancel_f, "FIXME"); Cmd_AddCommand ("demolist", SV_DemoList_f, "FIXME"); Cmd_AddCommand ("rmdemo", SV_DemoRemove_f, "FIXME"); Cmd_AddCommand ("rmdemonum", SV_DemoRemoveNum_f, "FIXME"); Cmd_AddCommand ("demoInfoAdd", SV_DemoInfoAdd_f, "FIXME"); Cmd_AddCommand ("demoInfoRemove", SV_DemoInfoRemove_f, "FIXME"); Cmd_AddCommand ("demoInfo", SV_DemoInfo_f, "FIXME"); }