From a1072a5ac30e4d2827b79c4a3469992e45ad389f Mon Sep 17 00:00:00 2001 From: divverent Date: Mon, 11 Aug 2008 18:12:51 +0000 Subject: [PATCH] demo recording: stuff csprogs.dat files into .dem files so demos can always be played back git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@8443 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_demo.c | 57 ++++++++++++++++++++++++++++++++++++++++++++- cl_parse.c | 2 ++ client.h | 5 +++- csprogs.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++-- csprogs.h | 2 ++ sv_main.c | 68 ++++++++++++++++++++++++++++++++++++------------------ 6 files changed, 174 insertions(+), 26 deletions(-) diff --git a/cl_demo.c b/cl_demo.c index 580125b2..27959bf6 100644 --- a/cl_demo.c +++ b/cl_demo.c @@ -119,6 +119,57 @@ void CL_WriteDemoMessage (sizebuf_t *message) FS_Write (cls.demofile, message->data, message->cursize); } +/* +==================== +CL_CutDemo + +Dumps the current demo to a buffer, and resets the demo to its starting point. +Used to insert csprogs.dat files as a download to the beginning of a demo file. +==================== +*/ +void CL_CutDemo (void **buf, fs_offset_t *filesize) +{ + *buf = NULL; + *filesize = 0; + + FS_Close(cls.demofile); + *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize); + + // restart the demo recording + cls.demofile = FS_Open(cls.demoname, "wb", false, false); + if(!cls.demofile) + Host_Error("failed to reopen the demo file"); + FS_Printf(cls.demofile, "%i\n", cls.forcetrack); +} + +/* +==================== +CL_PasteDemo + +Adds the cut stuff back to the demo. Also frees the buffer. +Used to insert csprogs.dat files as a download to the beginning of a demo file. +==================== +*/ +void CL_PasteDemo (void **buf, fs_offset_t *filesize) +{ + fs_offset_t startoffset = 0; + + if(!*buf) + return; + + // skip cdtrack + while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n') + ++startoffset; + if(startoffset < *filesize) + ++startoffset; + + FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset); + + Mem_Free(*buf); + *buf = NULL; + *filesize = 0; +} + /* ==================== CL_ReadDemoMessage @@ -209,6 +260,9 @@ void CL_ReadDemoMessage(void) MSG_BeginReading(); CL_ParseServerMessage(); + if (cls.signon != SIGNONS) + Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect! + // In case the demo contains a "svc_disconnect" message if (!cls.demoplayback) return; @@ -317,6 +371,7 @@ void CL_Record_f (void) Con_Print("ERROR: couldn't open.\n"); return; } + strlcpy(cls.demoname, name, sizeof(cls.demoname)); cls.forcetrack = track; FS_Printf(cls.demofile, "%i\n", cls.forcetrack); @@ -364,8 +419,8 @@ void CL_PlayDemo_f (void) cls.demonum = -1; // stop demo loop return; } - strlcpy(cls.demoname, name, sizeof(cls.demoname)); + cls.demoplayback = true; cls.state = ca_connected; cls.forcetrack = 0; diff --git a/cl_parse.c b/cl_parse.c index 6b8e372a..d6423d1b 100644 --- a/cl_parse.c +++ b/cl_parse.c @@ -424,6 +424,7 @@ static void CL_SetupWorldModel(void) if (cl.loadcsqc) { cl.loadcsqc = false; + CL_VM_Init(); } } @@ -1644,6 +1645,7 @@ void CL_ParseServerInfo (void) cls.forcetrack = -1; FS_Printf (cls.demofile, "%i\n", cls.forcetrack); cls.demorecording = true; + strlcpy(cls.demoname, demofile, sizeof(cls.demoname)); } else Con_Print ("ERROR: couldn't open.\n"); diff --git a/client.h b/client.h index 41298064..6d032f34 100644 --- a/client.h +++ b/client.h @@ -518,7 +518,7 @@ typedef struct client_static_s // list of demos in loop char demos[MAX_DEMOS][MAX_DEMONAME]; // the actively playing demo (set by CL_PlayDemo_f) - char demoname[64]; + char demoname[MAX_QPATH]; // demo recording info must be here, because record is started before // entering a map (and clearing client_state_t) @@ -1214,6 +1214,9 @@ void CL_StopPlayback(void); void CL_ReadDemoMessage(void); void CL_WriteDemoMessage(sizebuf_t *mesage); +void CL_CutDemo(void **buf, fs_offset_t *filesize); +void CL_PasteDemo(void **buf, fs_offset_t *filesize); + void CL_NextDemo(void); void CL_Stop_f(void); void CL_Record_f(void); diff --git a/csprogs.c b/csprogs.c index 69b9734c..92408e48 100644 --- a/csprogs.c +++ b/csprogs.c @@ -664,6 +664,46 @@ qboolean CL_VM_CB_LoadEdict(prvm_edict_t *ent) void Cmd_ClearCsqcFuncs (void); +// returns true if the packet is valid, false if end of file is reached +// used for dumping the CSQC download into demo files +qboolean MakeDownloadPacket(const char *filename, void *data, unsigned long len, int crc, int cnt, sizebuf_t *buf, int protocol) +{ + int packetsize = buf->maxsize - 7; // byte short long + int npackets = (len + packetsize - 1) / (packetsize); + + if(protocol == PROTOCOL_QUAKEWORLD) + return false; // CSQC can't run in QW anyway + + SZ_Clear(buf); + if(cnt == 0) + { + MSG_WriteByte(buf, svc_stufftext); + MSG_WriteString(buf, va("\ncl_downloadbegin %lu %s\n", len, filename)); + return true; + } + else if(cnt >= 1 && cnt <= npackets) + { + unsigned long thispacketoffset = (cnt - 1) * packetsize; + int thispacketsize = len - thispacketoffset; + if(thispacketsize > packetsize) + thispacketsize = packetsize; + + MSG_WriteByte(buf, svc_downloaddata); + MSG_WriteLong(buf, thispacketoffset); + MSG_WriteShort(buf, thispacketsize); + SZ_Write(buf, data + thispacketoffset, thispacketsize); + + return true; + } + else if(cnt == npackets + 1) + { + MSG_WriteByte(buf, svc_stufftext); + MSG_WriteString(buf, va("\ncl_downloadfinished %lu %d\n", len, crc)); + return true; + } + return false; +} + void CL_VM_Init (void) { const char* csprogsfn; @@ -696,16 +736,19 @@ void CL_VM_Init (void) if (csprogsdata) { csprogsdatacrc = CRC_Block(csprogsdata, csprogsdatasize); - Mem_Free(csprogsdata); if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize) { if (cls.demoplayback) { Con_Printf("^1Warning: Your %s is not the same version as the demo was recorded with (CRC/size are %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); - return; + // Mem_Free(csprogsdata); + // return; + // We WANT to continue here, and play the demo with different csprogs! + // After all, this is just a warning. Sure things may go wrong from here. } else { + Mem_Free(csprogsdata); Con_Printf("^1Your %s is not the same version as the server (CRC is %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); CL_Disconnect(); return; @@ -759,11 +802,30 @@ void CL_VM_Init (void) CL_VM_Error("CSQC %s ^2failed to load\n", csprogsfn); if(!sv.active) CL_Disconnect(); + Mem_Free(csprogsdata); return; } Con_Printf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize); + if(cls.demorecording) + { + int i; + char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + void *demobuf; fs_offset_t demofilesize; + + sb.data = (void *) buf; + sb.maxsize = sizeof(buf); + i = 0; + + CL_CutDemo(&demobuf, &demofilesize); + while(MakeDownloadPacket(csprogsfn, csprogsdata, csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol)) + CL_WriteDemoMessage(&sb); + CL_PasteDemo(&demobuf, &demofilesize); + } + Mem_Free(csprogsdata); + // check if OP_STATE animation is possible in this dat file if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0) prog->flag |= PRVM_OP_STATE; diff --git a/csprogs.h b/csprogs.h index 8d3278e2..25fabd91 100644 --- a/csprogs.h +++ b/csprogs.h @@ -53,4 +53,6 @@ extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name acco extern cvar_t csqc_progcrc; extern cvar_t csqc_progsize; +qboolean MakeDownloadPacket(const char *filename, void *data, unsigned long len, int crc, int cnt, sizebuf_t *buf, int protocol); + #endif diff --git a/sv_main.c b/sv_main.c index 937826d1..0ac5ac5d 100644 --- a/sv_main.c +++ b/sv_main.c @@ -22,6 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" #include "sv_demo.h" #include "libcurl.h" +#include "csprogs.h" static void SV_SaveEntFile_f(void); static void SV_StartDownload_f(void); @@ -739,6 +740,28 @@ void SV_SendServerinfo (client_t *client) dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)", gamename, buildstring, prog->filecrc); MSG_WriteString (&client->netconnection->message,message); + SV_StopDemoRecording(client); // to split up demos into different files + if(sv_autodemo_perclient.integer && client->netconnection) + { + char demofile[MAX_OSPATH]; + char levelname[MAX_QPATH]; + char ipaddress[MAX_QPATH]; + size_t i; + + // start a new demo file + strlcpy(levelname, FS_FileWithoutPath(sv.worldmodel->name), sizeof(levelname)); + if (strrchr(levelname, '.')) + *(strrchr(levelname, '.')) = 0; + + LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); + for(i = 0; ipaddress[i]; ++i) + if(!isalnum(ipaddress[i])) + ipaddress[i] = '-'; + dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), levelname, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); + + SV_StartDemoRecording(client, demofile, -1); + } + //[515]: init csprogs according to version of svprogs, check the crc, etc. if (sv.csqc_progname[0]) { @@ -750,6 +773,29 @@ void SV_SendServerinfo (client_t *client) MSG_WriteString (&client->netconnection->message, va("csqc_progsize %i\n", sv.csqc_progsize)); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", sv.csqc_progcrc)); + + if(client->sv_demo_file != NULL) + { + void *csqcbuf; + fs_offset_t csqclen; + int csqccrc; + int i; + char buf[NET_MAXMESSAGE]; + sizebuf_t sb; + + csqcbuf = FS_LoadFile(sv.csqc_progname, tempmempool, true, &csqclen); + if(csqcbuf) + { + csqccrc = CRC_Block(csqcbuf, csqclen); + sb.data = (void *) buf; + sb.maxsize = sizeof(buf); + i = 0; + while(MakeDownloadPacket(sv.csqc_progname, csqcbuf, csqclen, csqccrc, i++, &sb, sv.protocol)) + SV_WriteDemoMessage(client, &sb, false); + Mem_Free(csqcbuf); + } + } + //[515]: init stufftext string (it is sent before svc_serverinfo) val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.SV_InitCmd); if (val) @@ -819,28 +865,6 @@ void SV_SendServerinfo (client_t *client) client->num_pings = 0; #endif client->ping = 0; - - SV_StopDemoRecording(client); // to split up demos into different files - if(sv_autodemo_perclient.integer && client->netconnection) - { - char demofile[MAX_OSPATH]; - char levelname[MAX_QPATH]; - char ipaddress[MAX_QPATH]; - size_t i; - - // start a new demo file - strlcpy(levelname, FS_FileWithoutPath(sv.worldmodel->name), sizeof(levelname)); - if (strrchr(levelname, '.')) - *(strrchr(levelname, '.')) = 0; - - LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); - for(i = 0; ipaddress[i]; ++i) - if(!isalnum(ipaddress[i])) - ipaddress[i] = '-'; - dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), levelname, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); - - SV_StartDemoRecording(client, demofile, -1); - } } /* -- 2.39.2