/* =================== SV_ExecuteClientMessage Parse a client packet =================== */ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { int c; int serverId; MSG_Bitstream(msg); serverId = MSG_ReadLong( msg ); cl->messageAcknowledge = MSG_ReadLong( msg ); if (cl->messageAcknowledge < 0) { // usually only hackers create messages like this // it is more annoying for them to let them hanging //SV_DropClient( cl, "illegible client message" ); return; } cl->reliableAcknowledge = MSG_ReadLong( msg ); // NOTE: when the client message is fux0red the acknowledgement numbers // can be out of range, this could cause the server to send thousands of server // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { // usually only hackers create messages like this // it is more annoying for them to let them hanging //SV_DropClient( cl, "illegible client message" ); cl->reliableAcknowledge = cl->reliableSequence; return; } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate // // if the client was downloading, let it stay at whatever serverId and // gamestate it was at. This allows it to keep downloading even when // the gamestate changes. After the download is finished, we'll // notice and send it a new game state if ( serverId != sv.serverId && !*cl->downloadName ) { if ( serverId == sv.restartedServerId ) { // they just haven't caught the map_restart yet return; } // if we can tell that the client has dropped the last // gamestate we sent them, resend it if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); SV_SendClientGameState( cl ); } return; } // read optional clientCommand strings do { c = MSG_ReadByte( msg ); if ( c == clc_EOF ) { break; } if ( c != clc_clientCommand ) { break; } if ( !SV_ClientCommand( cl, msg ) ) { return; // we couldn't execute it because of the flood protection } if (cl->state == CS_ZOMBIE) { return; // disconnect command } } while ( 1 ); // read the usercmd_t if ( c == clc_move ) { SV_UserMove( cl, msg, qtrue ); } else if ( c == clc_moveNoDelta ) { SV_UserMove( cl, msg, qfalse ); } else if ( c != clc_EOF ) { Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients ); } // if ( msg->readcount != msg->cursize ) { // Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); // } }
/* =================== SV_ExecuteClientMessage Parse a client packet =================== */ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { int c; int serverId; MSG_Bitstream( msg ); serverId = MSG_ReadLong( msg ); cl->messageAcknowledge = MSG_ReadLong( msg ); if ( cl->messageAcknowledge < 0 ) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG SV_DropClient( cl, "DEBUG: illegible client message" ); #endif return; } cl->reliableAcknowledge = MSG_ReadLong( msg ); // NOTE: when the client message is fux0red the acknowledgement numbers // can be out of range, this could cause the server to send thousands of server // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient if ( cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS ) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG SV_DropClient( cl, "DEBUG: illegible client message" ); #endif cl->reliableAcknowledge = cl->reliableSequence; return; } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate // // if the client was downloading, let it stay at whatever serverId and // gamestate it was at. This allows it to keep downloading even when // the gamestate changes. After the download is finished, we'll // notice and send it a new game state // // show_bug.cgi?id=536 // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" // but we still need to read the next message to move to next download or send gamestate // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else if ( serverId != sv.serverId && !*cl->downloadName && !strstr( cl->lastClientCommandString, "nextdl" ) ) { if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart // they just haven't caught the map_restart yet Log::Debug( "%s^7: ignoring pre map_restart / outdated client message", cl->name ); return; } // if we can tell that the client has dropped the last // gamestate we sent them, resend it if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { Log::Debug( "%s^7: dropped gamestate, resending", cl->name ); SV_SendClientGameState( cl ); } // read optional clientCommand strings do { c = MSG_ReadByte( msg ); if ( c == clc_EOF ) { break; } if ( c != clc_clientCommand ) { break; } if ( !SV_ClientCommand( cl, msg, true ) ) { return; // we couldn't execute it because of the flood protection } if ( cl->state == clientState_t::CS_ZOMBIE ) { return; // disconnect command } } while ( 1 ); return; } // read optional clientCommand strings do { c = MSG_ReadByte( msg ); if ( c == clc_EOF ) { break; } if ( c != clc_clientCommand ) { break; } if ( !SV_ClientCommand( cl, msg, false ) ) { return; // we couldn't execute it because of the flood protection } if ( cl->state == clientState_t::CS_ZOMBIE ) { return; // disconnect command } } while ( 1 ); // read the usercmd_t if ( c == clc_move ) { SV_UserMove( cl, msg, true ); } else if ( c == clc_moveNoDelta ) { SV_UserMove( cl, msg, false ); } else if ( c != clc_EOF ) { Log::Warn( "bad command byte for client %i\n", ( int )( cl - svs.clients ) ); } SV_ParseBinaryMessage( cl, msg ); // if ( msg->readcount != msg->cursize ) { // Log::Notice( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); // } }
/* Start a server-side demo. This does it all, create the file and adjust the demo-related stuff in client_t. This is mostly ripped from sv_client.c/SV_SendClientGameState and cl_main.c/CL_Record_f. */ static void SVD_StartDemoFile(client_t *client, const char *path) { int i, len; entityState_t *base, nullstate; msg_t msg; byte buffer[MAX_MSGLEN]; fileHandle_t file; #ifdef USE_DEMO_FORMAT_42 char *s; int v, size; #endif Com_DPrintf("SVD_StartDemoFile\n"); assert(!client->demo_recording); // create the demo file and write the necessary header file = FS_FOpenFileWrite(path); assert(file != 0); /* File_write_header_demo // ADD this fx */ /* HOLBLIN entete demo */ #ifdef USE_DEMO_FORMAT_42 //@Barbatos: get the mod version from the server s = Cvar_VariableString("g_modversion"); size = strlen( s ); len = LittleLong( size ); FS_Write( &len, 4, file ); FS_Write( s , size , file ); v = LittleLong( PROTOCOL_VERSION ); FS_Write ( &v, 4 , file ); len = 0; len = LittleLong( len ); FS_Write ( &len, 4 , file ); FS_Write ( &len, 4 , file ); #endif /* END HOLBLIN entete demo */ MSG_Init(&msg, buffer, sizeof(buffer)); MSG_Bitstream(&msg); // XXX server code doesn't do this, client code does MSG_WriteLong(&msg, client->lastClientCommand); // TODO: or is it client->reliableSequence? MSG_WriteByte(&msg, svc_gamestate); MSG_WriteLong(&msg, client->reliableSequence); for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (sv.configstrings[i][0]) { MSG_WriteByte(&msg, svc_configstring); MSG_WriteShort(&msg, i); MSG_WriteBigString(&msg, sv.configstrings[i]); } } Com_Memset(&nullstate, 0, sizeof(nullstate)); for (i = 0 ; i < MAX_GENTITIES; i++) { base = &sv.svEntities[i].baseline; if (!base->number) { continue; } MSG_WriteByte(&msg, svc_baseline); MSG_WriteDeltaEntity(&msg, &nullstate, base, qtrue); } MSG_WriteByte(&msg, svc_EOF); MSG_WriteLong(&msg, client - svs.clients); MSG_WriteLong(&msg, sv.checksumFeed); MSG_WriteByte(&msg, svc_EOF); // XXX server code doesn't do this, SV_Netchan_Transmit adds it! len = LittleLong(client->netchan.outgoingSequence-1); FS_Write(&len, 4, file); len = LittleLong (msg.cursize); FS_Write(&len, 4, file); FS_Write(msg.data, msg.cursize, file); #ifdef USE_DEMO_FORMAT_42 // add size of packet in the end for backward play /* holblin */ FS_Write(&len, 4, file); #endif FS_Flush(file); // adjust client_t to reflect demo started client->demo_recording = qtrue; client->demo_file = file; client->demo_waiting = qtrue; client->demo_backoff = 1; client->demo_deltas = 0; }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; if ( cl_shownet->integer == 1 ) { Com_Printf ("%i ",msg->cursize); } else if ( cl_shownet->integer >= 2 ) { Com_Printf ("------------------\n"); } MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong( msg ); // if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { clc.reliableAcknowledge = clc.reliableSequence; } // // parse the message // while ( 1 ) { if ( msg->readcount > msg->cursize ) { Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); break; } cmd = MSG_ReadByte( msg ); if (cmd == svc_EOF) { SHOWNET( msg, "END OF MESSAGE" ); break; } if ( cl_shownet->integer >= 2 ) { if ( (cmd < 0) || (!svc_strings[cmd]) ) { Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); } else { SHOWNET( msg, svc_strings[cmd] ); } } // other commands switch ( cmd ) { default: Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message"); break; case svc_nop: break; case svc_serverCommand: CL_ParseCommandString( msg ); break; case svc_gamestate: CL_ParseGamestate( msg ); break; case svc_snapshot: CL_ParseSnapshot( msg ); break; case svc_download: CL_ParseDownload( msg ); break; case svc_voipSpeex: #ifdef USE_VOIP CL_ParseVoip( msg, qtrue ); #endif break; case svc_voipOpus: #ifdef USE_VOIP CL_ParseVoip( msg, !clc.voipEnabled ); #endif break; } } }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds During normal gameplay, a client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence <optional reliable commands> 1 clc_move or clc_moveNoDelta 1 command count <count * usercmds> =================== */ void CL_WritePacket( void ) { msg_t buf; byte data[MAX_MSGLEN]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count, key; // don't send anything if playing back a demo if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { return; } Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; MSG_Init( &buf, data, sizeof(data) ); MSG_Bitstream( &buf ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong( &buf, cl.serverId ); // write the last message we received, which can // be used for delta compression, and is also used // to tell if we dropped a gamestate MSG_WriteLong( &buf, clc.serverMessageSequence ); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write any unacknowledged clientCommands for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf("MAX_PACKET_USERCMDS\n"); } #ifdef USE_VOIP if (clc.voipOutgoingDataSize > 0) { // only send if data. // Move cl_voipSendTarget from a string to the bitmasks if needed. if (cl_voipSendTarget->modified) { char buffer[32]; const char *target = cl_voipSendTarget->string; if (Q_stricmp(target, "attacker") == 0) { int player = VM_Call( cgvm, CG_LAST_ATTACKER ); Com_sprintf(buffer, sizeof (buffer), "%d", player); target = buffer; } else if (Q_stricmp(target, "crosshair") == 0) { int player = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); Com_sprintf(buffer, sizeof (buffer), "%d", player); target = buffer; } if ((*target == '\0') || (Q_stricmp(target, "all") == 0)) { const int all = 0x7FFFFFFF; clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = all; } else if (Q_stricmp(target, "none") == 0) { clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; } else { const char *ptr = target; clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; do { if ((*ptr == ',') || (*ptr == '\0')) { const int val = atoi(target); target = ptr + 1; if ((val >= 0) && (val < 31)) { clc.voipTarget1 |= (1 << (val-0)); } else if ((val >= 31) && (val < 62)) { clc.voipTarget2 |= (1 << (val-31)); } else if ((val >= 62) && (val < 93)) { clc.voipTarget3 |= (1 << (val-62)); } } } while (*(ptr++)); } cl_voipSendTarget->modified = qfalse; } MSG_WriteByte (&buf, clc_EOF); // placate legacy servers. MSG_WriteByte (&buf, clc_extension); MSG_WriteByte (&buf, clc_voip); MSG_WriteByte (&buf, clc.voipOutgoingGeneration); MSG_WriteLong (&buf, clc.voipOutgoingSequence); MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); MSG_WriteLong (&buf, clc.voipTarget1); MSG_WriteLong (&buf, clc.voipTarget2); MSG_WriteLong (&buf, clc.voipTarget3); MSG_WriteShort (&buf, clc.voipOutgoingDataSize); MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); // If we're recording a demo, we have to fake a server packet with // this VoIP data so it gets to disk; the server doesn't send it // back to us, and we might as well eliminate concerns about dropped // and misordered packets here. if ( clc.demorecording && !clc.demowaiting ) { const int voipSize = clc.voipOutgoingDataSize; msg_t fakemsg; byte fakedata[MAX_MSGLEN]; MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); MSG_Bitstream (&fakemsg); MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); MSG_WriteByte (&fakemsg, svc_EOF); MSG_WriteByte (&fakemsg, svc_extension); MSG_WriteByte (&fakemsg, svc_voip); MSG_WriteShort (&fakemsg, clc.clientNum); MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames); MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize ); MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize); MSG_WriteByte (&fakemsg, svc_EOF); CL_WriteDemoMessage (&fakemsg, 0); } clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } else #endif if ( count >= 1 ) { if ( cl_showSend->integer ) { Com_Printf( "(%i)", count ); } // begin a client move command if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { MSG_WriteByte (&buf, clc_moveNoDelta); } else { MSG_WriteByte (&buf, clc_move); } // write the command count MSG_WriteByte( &buf, count ); // use the checksum feed in the key key = clc.checksumFeed; // also use the message acknowledge key ^= clc.serverMessageSequence; // also use the last acknowledged server command in the key key ^= MSG_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); // write all the commands, including the predicted command for ( i = 0 ; i < count ; i++ ) { j = (cl.cmdNumber - count + i + 1) & CMD_MASK; cmd = &cl.cmds[j]; MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); oldcmd = cmd; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.outPackets[ packetNum ].p_realtime = cls.realtime; cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; if ( cl_showSend->integer ) { Com_Printf( "%i ", buf.cursize ); } CL_Netchan_Transmit (&clc.netchan, &buf); // clients never really should have messages large enough // to fragment, but in case they do, fire them all off // at once // TTimo: this causes a packet burst, which is bad karma for winsock // added a WARNING message, we'll see if there are legit situations where this happens while ( clc.netchan.unsentFragments ) { Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" ); CL_Netchan_TransmitNextFragment( &clc.netchan ); } }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds A client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence <optional reliable commands> 1 clc_move or clc_moveNoDelta 1 command count <count * usercmds> =================== */ void CL_WritePacket() { msg_t buf; byte data[ MAX_MSGLEN ]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count; // don't send anything if playing back a demo if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { return; } memset( &nullcmd, 0, sizeof( nullcmd ) ); oldcmd = &nullcmd; MSG_Init( &buf, data, sizeof( data ) ); MSG_Bitstream( &buf ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong( &buf, cl.serverId ); // write the last message we received, which can // be used for delta compression, and is also used // to tell if we dropped a gamestate MSG_WriteLong( &buf, clc.serverMessageSequence ); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write any unacknowledged clientCommands // NOTE TTimo: if you verbose this, you will see that there are quite a few duplicates // typically several unacknowledged cp or userinfo commands stacked up for ( i = clc.reliableAcknowledge + 1; i <= clc.reliableSequence; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & ( MAX_RELIABLE_COMMANDS - 1 ) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = ( clc.netchan.outgoingSequence - 1 - cl_packetdup->integer ) & PACKET_MASK; count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf( "MAX_PACKET_USERCMDS" ); } #ifdef USE_VOIP if ( clc.voipOutgoingDataSize > 0 ) { if ( ( clc.voipFlags & VOIP_SPATIAL ) || Com_IsVoipTarget( clc.voipTargets, sizeof( clc.voipTargets ), -1 ) ) { MSG_WriteByte( &buf, clc_voip ); MSG_WriteByte( &buf, clc.voipOutgoingGeneration ); MSG_WriteLong( &buf, clc.voipOutgoingSequence ); MSG_WriteByte( &buf, clc.voipOutgoingDataFrames ); MSG_WriteData( &buf, clc.voipTargets, sizeof( clc.voipTargets ) ); MSG_WriteByte( &buf, clc.voipFlags ); MSG_WriteShort( &buf, clc.voipOutgoingDataSize ); MSG_WriteData( &buf, clc.voipOutgoingData, clc.voipOutgoingDataSize ); // If we're recording a demo, we have to fake a server packet with // this VoIP data so it gets to disk; the server doesn't send it // back to us, and we might as well eliminate concerns about dropped // and misordered packets here. if ( clc.demorecording && !clc.demowaiting ) { const int voipSize = clc.voipOutgoingDataSize; msg_t fakemsg; byte fakedata[ MAX_MSGLEN ]; MSG_Init( &fakemsg, fakedata, sizeof( fakedata ) ); MSG_Bitstream( &fakemsg ); MSG_WriteLong( &fakemsg, clc.reliableAcknowledge ); MSG_WriteByte( &fakemsg, svc_voip ); MSG_WriteShort( &fakemsg, clc.clientNum ); MSG_WriteByte( &fakemsg, clc.voipOutgoingGeneration ); MSG_WriteLong( &fakemsg, clc.voipOutgoingSequence ); MSG_WriteByte( &fakemsg, clc.voipOutgoingDataFrames ); MSG_WriteShort( &fakemsg, clc.voipOutgoingDataSize ); MSG_WriteBits( &fakemsg, clc.voipFlags, VOIP_FLAGCNT ); MSG_WriteData( &fakemsg, clc.voipOutgoingData, voipSize ); MSG_WriteByte( &fakemsg, svc_EOF ); CL_WriteDemoMessage( &fakemsg, 0 ); } clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } else { // We have data, but no targets. Silently discard all data clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } } #endif if ( count >= 1 ) { if ( cl_showSend->integer ) { Com_Printf( "(%i)", count ); } // begin a client move command if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { MSG_WriteByte( &buf, clc_moveNoDelta ); } else { MSG_WriteByte( &buf, clc_move ); } // write the command count MSG_WriteByte( &buf, count ); // write all the commands, including the predicted command for ( i = 0; i < count; i++ ) { j = ( cl.cmdNumber - count + i + 1 ) & CMD_MASK; cmd = &cl.cmds[ j ]; MSG_WriteDeltaUsercmd( &buf, oldcmd, cmd ); oldcmd = cmd; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.outPackets[ packetNum ].p_realtime = cls.realtime; cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; if ( cl_showSend->integer ) { Com_Printf("%i ", buf.cursize ); } MSG_WriteByte( &buf, clc_EOF ); CL_WriteBinaryMessage( &buf ); Netchan_Transmit( &clc.netchan, buf.cursize, buf.data ); // clients never really should have messages large enough // to fragment, but in case they do, fire them all off // at once // TTimo: this causes a packet burst, which is bad karma for winsock // added a WARNING message, we'll see if there are legit situations where this happens while ( clc.netchan.unsentFragments ) { if ( cl_showSend->integer ) { Com_Printf( "WARNING: unsent fragments (not supposed to happen!)" ); } Netchan_TransmitNextFragment( &clc.netchan ); } }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage(msg_t *msg) { int cmd; if (cl_shownet->integer == 1) { Com_Printf("%i ", msg->cursize); } else if (cl_shownet->integer >= 2) { Com_Printf("------------------\n"); } MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong(msg); if (clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS) { clc.reliableAcknowledge = clc.reliableSequence; } // parse the message while (1) { if (msg->readcount > msg->cursize) { Com_Error(ERR_DROP, "CL_ParseServerMessage: read past end of server message"); break; } cmd = MSG_ReadByte(msg); if (cmd == svc_EOF) { SHOWNET(msg, "END OF MESSAGE"); break; } if (cl_shownet->integer >= 2) { if (cmd < 0 || cmd > svc_EOF) // MSG_ReadByte might return -1 and we can't access our svc_strings array ... { Com_Printf("%3i:BAD BYTE %i\n", msg->readcount - 1, cmd); // -> ERR_DROP } else { if (!svc_strings[cmd]) { Com_Printf("%3i:BAD CMD %i\n", msg->readcount - 1, cmd); } else { SHOWNET(msg, svc_strings[cmd]); } } } // other commands switch (cmd) { default: Com_Error(ERR_DROP, "CL_ParseServerMessage: Illegible server message %d", cmd); break; case svc_nop: break; case svc_serverCommand: CL_ParseCommandString(msg); break; case svc_gamestate: CL_ParseGamestate(msg); break; case svc_snapshot: CL_ParseSnapshot(msg); break; case svc_download: CL_ParseDownload(msg); break; } } CL_ParseBinaryMessage(msg); }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds During normal gameplay, a client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence <optional reliable commands> 1 clc_move or clc_moveNoDelta 1 command count <count * usercmds> =================== */ void CL_WritePacket( void ) { msg_t buf; byte data[MAX_MSGLEN]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count, key; // don't send anything if playing back a demo if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { return; } Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; MSG_Init( &buf, data, sizeof(data) ); MSG_Bitstream( &buf ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong( &buf, cl.serverId ); // write the last message we received, which can // be used for delta compression, and is also used // to tell if we dropped a gamestate MSG_WriteLong( &buf, clc.serverMessageSequence ); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write any unacknowledged clientCommands for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf("MAX_PACKET_USERCMDS\n"); } if ( count >= 1 ) { if ( cl_showSend->integer ) { Com_Printf( "(%i)", count ); } // begin a client move command if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { MSG_WriteByte (&buf, clc_moveNoDelta); } else { MSG_WriteByte (&buf, clc_move); } // write the command count MSG_WriteByte( &buf, count ); // use the checksum feed in the key key = clc.checksumFeed; // also use the message acknowledge key ^= clc.serverMessageSequence; // also use the last acknowledged server command in the key key ^= Com_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); // write all the commands, including the predicted command for ( i = 0 ; i < count ; i++ ) { j = (cl.cmdNumber - count + i + 1) & CMD_MASK; cmd = &cl.cmds[j]; MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); oldcmd = cmd; } if (cl.gcmdSentValue) { //hmm, just clear here, I guess.. hoping it will resolve issues with gencmd values sometimes not going through. cl.gcmdSendValue = qfalse; cl.gcmdSentValue = qfalse; cl.gcmdValue = 0; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.outPackets[ packetNum ].p_realtime = cls.realtime; cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; if ( cl_showSend->integer ) { Com_Printf( "%i ", buf.cursize ); } CL_Netchan_Transmit (&clc.netchan, &buf); // clients never really should have messages large enough // to fragment, but in case they do, fire them all off // at once while ( clc.netchan.unsentFragments ) { CL_Netchan_TransmitNextFragment( &clc.netchan ); } }
qboolean CL_PeekSnapshot(int snapshotNumber, snapshot_t *snapshot) { clSnapshot_t *clSnap; clSnapshot_t csn; int i, count; int origPosition; int cmd; //char *s; char buffer[16]; qboolean success = qfalse; int r; msg_t buf; byte bufData[MAX_MSGLEN]; int j; int lastPacketTimeOrig; int parseEntitiesNumOrig; int currentSnapNum; //int serverMessageSequence; clSnap = &csn; if (!clc.demoplaying) { return qfalse; } if (snapshotNumber <= cl.snap.messageNum) { success = CL_GetSnapshot(snapshotNumber, snapshot); if (!success) { Com_FuncPrinf("snapshot number outside of backup buffer\n"); return qfalse; } return qtrue; } if (snapshotNumber > cl.snap.messageNum + 1) { Com_FuncPrinf("FIXME CL_PeekSnapshot %d > cl.snap.messageNum + 1 (%d)\n", snapshotNumber, cl.snap.messageNum); return qfalse; // FIXME: } parseEntitiesNumOrig = cl.parseEntitiesNum; lastPacketTimeOrig = clc.lastPacketTime; // CL_ReadDemoMessage() origPosition = FS_FTell(clc.demofile); if (origPosition < 0) { // FS_FTell prints the warning ... return qfalse; } currentSnapNum = cl.snap.messageNum; for (j = 0; j < snapshotNumber - currentSnapNum; j++) { // get the sequence number memset(buffer, 0, sizeof(buffer)); r = FS_Read(&buffer, 4, clc.demofile); if (r != 4) { Com_FuncPrinf("couldn't read sequence number\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } //serverMessageSequence = LittleLong(*((int *)buffer)); // init the message memset(&buf, 0, sizeof(msg_t)); MSG_Init(&buf, bufData, sizeof(bufData)); // get the length r = FS_Read(&buf.cursize, 4, clc.demofile); if (r != 4) { Com_FuncPrinf("couldn't get length\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } buf.cursize = LittleLong(buf.cursize); if (buf.cursize == -1) { Com_FuncPrinf("buf.cursize == -1\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } if (buf.cursize > buf.maxsize) { Com_FuncDrop("demoMsglen > MAX_MSGLEN"); return qfalse; } r = FS_Read(buf.data, buf.cursize, clc.demofile); if (r != buf.cursize) { Com_FuncPrinf("Demo file was truncated.\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qfalse; } clc.lastPacketTime = cls.realtime; buf.readcount = 0; MSG_Bitstream(&buf); // get the reliable sequence acknowledge number MSG_ReadLong(&buf); // parse the message while (qtrue) { if (buf.readcount > buf.cursize) { Com_FuncDrop("read past end of server message"); return qfalse; } cmd = MSG_ReadByte(&buf); if (cmd == svc_EOF) { break; } success = qfalse; switch (cmd) { default: Com_FuncDrop("Illegible server message"); return qfalse; case svc_nop: break; case svc_serverCommand: MSG_ReadLong(&buf); // seq //s = MSG_ReadString(&buf); MSG_ReadString(&buf); break; case svc_gamestate: Com_FuncPrinf("FIXME gamestate\n"); goto alldone; break; case svc_snapshot: // TODO: changed this check if it works CL_ParseSnapshot(&buf); if (cl.snap.valid) { success = qtrue; } break; case svc_download: Com_FuncPrinf("FIXME download\n"); goto alldone; break; } } alldone: if (!success) { Com_FuncPrinf("failed\n"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return success; } // FIXME other ents not supported yet // if the entities in the frame have fallen out of their // circular buffer, we can't return it if (cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES) { Com_FuncPrinf("cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES"); FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; return qtrue; // FIXME if you fix other ents } // write the snapshot snapshot->snapFlags = clSnap->snapFlags; snapshot->serverCommandSequence = clSnap->serverCommandNum; snapshot->ping = clSnap->ping; snapshot->serverTime = clSnap->serverTime; Com_Memcpy(snapshot->areamask, clSnap->areamask, sizeof(snapshot->areamask)); snapshot->ps = clSnap->ps; count = clSnap->numEntities; if (count > MAX_ENTITIES_IN_SNAPSHOT) { Com_FuncPrinf("truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT); count = MAX_ENTITIES_IN_SNAPSHOT; } snapshot->numEntities = count; for (i = 0; i < count; i++) { snapshot->entities[i] = cl.parseEntities[(clSnap->parseEntitiesNum + i) & (MAX_PARSE_ENTITIES - 1)]; } } FS_Seek(clc.demofile, origPosition, FS_SEEK_SET); clc.lastPacketTime = lastPacketTimeOrig; cl.parseEntitiesNum = parseEntitiesNumOrig; // TODO: configstring changes and server commands!!! return qtrue; }
// Do very shallow parse of the demo (could be extended) just to get times and snapshot count static void CL_ParseDemo(void) { int tstart = 0; int demofile = 0; // Reset our demo data memset(&di, 0, sizeof(di)); // Parse start di.gameStartTime = -1; di.gameEndTime = -1; FS_Seek(clc.demofile, 0, FS_SEEK_SET); tstart = Sys_Milliseconds(); while (qtrue) { int r; msg_t buf; msg_t *msg; byte bufData[MAX_MSGLEN]; int s; int cmd; di.demoPos = FS_FTell(clc.demofile); // get the sequence number r = FS_Read(&s, 4, clc.demofile); if (r != 4) { CL_DemoCompleted(); return; } clc.serverMessageSequence = LittleLong(s); // init the message MSG_Init(&buf, bufData, sizeof(bufData)); // get the length r = FS_Read(&buf.cursize, 4, clc.demofile); if (r != 4) { break; } buf.cursize = LittleLong(buf.cursize); if (buf.cursize == -1) { break; } if (buf.cursize > buf.maxsize) { Com_FuncPrinf("demoMsglen > MAX_MSGLEN"); break; } r = FS_Read(buf.data, buf.cursize, clc.demofile); if (r != buf.cursize) { Com_FuncPrinf("Demo file was truncated.\n"); break; } clc.lastPacketTime = cls.realtime; buf.readcount = 0; // parse msg = &buf; MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong(msg); if (clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS) { clc.reliableAcknowledge = clc.reliableSequence; } // parse the message while (qtrue) { if (msg->readcount > msg->cursize) { Com_FuncDrop("read past end of server message"); return; } cmd = MSG_ReadByte(msg); if (cmd == svc_EOF) { break; } // other commands switch (cmd) { default: Com_FuncDrop("Illegible server message %d", cmd); return; case svc_nop: break; case svc_serverCommand: MSG_ReadLong(msg); MSG_ReadString(msg); break; case svc_gamestate: clc.serverCommandSequence = MSG_ReadLong(msg); cl.gameState.dataCount = 1; while (qtrue) { int cmd2 = MSG_ReadByte(msg); if (cmd2 == svc_EOF) { break; } if (cmd2 == svc_configstring) { MSG_ReadShort(msg); MSG_ReadBigString(msg); } else if (cmd2 == svc_baseline) { entityState_t s1, s2; memset(&s1, 0, sizeof(s1)); memset(&s2, 0, sizeof(s2)); MSG_ReadBits(msg, GENTITYNUM_BITS); MSG_ReadDeltaEntity(msg, &s1, &s2, 0); } else { Com_FuncDrop("bad command byte"); return; } } MSG_ReadLong(msg); MSG_ReadLong(msg); break; case svc_snapshot: CL_ParseDemoSnapShotSimple(msg); break; case svc_download: MSG_ReadShort(msg); break; } } if (!cl.snap.valid) { Com_FuncPrinf("!cl.snap.valid\n"); continue; } if (cl.snap.serverTime < cl.oldFrameServerTime) { // ignore snapshots that don't have entities if (cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE) { continue; } cls.state = CA_ACTIVE; // set the timedelta so we are exactly on this first frame cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; cl.oldServerTime = cl.snap.serverTime; clc.timeDemoBaseTime = cl.snap.serverTime; } cl.oldFrameServerTime = cl.snap.serverTime; if (cl.newSnapshots) { CL_AdjustTimeDelta(); } di.lastServerTime = cl.snap.serverTime; if (di.firstServerTime == 0) { di.firstServerTime = cl.snap.serverTime; Com_FuncPrinf("firstServerTime %d\n", di.firstServerTime); } di.snapsInDemo++; } Com_FuncPrinf("Snaps in demo: %i\n", di.snapsInDemo); Com_FuncPrinf("last serverTime %d total %f minutes\n", cl.snap.serverTime, (cl.snap.serverTime - di.firstServerTime) / 1000.0 / 60.0); Com_FuncPrinf("parse time %f seconds\n", (float)(Sys_Milliseconds() - tstart) / 1000.0); FS_Seek(clc.demofile, 0, FS_SEEK_SET); clc.demoplaying = qfalse; demofile = clc.demofile; CL_ClearState(); Com_Memset(&clc, 0, sizeof(clc)); Com_ClearDownload(); clc.demofile = demofile; cls.state = CA_DISCONNECTED; cl_connectedToPureServer = qfalse; dpi.firstTime = di.firstServerTime; dpi.lastTime = di.lastServerTime; }
void CL_Record(const char *name) { int i; msg_t buf; byte bufData[MAX_MSGLEN]; entityState_t *ent; entityState_t nullstate; char *s; int len; // open the demo file Com_FuncPrinf("Recording to %s.\n", name); clc.demofile = FS_FOpenFileWrite(name); if (!clc.demofile) { Com_FuncPrinf("ERROR: couldn't open.\n"); return; } clc.demorecording = qtrue; Cvar_Set("cl_demorecording", "1"); // fretn Q_strncpyz(clc.demoName, demoName, sizeof(clc.demoName)); Cvar_Set("cl_demofilename", clc.demoName); // bani Cvar_Set("cl_demooffset", "0"); // bani // don't start saving messages until a non-delta compressed message is received clc.demowaiting = qtrue; // write out the gamestate message MSG_Init(&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE: all server->client messages now acknowledge MSG_WriteLong(&buf, clc.reliableSequence); MSG_WriteByte(&buf, svc_gamestate); MSG_WriteLong(&buf, clc.serverCommandSequence); // configstrings for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (!cl.gameState.stringOffsets[i]) { continue; } s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; MSG_WriteByte(&buf, svc_configstring); MSG_WriteShort(&buf, i); MSG_WriteBigString(&buf, s); } // baselines memset(&nullstate, 0, sizeof(nullstate)); for (i = 0; i < MAX_GENTITIES; i++) { ent = &cl.entityBaselines[i]; if (!ent->number) { continue; } MSG_WriteByte(&buf, svc_baseline); MSG_WriteDeltaEntity(&buf, &nullstate, ent, qtrue); } MSG_WriteByte(&buf, svc_EOF); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clc.clientNum); // write the checksum feed MSG_WriteLong(&buf, clc.checksumFeed); // finished writing the client packet MSG_WriteByte(&buf, svc_EOF); // write it to the demo file len = LittleLong(clc.serverMessageSequence - 1); FS_Write(&len, 4, clc.demofile); len = LittleLong(buf.cursize); FS_Write(&len, 4, clc.demofile); FS_Write(buf.data, buf.cursize, clc.demofile); // the rest of the demo file will be copied from net messages }
void demoConvert( const char *oldName, const char *newBaseName, qboolean smoothen ) { fileHandle_t oldHandle = 0; fileHandle_t newHandle = 0; int temp; int oldSize; int msgSequence; msg_t oldMsg; byte oldData[ MAX_MSGLEN ]; int oldTime, nextTime, fullTime; int clientNum; demoFrame_t *workFrame; int parseEntitiesNum = 0; demoConvert_t *convert; char bigConfigString[BIG_INFO_STRING]; int bigConfigNumber; const char *s; clSnapshot_t *oldSnap = 0; clSnapshot_t *newSnap; int levelCount = 0; char newName[MAX_OSPATH]; oldSize = FS_FOpenFileRead( oldName, &oldHandle, qtrue ); if (!oldHandle) { Com_Printf("Failed to open %s for conversion.", oldName); return; } /* Alloc some memory */ convert = Z_Malloc( sizeof( demoConvert_t) ); /* Initialize the first workframe's strings */ while (oldSize > 0) { MSG_Init( &oldMsg, oldData, sizeof( oldData ) ); /* Read the sequence number */ if (FS_Read( &convert->messageNum, 4, oldHandle) != 4) goto conversionerror; convert->messageNum = LittleLong( convert->messageNum ); oldSize -= 4; /* Read the message size */ if (FS_Read( &oldMsg.cursize,4, oldHandle) != 4) goto conversionerror; oldSize -= 4; oldMsg.cursize = LittleLong( oldMsg.cursize ); /* Negative size signals end of demo */ if (oldMsg.cursize < 0) break; if ( oldMsg.cursize > oldMsg.maxsize ) goto conversionerror; /* Read the actual message */ if (FS_Read( oldMsg.data, oldMsg.cursize, oldHandle ) != oldMsg.cursize) goto conversionerror; oldSize -= oldMsg.cursize; // init the bitstream MSG_BeginReading( &oldMsg ); // Skip the reliable sequence acknowledge number MSG_ReadLong( &oldMsg ); // // parse the message // while ( 1 ) { byte cmd; if ( oldMsg.readcount > oldMsg.cursize ) { Com_Printf ("Demo conversion, read past end of server message.\n"); goto conversionerror; } cmd = MSG_ReadByte( &oldMsg ); if ( cmd == svc_EOF) { break; } workFrame = &convert->frames[ convert->frameIndex % DEMOCONVERTFRAMES ]; // other commands switch ( cmd ) { default: Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); break; case svc_nop: break; case svc_serverCommand: temp = MSG_ReadLong( &oldMsg ); s = MSG_ReadString( &oldMsg ); if (temp<=msgSequence) break; // Com_Printf( " server command %s\n", s ); msgSequence = temp; Cmd_TokenizeString( s ); if ( !Q_stricmp( Cmd_Argv(0), "bcs0" ) ) { bigConfigNumber = atoi( Cmd_Argv(1) ); Q_strncpyz( bigConfigString, Cmd_Argv(2), sizeof( bigConfigString )); break; } if ( !Q_stricmp( Cmd_Argv(0), "bcs1" ) ) { Q_strcat( bigConfigString, sizeof( bigConfigString ), Cmd_Argv(2)); break; } if ( !Q_stricmp( Cmd_Argv(0), "bcs2" ) ) { Q_strcat( bigConfigString, sizeof( bigConfigString ), Cmd_Argv(2)); demoFrameAddString( &workFrame->string, bigConfigNumber, bigConfigString ); break; } if ( !Q_stricmp( Cmd_Argv(0), "cs" ) ) { int num = atoi( Cmd_Argv(1) ); s = Cmd_ArgsFrom( 2 ); demoFrameAddString( &workFrame->string, num, Cmd_ArgsFrom( 2 ) ); break; } if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { int len = strlen( s ) + 1; char *dst; if (workFrame->commandUsed + len + 1 > sizeof( workFrame->commandData)) { Com_Printf("Overflowed state command data.\n"); goto conversionerror; } dst = workFrame->commandData + workFrame->commandUsed; *dst = clientNum; Com_Memcpy( dst+1, s, len ); workFrame->commandUsed += len + 1; } break; case svc_gamestate: if (newHandle) { FS_FCloseFile( newHandle ); newHandle = 0; } if (levelCount) { Com_sprintf( newName, sizeof( newName ), "%s.%d.mme", newBaseName, levelCount ); } else { Com_sprintf( newName, sizeof( newName ), "%s.mme", newBaseName ); } fullTime = -1; clientNum = -1; oldTime = -1; Com_Memset( convert, 0, sizeof( *convert )); convert->frames[0].string.used = 1; levelCount++; newHandle = FS_FOpenFileWrite( newName ); if (!newHandle) { Com_Printf("Failed to open %s for target conversion target.\n", newName); goto conversionerror; return; } else { FS_Write ( demoHeader, strlen( demoHeader ), newHandle ); } Com_sprintf( newName, sizeof( newName ), "%s.txt", newBaseName ); workFrame = &convert->frames[ convert->frameIndex % DEMOCONVERTFRAMES ]; msgSequence = MSG_ReadLong( &oldMsg ); while( 1 ) { cmd = MSG_ReadByte( &oldMsg ); if (cmd == svc_EOF) break; if ( cmd == svc_configstring) { int num; const char *s; num = MSG_ReadShort( &oldMsg ); s = MSG_ReadBigString( &oldMsg ); demoFrameAddString( &workFrame->string, num, s ); } else if ( cmd == svc_baseline ) { int num = MSG_ReadBits( &oldMsg, GENTITYNUM_BITS ); if ( num < 0 || num >= MAX_GENTITIES ) { Com_Printf( "Baseline number out of range: %i.\n", num ); goto conversionerror; } MSG_ReadDeltaEntity( &oldMsg, &demoNullEntityState, &convert->entityBaselines[num], num ); } else { Com_Printf( "Unknown block while converting demo gamestate.\n" ); goto conversionerror; } } clientNum = MSG_ReadLong( &oldMsg ); /* Skip the checksum feed */ MSG_ReadLong( &oldMsg ); break; case svc_snapshot: nextTime = MSG_ReadLong( &oldMsg ); /* Delta number, not needed */ newSnap = &convert->snapshots[convert->messageNum & PACKET_MASK]; Com_Memset (newSnap, 0, sizeof(*newSnap)); newSnap->deltaNum = MSG_ReadByte( &oldMsg ); newSnap->messageNum = convert->messageNum; if (!newSnap->deltaNum) { newSnap->deltaNum = -1; newSnap->valid = qtrue; // uncompressed frame oldSnap = NULL; } else { newSnap->deltaNum = newSnap->messageNum - newSnap->deltaNum; oldSnap = &convert->snapshots[newSnap->deltaNum & PACKET_MASK]; if (!oldSnap->valid) { Com_Printf( "Delta snapshot without base.\n" ); goto conversionerror; } else if (oldSnap->messageNum != newSnap->deltaNum) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_Printf ("Delta frame too old.\n"); } else if ( parseEntitiesNum - oldSnap->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { Com_Printf ("Delta parseEntitiesNum too old.\n"); } else { newSnap->valid = qtrue; // valid delta parse } } /* Snapflags, not needed */ newSnap->snapFlags = MSG_ReadByte( &oldMsg ); // read areamask workFrame->areaUsed = MSG_ReadByte( &oldMsg ); MSG_ReadData( &oldMsg, workFrame->areamask, workFrame->areaUsed ); if (clientNum <0 || clientNum >= MAX_CLIENTS) { Com_Printf("Got snapshot with invalid client.\n"); goto conversionerror; } MSG_ReadDeltaPlayerstate( &oldMsg, oldSnap ? &oldSnap->ps : &demoNullPlayerState, &newSnap->ps ); /* Read the individual entities */ newSnap->parseEntitiesNum = parseEntitiesNum; newSnap->numEntities = 0; Com_Memset( workFrame->entityData, 0, sizeof( workFrame->entityData )); /* The beast that is entity parsing */ { int newnum; entityState_t *oldstate, *newstate; int oldindex = 0; int oldnum; newnum = MSG_ReadBits( &oldMsg, GENTITYNUM_BITS ); while ( 1 ) { // read the entity index number if (oldSnap && oldindex < oldSnap->numEntities) { oldstate = &convert->parseEntities[(oldSnap->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } else { oldstate = 0; oldnum = 99999; } newstate = &convert->parseEntities[parseEntitiesNum]; if ( !oldstate && (newnum == (MAX_GENTITIES-1))) { break; } else if ( oldnum < newnum ) { *newstate = *oldstate; oldindex++; } else if (oldnum == newnum) { oldindex++; MSG_ReadDeltaEntity( &oldMsg, oldstate, newstate, newnum ); if ( newstate->number != MAX_GENTITIES-1) workFrame->entityData[ newstate->number ] = 1; newnum = MSG_ReadBits( &oldMsg, GENTITYNUM_BITS ); } else if (oldnum > newnum) { MSG_ReadDeltaEntity( &oldMsg, &convert->entityBaselines[newnum], newstate , newnum ); if ( newstate->number != MAX_GENTITIES-1) workFrame->entityData[ newstate->number ] = 1; newnum = MSG_ReadBits( &oldMsg, GENTITYNUM_BITS ); } if (newstate->number == MAX_GENTITIES-1) continue; parseEntitiesNum++; parseEntitiesNum &= (MAX_PARSE_ENTITIES-1); newSnap->numEntities++; }} /* Stop processing this further since it's an invalid snap due to lack of delta data */ if (!newSnap->valid) break; /* Skipped snapshots will be set invalid in the circular buffer */ if ( newSnap->messageNum - convert->lastMessageNum >= PACKET_BACKUP ) { convert->lastMessageNum = newSnap->messageNum - ( PACKET_BACKUP - 1 ); } for ( ; convert->lastMessageNum < newSnap->messageNum ; convert->lastMessageNum++ ) { convert->snapshots[convert->lastMessageNum & PACKET_MASK].valid = qfalse; } convert->lastMessageNum = newSnap->messageNum + 1; /* compress the frame into the new format */ if (nextTime > oldTime) { demoFrame_t *cleanFrame; int writeIndex; for (temp = 0;temp<newSnap->numEntities;temp++) { int p = (newSnap->parseEntitiesNum+temp) & (MAX_PARSE_ENTITIES-1); entityState_t *newState = &convert->parseEntities[p]; workFrame->entities[newState->number] = *newState; } workFrame->clientData[clientNum] = 1; workFrame->clients[clientNum] = newSnap->ps; workFrame->serverTime = nextTime; /* Which frame from the cache to save */ writeIndex = convert->frameIndex - (DEMOCONVERTFRAMES/2); if (writeIndex >= 0) { const demoFrame_t *newFrame; msg_t writeMsg; // init the message MSG_Init( &writeMsg, demoBuffer, sizeof (demoBuffer)); MSG_Clear( &writeMsg ); MSG_Bitstream( &writeMsg ); newFrame = &convert->frames[ writeIndex % DEMOCONVERTFRAMES]; if ( smoothen ) demoFrameInterpolate( convert->frames, DEMOCONVERTFRAMES, writeIndex ); if ( nextTime > fullTime || writeIndex <= 0 ) { /* Plan the next time for a full write */ fullTime = nextTime + 2000; demoFramePack( &writeMsg, newFrame, 0 ); } else { const demoFrame_t *oldFrame = &convert->frames[ ( writeIndex -1 ) % DEMOCONVERTFRAMES]; demoFramePack( &writeMsg, newFrame, oldFrame ); } /* Write away the new data in the msg queue */ temp = LittleLong( writeMsg.cursize ); FS_Write (&temp, 4, newHandle ); FS_Write ( writeMsg.data , writeMsg.cursize, newHandle ); } /* Clean up the upcoming frame for all new changes */ convert->frameIndex++; cleanFrame = &convert->frames[ convert->frameIndex % DEMOCONVERTFRAMES]; cleanFrame->serverTime = 0; for (temp = 0;temp<MAX_GENTITIES;temp++) cleanFrame->entities[temp].number = MAX_GENTITIES-1; Com_Memset( cleanFrame->clientData, 0, sizeof ( cleanFrame->clientData )); Com_Memcpy( cleanFrame->string.data, workFrame->string.data, workFrame->string.used ); Com_Memcpy( cleanFrame->string.offsets, workFrame->string.offsets, sizeof( workFrame->string.offsets )); cleanFrame->string.used = workFrame->string.used; cleanFrame->commandUsed = 0; /* keep track of this last frame's time */ oldTime = nextTime; } break; case svc_download: // read block number temp = MSG_ReadShort ( &oldMsg ); if (!temp) //0 block, read file size MSG_ReadLong( &oldMsg ); // read block size temp = MSG_ReadShort ( &oldMsg ); // read the data block for ( ;temp>0;temp--) MSG_ReadByte( &oldMsg ); break; } } } conversionerror: FS_FCloseFile( oldHandle ); FS_FCloseFile( newHandle ); Z_Free( convert ); return; }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; if ( cl_shownet->integer == 1 ) { Com_Printf ("%i ",msg->cursize); } else if ( cl_shownet->integer >= 2 ) { Com_Printf ("------------------\n"); } MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong( msg ); // if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { clc.reliableAcknowledge = clc.reliableSequence; } // // parse the message // while ( 1 ) { if ( msg->readcount > msg->cursize ) { Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); break; } cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF) { SHOWNET( msg, "END OF MESSAGE" ); break; } if ( cl_shownet->integer >= 2 ) { if ( !svc_strings[cmd] ) { Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); } else { SHOWNET( msg, svc_strings[cmd] ); } } // other commands switch ( cmd ) { default: Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); break; case svc_nop: break; case svc_serverCommand: CL_ParseCommandString( msg ); break; case svc_gamestate: CL_ParseGamestate( msg ); break; case svc_snapshot: CL_ParseSnapshot( msg ); break; case svc_download: CL_ParseDownload( msg ); break; case svc_lua: //Make Lua VMCall Here. -Hxrmn CL_SetLuaMessage(msg); VM_Call (cgvm, CG_LUA_MSG); break; } } }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; // msg_t msgback; // msgback = *msg; if ( cl_shownet->integer == 1 ) { Com_Printf("%i ", msg->cursize ); } else if ( cl_shownet->integer >= 2 ) { Com_Printf( "------------------\n" ); } MSG_Bitstream( msg ); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong( msg ); // if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { clc.reliableAcknowledge = clc.reliableSequence; } // // parse the message // while ( 1 ) { if ( msg->readcount > msg->cursize ) { Com_Error( ERR_DROP, "CL_ParseServerMessage: read past end of server message" ); } cmd = MSG_ReadByte( msg ); // See if this is an extension command after the EOF, which means we // got data that a legacy client should ignore. if ( ( cmd == svc_EOF ) && ( MSG_LookaheadByte( msg ) == svc_extension ) ) { SHOWNET( msg, "EXTENSION" ); MSG_ReadByte( msg ); // throw the svc_extension byte away. cmd = MSG_ReadByte( msg ); // something legacy clients can't do! // sometimes you get a svc_extension at end of stream...dangling // bits in the huffman decoder giving a bogus value? if ( cmd == -1 ) { cmd = svc_EOF; } } if ( cmd == svc_EOF ) { SHOWNET( msg, "END OF MESSAGE" ); break; } if ( cl_shownet->integer >= 2 ) { if ( !svc_strings[ cmd ] ) { Com_Printf( "%3i:BAD CMD %i\n", msg->readcount - 1, cmd ); } else { SHOWNET( msg, svc_strings[ cmd ] ); } } // other commands switch ( cmd ) { default: Com_Error( ERR_DROP, "CL_ParseServerMessage: Illegible server message %d", cmd ); case svc_nop: break; case svc_serverCommand: CL_ParseCommandString( msg ); break; case svc_gamestate: CL_ParseGamestate( msg ); break; case svc_snapshot: CL_ParseSnapshot( msg ); break; case svc_download: CL_ParseDownload( msg ); break; case svc_voip: #ifdef USE_VOIP CL_ParseVoip( msg ); #endif break; } } }
void demoCutWriteDeltaSnapshot(int firstServerCommand, fileHandle_t f, qboolean forceNonDelta, clientConnection_t *clcCut, clientActive_t *clCut) { msg_t msgImpl, *msg = &msgImpl; byte msgData[MAX_MSGLEN]; clSnapshot_t *frame, *oldframe; int lastframe = 0; int snapFlags; MSG_Init(msg, msgData, sizeof(msgData)); MSG_Bitstream(msg); MSG_WriteLong(msg, clcCut->reliableSequence); // copy over any commands for (int serverCommand = firstServerCommand; serverCommand <= clcCut->serverCommandSequence; serverCommand++) { char *command = clcCut->serverCommands[serverCommand & (MAX_RELIABLE_COMMANDS - 1)]; MSG_WriteByte(msg, svc_serverCommand); MSG_WriteLong(msg, serverCommand/* + serverCommandOffset*/); MSG_WriteString(msg, command); } // this is the snapshot we are creating frame = &clCut->snap; if (clCut->snap.messageNum > 0 && !forceNonDelta) { lastframe = 1; oldframe = &clCut->snapshots[(clCut->snap.messageNum - 1) & PACKET_MASK]; // 1 frame previous if (!oldframe->valid) { // not yet set lastframe = 0; oldframe = NULL; } } else { lastframe = 0; oldframe = NULL; } MSG_WriteByte(msg, svc_snapshot); // send over the current server time so the client can drift // its view of time to try to match MSG_WriteLong(msg, frame->serverTime); // what we are delta'ing from MSG_WriteByte(msg, lastframe); snapFlags = frame->snapFlags; MSG_WriteByte(msg, snapFlags); // send over the areabits MSG_WriteByte(msg, sizeof(frame->areamask)); MSG_WriteData(msg, frame->areamask, sizeof(frame->areamask)); // delta encode the playerstate if (oldframe) { #ifdef _ONEBIT_COMBO MSG_WriteDeltaPlayerstate(msg, &oldframe->ps, &frame->ps, frame->pDeltaOneBit, frame->pDeltaNumBit); #else MSG_WriteDeltaPlayerstate(msg, &oldframe->ps, &frame->ps); #endif if (frame->ps.m_iVehicleNum) { //then write the vehicle's playerstate too if (!oldframe->ps.m_iVehicleNum) { //if last frame didn't have vehicle, then the old vps isn't gonna delta //properly (because our vps on the client could be anything) #ifdef _ONEBIT_COMBO MSG_WriteDeltaPlayerstate(msg, NULL, &frame->vps, NULL, NULL, qtrue); #else MSG_WriteDeltaPlayerstate(msg, NULL, &frame->vps, qtrue); #endif } else { #ifdef _ONEBIT_COMBO MSG_WriteDeltaPlayerstate(msg, &oldframe->vps, &frame->vps, frame->pDeltaOneBitVeh, frame->pDeltaNumBitVeh, qtrue); #else MSG_WriteDeltaPlayerstate(msg, &oldframe->vps, &frame->vps, qtrue); #endif } } } else { #ifdef _ONEBIT_COMBO MSG_WriteDeltaPlayerstate(msg, NULL, &frame->ps, NULL, NULL); #else MSG_WriteDeltaPlayerstate(msg, NULL, &frame->ps); #endif if (frame->ps.m_iVehicleNum) { //then write the vehicle's playerstate too #ifdef _ONEBIT_COMBO MSG_WriteDeltaPlayerstate(msg, NULL, &frame->vps, NULL, NULL, qtrue); #else MSG_WriteDeltaPlayerstate(msg, NULL, &frame->vps, qtrue); #endif } } // delta encode the entities demoCutEmitPacketEntities(oldframe, frame, msg, clCut); MSG_WriteByte(msg, svc_EOF); demoCutWriteDemoMessage(msg, f, clcCut); }
/* Start a server-side demo. This does it all, create the file and adjust the demo-related stuff in client_t. This is mostly ripped from sv_client.c/SV_SendClientGameState and cl_main.c/CL_Record_f. */ static void SVD_StartDemoFile(client_t *client, const char *path) { int i, len; entityState_t *base, nullstate; msg_t msg; byte buffer[MAX_MSGLEN]; fileHandle_t file; Com_DPrintf("SVD_StartDemoFile\n"); assert(!client->demo_recording); // create the demo file and write the necessary header file = FS_FOpenFileWrite(path); assert(file != 0); MSG_Init(&msg, buffer, sizeof(buffer)); MSG_Bitstream(&msg); // XXX server code doesn't do this, client code does MSG_WriteLong(&msg, client->lastClientCommand); // TODO: or is it client->reliableSequence? MSG_WriteByte(&msg, svc_gamestate); MSG_WriteLong(&msg, client->reliableSequence); for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (sv.configstrings[i][0]) { MSG_WriteByte(&msg, svc_configstring); MSG_WriteShort(&msg, i); MSG_WriteBigString(&msg, sv.configstrings[i]); } } Com_Memset(&nullstate, 0, sizeof(nullstate)); for (i = 0 ; i < MAX_GENTITIES; i++) { base = &sv.svEntities[i].baseline; if (!base->number) { continue; } MSG_WriteByte(&msg, svc_baseline); MSG_WriteDeltaEntity(&msg, &nullstate, base, qtrue); } MSG_WriteByte(&msg, svc_EOF); MSG_WriteLong(&msg, client - svs.clients); MSG_WriteLong(&msg, sv.checksumFeed); MSG_WriteByte(&msg, svc_EOF); // XXX server code doesn't do this, SV_Netchan_Transmit adds it! len = LittleLong(client->netchan.outgoingSequence-1); FS_Write(&len, 4, file); len = LittleLong (msg.cursize); FS_Write(&len, 4, file); FS_Write(msg.data, msg.cursize, file); FS_Flush(file); // adjust client_t to reflect demo started client->demo_recording = qtrue; client->demo_file = file; client->demo_waiting = qtrue; client->demo_backoff = 1; client->demo_deltas = 0; }
void demoCutWriteDemoHeader(fileHandle_t f, clientConnection_t *clcCut, clientActive_t *clCut) { byte bufData[MAX_MSGLEN]; msg_t buf; int i; int len; entityState_t *ent; entityState_t nullstate; char *s; // write out the gamestate message MSG_Init(&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE, MRE: all server->client messages now acknowledge MSG_WriteLong(&buf, clcCut->reliableSequence); MSG_WriteByte(&buf, svc_gamestate); MSG_WriteLong(&buf, clcCut->serverCommandSequence); // configstrings for (i = 0; i < MAX_CONFIGSTRINGS; i++) { if (!clCut->gameState.stringOffsets[i]) { continue; } s = clCut->gameState.stringData + clCut->gameState.stringOffsets[i]; MSG_WriteByte(&buf, svc_configstring); MSG_WriteShort(&buf, i); MSG_WriteBigString(&buf, s); } // baselines Com_Memset(&nullstate, 0, sizeof(nullstate)); for (i = 0; i < MAX_GENTITIES ; i++) { ent = &clCut->entityBaselines[i]; if ( !ent->number ) { continue; } MSG_WriteByte(&buf, svc_baseline); MSG_WriteDeltaEntity(&buf, &nullstate, ent, qtrue); } MSG_WriteByte(&buf, svc_EOF); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clcCut->clientNum); // write the checksum feed MSG_WriteLong(&buf, clcCut->checksumFeed); // RMG stuff if ( clcCut->rmgHeightMapSize ) { // Height map MSG_WriteShort(&buf, (unsigned short)clcCut->rmgHeightMapSize); MSG_WriteBits(&buf, 0, 1 ); MSG_WriteData(&buf, clcCut->rmgHeightMap, clcCut->rmgHeightMapSize); // Flatten map MSG_WriteShort(&buf, (unsigned short)clcCut->rmgHeightMapSize); MSG_WriteBits(&buf, 0, 1 ); MSG_WriteData(&buf, clcCut->rmgFlattenMap, clcCut->rmgHeightMapSize); // Seed MSG_WriteLong (&buf, clcCut->rmgSeed); // Automap symbols MSG_WriteShort (&buf, (unsigned short)clcCut->rmgAutomapSymbolCount); for (i = 0; i < clcCut->rmgAutomapSymbolCount; i ++) { MSG_WriteByte(&buf, (unsigned char)clcCut->rmgAutomapSymbols[i].mType); MSG_WriteByte(&buf, (unsigned char)clcCut->rmgAutomapSymbols[i].mSide); MSG_WriteLong(&buf, (long)clcCut->rmgAutomapSymbols[i].mOrigin[0]); MSG_WriteLong(&buf, (long)clcCut->rmgAutomapSymbols[i].mOrigin[1]); } } else { MSG_WriteShort (&buf, 0); } // finished writing the client packet MSG_WriteByte(&buf, svc_EOF); // write it to the demo file len = LittleLong(clcCut->serverMessageSequence - 1); FS_Write(&len, 4, f); len = LittleLong(buf.cursize); FS_Write(&len, 4, f); FS_Write(buf.data, buf.cursize, f); }
/* ==================== CL_Record_f record <slot> [<demoname>] Begins recording a demo from the current position ==================== */ void CL_Record( client_t *cl, char *s ) { char name[MAX_OSPATH]; char name_zip[MAX_OSPATH]; byte bufData[MAX_MSGLEN]; msg_t buf; int i; int len; entityState_t *ent; entityState_t nullstate; int clientnum; char *guid; char prefix[MAX_OSPATH]; clientnum = cl - svs.clients; if ( cl->demorecording ) { Com_Printf ("Already recording client %i.\n", clientnum); return; } // if ( cl->state != CA_ACTIVE ) { // Com_Printf ("You must be in a level to record.\n"); // return; // } // // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. // if ( NET_IsLocalAddress( cl->serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) { // Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); // } if ( s ) { Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", s, PROTOCOL_VERSION ); } else { int number,n,a,b,c,d; guid = Info_ValueForKey(cl->userinfo, "cl_guid"); if (!Q_stricmp(guid, "")) { guid = "LONGGONE"; } Q_strncpyz(prefix, guid, 9); // scan for a free demo name for ( number = 0 ; number <= 9999 ; number++ ) { if(number < 0 || number > 9999) number = 9999; n = number; a = n / 1000; n -= a*1000; b = n / 100; n -= b*100; c = n / 10; n -= c*10; d = n; Com_sprintf (name, sizeof(name), "demos/%s_%s_%i%i%i%i.dm_%d", prefix, sv_mapname->string, a, b, c, d, PROTOCOL_VERSION ); Com_sprintf (name_zip, sizeof(name_zip), "demos/%s_%s_%i%i%i%i.dm_%d.zip", prefix, sv_mapname->string, a, b, c, d, PROTOCOL_VERSION ); Q_strlwr(name); Q_strlwr(name_zip); if (!FS_FileExists(name) && !FS_FileExists(name_zip)) { break; // file doesn't exist } } } // open the demo file if (!sv_autorecord->integer) { Com_Printf ("recording client %i to %s.\n", clientnum, name); } else { Com_Printf ("Record: %i: %s\n", clientnum, name); } cl->demofile = FS_FOpenFileWrite( name ); if ( !cl->demofile ) { Com_Printf ("ERROR: couldn't open.\n"); return; } // don't start saving messages until a non-delta compressed message is received cl->demowaiting = qtrue; cl->savedemo = qfalse; // demo will not be saved if sv_autorecord 1 and cl's score is too low Q_strncpyz( cl->demoName, name, sizeof( cl->demoName ) ); // write out the gamestate message MSG_Init (&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE, MRE: all server->client messages now acknowledge MSG_WriteLong( &buf, cl->lastClientCommand );// 0007 - 000A MSG_WriteByte (&buf, svc_gamestate);// 000B MSG_WriteLong (&buf, cl->reliableSequence );// 000C - 000F // write the configstrings for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { if (sv.configstrings[i][0]) { MSG_WriteByte( &buf, svc_configstring ); MSG_WriteShort( &buf, i ); MSG_WriteBigString( &buf, sv.configstrings[i] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( i = 0 ; i < MAX_GENTITIES; i++ ) { ent = &sv.svEntities[i].baseline; if ( !ent->number ) { continue; } MSG_WriteByte( &buf, svc_baseline ); MSG_WriteDeltaEntity( &buf, &nullstate, ent, qtrue ); } MSG_WriteByte( &buf, svc_EOF ); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clientnum); // write the checksum feed MSG_WriteLong(&buf, sv.checksumFeed); // finished writing the client packet MSG_WriteByte( &buf, svc_EOF ); // write it to the demo file len = LittleLong( cl->netchan.outgoingSequence-1 ); FS_Write (&len, 4, cl->demofile);// 0000 - 0003 len = LittleLong (buf.cursize); FS_Write (&len, 4, cl->demofile);// 0004 - 0007 FS_Write (buf.data, buf.cursize, cl->demofile);// 0007 - ... // the rest of the demo file will be copied from net messages cl->demorecording = qtrue; }
/* =================== SV_ExecuteClientMessage Parse a client packet =================== */ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { int c; int serverId; MSG_Bitstream(msg); serverId = MSG_ReadLong( msg ); cl->messageAcknowledge = MSG_ReadLong( msg ); if (cl->messageAcknowledge < 0) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG SV_DropClient( cl, "DEBUG: illegible client message" ); #endif return; } cl->reliableAcknowledge = MSG_ReadLong( msg ); // NOTE: when the client message is fux0red the acknowledgement numbers // can be out of range, this could cause the server to send thousands of server // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG SV_DropClient( cl, "DEBUG: illegible client message" ); #endif cl->reliableAcknowledge = cl->reliableSequence; return; } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate // // if the client was downloading, let it stay at whatever serverId and // gamestate it was at. This allows it to keep downloading even when // the gamestate changes. After the download is finished, we'll // notice and send it a new game state // // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" // but we still need to read the next message to move to next download or send gamestate // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart // they just haven't caught the map_restart yet Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); return; } // if we can tell that the client has dropped the last // gamestate we sent them, resend it if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); SV_SendClientGameState( cl ); } return; } // this client has acknowledged the new gamestate so it's // safe to start sending it the real time again if( cl->oldServerTime && serverId == sv.serverId ){ Com_DPrintf( "%s acknowledged gamestate\n", cl->name ); cl->oldServerTime = 0; } // read optional clientCommand strings do { c = MSG_ReadByte( msg ); // See if this is an extension command after the EOF, which means we // got data that a legacy server should ignore. if ((c == clc_EOF) && (MSG_LookaheadByte( msg ) == clc_extension)) { MSG_ReadByte( msg ); // throw the clc_extension byte away. c = MSG_ReadByte( msg ); // something legacy servers can't do! // sometimes you get a clc_extension at end of stream...dangling // bits in the huffman decoder giving a bogus value? if (c == -1) { c = clc_EOF; } } if ( c == clc_EOF ) { break; } if ( c != clc_clientCommand ) { break; } if ( !SV_ClientCommand( cl, msg ) ) { return; // we couldn't execute it because of the flood protection } if (cl->state == CS_ZOMBIE) { return; // disconnect command } } while ( 1 ); // read the usercmd_t if ( c == clc_move ) { SV_UserMove( cl, msg, qtrue ); } else if ( c == clc_moveNoDelta ) { SV_UserMove( cl, msg, qfalse ); } else if ( c == clc_voip ) { #ifdef USE_VOIP SV_UserVoip( cl, msg ); #endif } else if ( c != clc_EOF ) { Com_Printf( "WARNING: bad command byte for client %i\n", (int) (cl - svs.clients) ); } // if ( msg->readcount != msg->cursize ) { // Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); // } }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds During normal gameplay, a client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence <optional reliable commands> 1 clc_move or clc_moveNoDelta 1 command count <count * usercmds> =================== */ void CL_WritePacket( void ) { msg_t buf; byte data[MAX_MSGLEN]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count, key; // don't send anything if playing back a demo if ( clc.demoplaying || clc.state == CA_CINEMATIC ) { return; } Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; MSG_Init( &buf, data, sizeof(data) ); MSG_Bitstream( &buf ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong( &buf, cl.serverId ); // write the last message we received, which can // be used for delta compression, and is also used // to tell if we dropped a gamestate MSG_WriteLong( &buf, clc.serverMessageSequence ); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write any unacknowledged clientCommands for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf("MAX_PACKET_USERCMDS\n"); } #ifdef USE_VOIP if (clc.voipOutgoingDataSize > 0) { if((clc.voipFlags & VOIP_SPATIAL) || Com_IsVoipTarget(clc.voipTargets, sizeof(clc.voipTargets), -1)) { MSG_WriteByte (&buf, clc_voip); MSG_WriteByte (&buf, clc.voipOutgoingGeneration); MSG_WriteLong (&buf, clc.voipOutgoingSequence); MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); MSG_WriteData (&buf, clc.voipTargets, sizeof(clc.voipTargets)); MSG_WriteByte(&buf, clc.voipFlags); MSG_WriteShort (&buf, clc.voipOutgoingDataSize); MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); // If we're recording a demo, we have to fake a server packet with // this VoIP data so it gets to disk; the server doesn't send it // back to us, and we might as well eliminate concerns about dropped // and misordered packets here. if(clc.demorecording && !clc.demowaiting) { const int voipSize = clc.voipOutgoingDataSize; msg_t fakemsg; byte fakedata[MAX_MSGLEN]; MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); MSG_Bitstream (&fakemsg); MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); MSG_WriteByte (&fakemsg, svc_voip); MSG_WriteShort (&fakemsg, clc.clientNum); MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames); MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize ); MSG_WriteBits (&fakemsg, clc.voipFlags, VOIP_FLAGCNT); MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize); MSG_WriteByte (&fakemsg, svc_EOF); CL_WriteDemoMessage (&fakemsg, 0); } clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } else { // We have data, but no targets. Silently discard all data clc.voipOutgoingDataSize = 0; clc.voipOutgoingDataFrames = 0; } } #endif if ( count >= 1 ) { if ( cl_showSend->integer ) { Com_Printf( "(%i)", count ); } // begin a client move command if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { MSG_WriteByte (&buf, clc_moveNoDelta); } else { MSG_WriteByte (&buf, clc_move); } // write the command count MSG_WriteByte( &buf, count ); // use the checksum feed in the key key = clc.checksumFeed; // also use the message acknowledge key ^= clc.serverMessageSequence; // also use the last acknowledged server command in the key key ^= MSG_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); // write all the commands, including the predicted command for ( i = 0 ; i < count ; i++ ) { j = (cl.cmdNumber - count + i + 1) & CMD_MASK; cmd = &cl.cmds[j]; MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); oldcmd = cmd; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.outPackets[ packetNum ].p_realtime = cls.realtime; cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; if ( cl_showSend->integer ) { Com_Printf( "%i ", buf.cursize ); } CL_Netchan_Transmit (&clc.netchan, &buf); }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; // msg_t msgback; // msgback = *msg; if ( cl_shownet->integer == 1 ) { Log::Notice("%i ", msg->cursize ); } else if ( cl_shownet->integer >= 2 ) { Log::Notice( "------------------\n" ); } MSG_Bitstream( msg ); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong( msg ); // if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { clc.reliableAcknowledge = clc.reliableSequence; } // // parse the message // while ( 1 ) { if ( msg->readcount > msg->cursize ) { Com_Error( errorParm_t::ERR_DROP, "CL_ParseServerMessage: read past end of server message" ); } cmd = MSG_ReadByte( msg ); if ( cmd < 0 || cmd == svc_EOF ) { SHOWNET( msg, "END OF MESSAGE" ); break; } if ( cl_shownet->integer >= 2 ) { if ( !svc_strings[ cmd ] ) { Log::Notice( "%3i:BAD CMD %i\n", msg->readcount - 1, cmd ); } else { SHOWNET( msg, svc_strings[ cmd ] ); } } // other commands switch ( cmd ) { default: Com_Error( errorParm_t::ERR_DROP, "CL_ParseServerMessage: Illegible server message %d", cmd ); case svc_nop: break; case svc_serverCommand: CL_ParseCommandString( msg ); break; case svc_gamestate: CL_ParseGamestate( msg ); break; case svc_snapshot: CL_ParseSnapshot( msg ); break; case svc_download: CL_ParseDownload( msg ); break; } } }