/* =============== Netchan_Transmit Sends a message to a connection, fragmenting if necessary A 0 length will still generate a packet. ================ */ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { msg_t send; byte send_buf[MAX_PACKETLEN]; if ( length > MAX_MSGLEN ) { Com_Error( ERR_DROP, "Netchan_Transmit: length = %i", length ); } chan->unsentFragmentStart = 0; // fragment large reliable messages if ( length >= FRAGMENT_SIZE ) { chan->unsentFragments = qtrue; chan->unsentLength = length; Com_Memcpy( chan->unsentBuffer, data, length ); // only send the first fragment now Netchan_TransmitNextFragment( chan ); return; } // write the packet header MSG_InitOOB (&send, send_buf, sizeof(send_buf)); MSG_WriteLong( &send, chan->outgoingSequence ); chan->outgoingSequence++; // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } MSG_WriteData( &send, data, length ); // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); if ( showpackets->integer ) { Com_Printf( "%s send %4i : s=%i ack=%i\n" , netsrcString[ chan->sock ] , send.cursize , chan->outgoingSequence - 1 , chan->incomingSequence ); } }
/* ======================================================================================================================================= SV_WriteBinaryMessage ======================================================================================================================================= */ static void SV_WriteBinaryMessage(msg_t *msg, client_t *cl) { if (!cl->binaryMessageLength) { return; } MSG_Uncompressed(msg); if ((msg->cursize + cl->binaryMessageLength) >= msg->maxsize) { cl->binaryMessageOverflowed = qtrue; return; } MSG_WriteData(msg, cl->binaryMessage, cl->binaryMessageLength); cl->binaryMessageLength = 0; cl->binaryMessageOverflowed = qfalse; }
void HL2Rcon_SourceRconSendDataToEachClient( const byte* data, int msglen, int type){ rconUser_t* user; int i; msg_t msg; int32_t *updatelen; byte sourcemsgbuf[MAX_MSGLEN]; qboolean msgbuild = qfalse; for(i = 0, user = sourceRcon.activeRconUsers; i < MAX_RCONUSERS; i++, user++ ){ if(!user->streamgamelog && type == SERVERDATA_GAMELOG) continue; if(!user->streamlog && type == SERVERDATA_CONLOG) continue; if(!user->streamevents && type == SERVERDATA_EVENT) continue; if(!msgbuild){ MSG_Init(&msg, sourcemsgbuf, sizeof(sourcemsgbuf)); MSG_WriteLong(&msg, 0); //writing 0 for now MSG_WriteLong(&msg, 0); MSG_WriteLong(&msg, type); if(type == SERVERDATA_EVENT) MSG_WriteData(&msg, data, msglen); else MSG_WriteBigString(&msg, (char*)data); MSG_WriteByte(&msg, 0); //Adjust the length updatelen = (int32_t*)msg.data; *updatelen = msg.cursize - 4; msgbuild = qtrue; } NET_SendData(user->remote.sock, &msg); } }
/* ================== SV_WriteVoipToClient Check to see if there is any VoIP queued for a client, and send if there is. ================== */ void SV_WriteVoipToClient( client_t *cl, msg_t *msg ) { voipServerPacket_t *packet = &cl->voipPacket[0]; int totalbytes = 0; int i; if (*cl->downloadName) { cl->queuedVoipPackets = 0; return; // no VoIP allowed if download is going, to save bandwidth. } // Write as many VoIP packets as we reasonably can... for (i = 0; i < cl->queuedVoipPackets; i++, packet++) { totalbytes += packet->len; if (totalbytes > MAX_DOWNLOAD_BLKSIZE) break; // You have to start with a svc_EOF, so legacy clients drop the // rest of this packet. Otherwise, those without VoIP support will // see the svc_voip command, then panic and disconnect. // Generally we don't send VoIP packets to legacy clients, but this // serves as both a safety measure and a means to keep demo files // compatible. MSG_WriteByte( msg, svc_EOF ); MSG_WriteByte( msg, svc_extension ); MSG_WriteByte( msg, svc_voip ); MSG_WriteShort( msg, packet->sender ); MSG_WriteByte( msg, (byte) packet->generation ); MSG_WriteLong( msg, packet->sequence ); MSG_WriteByte( msg, packet->frames ); MSG_WriteShort( msg, packet->len ); MSG_WriteData( msg, packet->data, packet->len ); } // !!! FIXME: I hate this queue system. cl->queuedVoipPackets -= i; if (cl->queuedVoipPackets > 0) { memmove( &cl->voipPacket[0], &cl->voipPacket[i], sizeof (voipServerPacket_t) * i); } }
static void CL_SendUserinfo( void ) { char userinfo[MAX_INFO_STRING]; cvar_t *var; int i; if( !cls.userinfo_modified ) { return; } if( cls.userinfo_modified == MAX_PACKET_USERINFOS ) { size_t len = Cvar_BitInfo( userinfo, CVAR_USERINFO ); Com_DDPrintf( "%s: %u: full update\n", __func__, com_framenum ); MSG_WriteByte( clc_userinfo ); MSG_WriteData( userinfo, len + 1 ); MSG_FlushTo( &cls.netchan->message ); } else if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) { Com_DDPrintf( "%s: %u: %d updates\n", __func__, com_framenum, cls.userinfo_modified ); for( i = 0; i < cls.userinfo_modified; i++ ) { var = cls.userinfo_updates[i]; MSG_WriteByte( clc_userinfo_delta ); MSG_WriteString( var->name ); if( var->flags & CVAR_USERINFO ) { MSG_WriteString( var->string ); } else { // no longer in userinfo MSG_WriteString( NULL ); } } MSG_FlushTo( &cls.netchan->message ); } else { Com_WPrintf( "%s: update count is %d, should never happen.\n", __func__, cls.userinfo_modified ); } cls.userinfo_modified = 0; }
/* ================== SV_WriteVoipToClient Check to see if there is any VoIP queued for a client, and send if there is. ================== */ static void SV_WriteVoipToClient( client_t* cl, msg_s* msg ) { int totalbytes = 0; int i; voipServerPacket_t* packet; if ( cl->queuedVoipPackets ) { // Write as many VoIP packets as we reasonably can... for ( i = 0; i < cl->queuedVoipPackets; i++ ) { packet = cl->voipPacket[( i + cl->queuedVoipIndex ) % ARRAY_LEN( cl->voipPacket )]; if ( !*cl->downloadName ) { totalbytes += packet->len; if ( totalbytes > ( msg->maxsize - msg->cursize ) / 2 ) break; MSG_WriteByte( msg, svc_voip ); MSG_WriteShort( msg, packet->sender ); MSG_WriteByte( msg, ( byte ) packet->generation ); MSG_WriteLong( msg, packet->sequence ); MSG_WriteByte( msg, packet->frames ); MSG_WriteShort( msg, packet->len ); MSG_WriteBits( msg, packet->flags, VOIP_FLAGCNT ); MSG_WriteData( msg, packet->data, packet->len ); } free( packet ); } cl->queuedVoipPackets -= i; cl->queuedVoipIndex += i; cl->queuedVoipIndex %= ARRAY_LEN( cl->voipPacket ); } }
/* ================== SV_WriteDownloadToClient Check to see if the client wants a file, open it if needed and start pumping the client Fill up msg with data ================== */ void SV_WriteDownloadToClient(client_t *cl, msg_t *msg) { int curindex; int rate; int blockspersnap; int unreferenced = 1; char errorMessage[1024]; char pakbuf[MAX_QPATH], *pakptr; int numRefPaks; if (!*cl->downloadName) return; // Nothing being downloaded if(!cl->download) { qboolean idPack = qfalse; #ifndef STANDALONE qboolean missionPack = qfalse; #endif // Chop off filename extension. Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); pakptr = strrchr(pakbuf, '.'); if(pakptr) { *pakptr = '\0'; // Check for pk3 filename extension if(!Q_stricmp(pakptr + 1, "pk3")) { const char *referencedPaks = FS_ReferencedPakNames(); // Check whether the file appears in the list of referenced // paks to prevent downloading of arbitrary files. Cmd_TokenizeStringIgnoreQuotes(referencedPaks); numRefPaks = Cmd_Argc(); for(curindex = 0; curindex < numRefPaks; curindex++) { if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf)) { unreferenced = 0; // now that we know the file is referenced, // check whether it's legal to download it. missionPack = FS_idPak(pakbuf, "missionpack"); idPack = missionPack; idPack = (qboolean)(idPack || FS_idPak(pakbuf, "base")); break; } } } } cl->download = 0; // We open the file here if ( !sv_allowDownload->integer || idPack || unreferenced || ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { // cannot auto-download file if(unreferenced) { Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", (int) (cl - svs.clients), cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName); } else if (idPack) { Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", (int) (cl - svs.clients), cl->downloadName); if(missionPack) { Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" "The Team Arena mission pack can be found in your local game store.", cl->downloadName); } else { Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); } } else if ( !sv_allowDownload->integer ) { Com_Printf("clientDownload: %d : \"%s\" download disabled\n", (int) (cl - svs.clients), cl->downloadName); if (sv_pure->integer) { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "You will need to get this file elsewhere before you " "can connect to this pure server.\n", cl->downloadName); } else { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "The server you are connecting to is not a pure server, " "set autodownload to No in your settings and you might be " "able to join the game anyway.\n", cl->downloadName); } } else { // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? // if the pk3 is referenced, it must have been found somewhere in the filesystem Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", (int) (cl - svs.clients), cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); } MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, 0 ); // client is expecting block zero MSG_WriteLong( msg, -1 ); // illegal file size MSG_WriteString( msg, errorMessage ); *cl->downloadName = 0; if(cl->download) FS_FCloseFile(cl->download); return; } Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); // Init cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; cl->downloadCount = 0; cl->downloadEOF = qfalse; } // Perform any reads that we need to while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && cl->downloadSize != cl->downloadCount) { curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); if (!cl->downloadBlocks[curindex]) cl->downloadBlocks[curindex] = (unsigned char *)Z_Malloc( MAX_DOWNLOAD_BLKSIZE, TAG_DOWNLOAD, qtrue ); cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); if (cl->downloadBlockSize[curindex] < 0) { // EOF right now cl->downloadCount = cl->downloadSize; break; } cl->downloadCount += cl->downloadBlockSize[curindex]; // Load in next block cl->downloadCurrentBlock++; } // Check to see if we have eof condition and add the EOF block if (cl->downloadCount == cl->downloadSize && !cl->downloadEOF && cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; cl->downloadCurrentBlock++; cl->downloadEOF = qtrue; // We have added the EOF block } // Loop up to window size times based on how many blocks we can fit in the // client snapMsec and rate // based on the rate, how many bytes can we fit in the snapMsec time of the client // normal rate / snapshotMsec calculation rate = cl->rate; if ( sv_maxRate->integer ) { if ( sv_maxRate->integer < 1000 ) { Cvar_Set( "sv_MaxRate", "1000" ); } if ( sv_maxRate->integer < rate ) { rate = sv_maxRate->integer; } } if (!rate) { blockspersnap = 1; } else { blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / MAX_DOWNLOAD_BLKSIZE; } if (blockspersnap < 0) blockspersnap = 1; while (blockspersnap--) { // Write out the next section of the file, if we have already reached our window, // automatically start retransmitting if (cl->downloadClientBlock == cl->downloadCurrentBlock) return; // Nothing to transmit if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { // We have transmitted the complete window, should we start resending? //FIXME: This uses a hardcoded one second timeout for lost blocks //the timeout should be based on client rate somehow if (svs.time - cl->downloadSendTime > 1000) cl->downloadXmitBlock = cl->downloadClientBlock; else return; } // Send current block curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, cl->downloadXmitBlock ); // block zero is special, contains file size if ( cl->downloadXmitBlock == 0 ) MSG_WriteLong( msg, cl->downloadSize ); MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); // Write the block if ( cl->downloadBlockSize[curindex] ) { MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); } Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock ); // Move on to the next block // It will get sent with next snap shot. The rate will keep us in line. cl->downloadXmitBlock++; cl->downloadSendTime = svs.time; } }
/* ================ SV_SendClientGameState Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each new map load. It will be resent if the client acknowledges a later message but has the wrong gamestate. ================ */ void SV_SendClientGameState( client_t *client ) { int start; entityState_t *base, nullstate; msg_t msg; byte msgBuffer[MAX_MSGLEN]; // MW - my attempt to fix illegible server message errors caused by // packet fragmentation of initial snapshot. while(client->state&&client->netchan.unsentFragments) { // send additional message fragments if the last message // was too large to send at once Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); SV_Netchan_TransmitNextFragment(&client->netchan); } Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); client->state = CS_PRIMED; client->pureAuthentic = 0; // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit client->gamestateMessageNum = client->netchan.outgoingSequence; MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( &msg, client->lastClientCommand ); // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, &msg ); // send the gamestate MSG_WriteByte( &msg, svc_gamestate ); MSG_WriteLong( &msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( &msg, svc_configstring ); MSG_WriteShort( &msg, start ); MSG_WriteBigString( &msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].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); // write the checksum feed MSG_WriteLong( &msg, sv.checksumFeed); //rwwRMG - send info for the terrain if ( TheRandomMissionManager ) { z_stream zdata; // Send the height map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_MAX_COMPRESSION ); unsigned char heightmap[15000]; zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetHeightMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( &msg, 1, 1 ); MSG_WriteData ( &msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Send the flatten map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_MAX_COMPRESSION ); zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetFlattenMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( &msg, 1, 1 ); MSG_WriteData ( &msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Seed is needed for misc ents and noise MSG_WriteLong ( &msg, TheRandomMissionManager->GetLandScape()->get_rand_seed ( ) ); SV_WriteRMGAutomapSymbols ( &msg ); } else { MSG_WriteShort ( &msg, 0 ); } // deliver this to the client SV_SendMessageToClient( &msg, client ); }
/* =================== 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 ); } }
/* ================== SV_WriteSnapshotToClient ================== */ static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { clientSnapshot_t *frame, *oldframe; int lastframe; int i; int snapFlags; int deltaMessage; // this is the snapshot we are creating frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; // bots never acknowledge, but it doesn't matter since the only use case is for serverside demos // in which case we can delta against the very last message every time deltaMessage = client->deltaMessage; if ( client->demo.isBot ) { client->deltaMessage = client->netchan.outgoingSequence; } // try to use a previous frame as the source for delta compressing the snapshot if ( deltaMessage <= 0 || client->state != CS_ACTIVE ) { // client is asking for a retransmit oldframe = NULL; lastframe = 0; } else if ( client->netchan.outgoingSequence - deltaMessage >= (PACKET_BACKUP - 3) ) { // client hasn't gotten a good message through in a long time Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); oldframe = NULL; lastframe = 0; } else if ( client->demo.demorecording && client->demo.demowaiting ) { // demo is waiting for a non-delta-compressed frame for this client, so don't delta compress oldframe = NULL; lastframe = 0; } else if ( client->demo.minDeltaFrame > deltaMessage ) { // we saved a non-delta frame to the demo and sent it to the client, but the client didn't ack it // we can't delta against an old frame that's not in the demo without breaking the demo. so send // non-delta frames until the client acks. oldframe = NULL; lastframe = 0; } else { // we have a valid snapshot to delta from oldframe = &client->frames[ deltaMessage & PACKET_MASK ]; lastframe = client->netchan.outgoingSequence - deltaMessage; // the snapshot's entities may still have rolled off the buffer, though if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); oldframe = NULL; lastframe = 0; } } if ( oldframe == NULL ) { if ( client->demo.demowaiting ) { // this is a non-delta frame, so we can delta against it in the demo client->demo.minDeltaFrame = client->netchan.outgoingSequence; } client->demo.demowaiting = qfalse; } MSG_WriteByte (msg, svc_snapshot); // NOTE, MRE: now sent at the start of every message from server to client // let the client know which reliable clientCommands we have received //MSG_WriteLong( msg, client->lastClientCommand ); // send over the current server time so the client can drift // its view of time to try to match if( client->oldServerTime && !( client->demo.demorecording && client->demo.isBot ) ) { // The server has not yet got an acknowledgement of the // new gamestate from this client, so continue to send it // a time as if the server has not restarted. Note from // the client's perspective this time is strictly speaking // incorrect, but since it'll be busy loading a map at // the time it doesn't really matter. MSG_WriteLong (msg, sv.time + client->oldServerTime); } else { MSG_WriteLong (msg, sv.time); } // what we are delta'ing from MSG_WriteByte (msg, lastframe); snapFlags = svs.snapFlagServerBit; if ( client->rateDelayed ) { snapFlags |= SNAPFLAG_RATE_DELAYED; } if ( client->state != CS_ACTIVE ) { snapFlags |= SNAPFLAG_NOT_ACTIVE; } MSG_WriteByte (msg, snapFlags); // send over the areabits MSG_WriteByte (msg, frame->areabytes); MSG_WriteData (msg, frame->areabits, frame->areabytes); // 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 SV_EmitPacketEntities (oldframe, frame, msg); // padding for rate debugging if ( sv_padPackets->integer ) { for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { MSG_WriteByte (msg, svc_nop); } } }
/* ================== SV_WriteSnapshotToClient ================== */ static void SV_WriteSnapshotToClient(client_t *client, msg_t *msg) { clientSnapshot_t *frame, *oldframe; int lastframe; int snapFlags; // this is the snapshot we are creating frame = &client->frames[client->netchan.outgoingSequence & PACKET_MASK]; // try to use a previous frame as the source for delta compressing the snapshot if (client->deltaMessage <= 0 || client->state != CS_ACTIVE) { // client is asking for a retransmit oldframe = NULL; lastframe = 0; } else if (client->netchan.outgoingSequence - client->deltaMessage >= (PACKET_BACKUP - 3)) { // client hasn't gotten a good message through in a long time Com_DPrintf("%s: Delta request from out of date packet.\n", client->name); oldframe = NULL; lastframe = 0; } else { // we have a valid snapshot to delta from oldframe = &client->frames[client->deltaMessage & PACKET_MASK]; lastframe = client->netchan.outgoingSequence - client->deltaMessage; // the snapshot's entities may still have rolled off the buffer, though if (oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities) { Com_DPrintf("%s: Delta request from out of date entities.\n", client->name); oldframe = NULL; lastframe = 0; } } MSG_WriteByte(msg, svc_snapshot); // NOTE, MRE: now sent at the start of every message from server to client // let the client know which reliable clientCommands we have received //MSG_WriteLong( msg, client->lastClientCommand ); // send over the current server time so the client can drift // its view of time to try to match MSG_WriteLong(msg, svs.time); // what we are delta'ing from MSG_WriteByte(msg, lastframe); snapFlags = svs.snapFlagServerBit; if (client->rateDelayed) { snapFlags |= SNAPFLAG_RATE_DELAYED; } if (client->state != CS_ACTIVE) { snapFlags |= SNAPFLAG_NOT_ACTIVE; } MSG_WriteByte(msg, snapFlags); // send over the areabits MSG_WriteByte(msg, frame->areabytes); MSG_WriteData(msg, frame->areabits, frame->areabytes); //{ //int sz = msg->cursize; //int usz = msg->uncompsize; // delta encode the playerstate if (oldframe) { MSG_WriteDeltaPlayerstate(msg, &oldframe->ps, &frame->ps); } else { MSG_WriteDeltaPlayerstate(msg, NULL, &frame->ps); } //Com_Printf( "Playerstate delta size: %f\n", ((msg->cursize - sz) * sv_fps->integer) / 8.f ); //} // delta encode the entities SV_EmitPacketEntities(oldframe, frame, msg); // padding for rate debugging if (sv_padPackets->integer) { int i; for (i = 0 ; i < sv_padPackets->integer ; i++) { MSG_WriteByte(msg, svc_nop); } } }
/* =================== 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 ); } }
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); }
/* ================== SV_WriteSnapshotToClient ================== */ static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { clientSnapshot_t *frame, *oldframe; int lastframe; int snapFlags; // this is the snapshot we are creating frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; // try to use a previous frame as the source for delta compressing the snapshot if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { // client is asking for a retransmit oldframe = NULL; lastframe = 0; } else if ( client->netchan.outgoingSequence - client->deltaMessage >= (PACKET_BACKUP - 3) ) { // client hasn't gotten a good message through in a long time Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); oldframe = NULL; lastframe = 0; } else { // we have a valid snapshot to delta from oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; lastframe = client->netchan.outgoingSequence - client->deltaMessage; // the snapshot's entities may still have rolled off the buffer, though if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); oldframe = NULL; lastframe = 0; } } MSG_WriteByte (msg, svc_snapshot); // let the client know which reliable clientCommands we have received MSG_WriteLong( msg, client->lastClientCommand ); // send over the current server time so the client can drift // its view of time to try to match MSG_WriteLong (msg, sv.time); // we must write a message number, because recorded demos won't have // the same network message sequences MSG_WriteLong (msg, client->netchan.outgoingSequence ); MSG_WriteByte (msg, lastframe); // what we are delta'ing from MSG_WriteLong (msg, client->cmdNum); // we have executed up to here snapFlags = client->rateDelayed | ( client->droppedCommands << 1 ); client->droppedCommands = 0; MSG_WriteByte (msg, snapFlags); // send over the areabits MSG_WriteByte (msg, frame->areabytes); MSG_WriteData (msg, frame->areabits, frame->areabytes); // delta encode the playerstate if ( oldframe ) { MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); } else { MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); } // delta encode the entities SV_EmitPacketEntities (oldframe, frame, msg); }
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); }
void SV_CreateClientGameStateMessage( client_t *client, msg_t *msg, qboolean updateServerCommands ) { int start; entityState_t *base, nullstate; // NOTE, MRE: all server->client messages now acknowledge // let the client know which reliable clientCommands we have received MSG_WriteLong( msg, client->lastClientCommand ); if ( updateServerCommands ) { // send any server commands waiting to be sent first. // we have to do this cause we send the client->reliableSequence // with a gamestate and it sets the clc.serverCommandSequence at // the client side SV_UpdateServerCommandsToClient( client, msg ); } // send the gamestate MSG_WriteByte( msg, svc_gamestate ); MSG_WriteLong( msg, client->reliableSequence ); // write the configstrings for ( start = 0 ; start < MAX_CONFIGSTRINGS ; start++ ) { if (sv.configstrings[start][0]) { MSG_WriteByte( msg, svc_configstring ); MSG_WriteShort( msg, start ); MSG_WriteBigString( msg, sv.configstrings[start] ); } } // write the baselines Com_Memset( &nullstate, 0, sizeof( nullstate ) ); for ( start = 0 ; start < MAX_GENTITIES; start++ ) { base = &sv.svEntities[start].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); // write the checksum feed MSG_WriteLong( msg, sv.checksumFeed); //rwwRMG - send info for the terrain if ( TheRandomMissionManager ) { z_stream zdata; // Send the height map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_BEST_COMPRESSION ); unsigned char heightmap[15000]; zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetHeightMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( msg, 1, 1 ); MSG_WriteData ( msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Send the flatten map memset(&zdata, 0, sizeof(z_stream)); deflateInit ( &zdata, Z_BEST_COMPRESSION ); zdata.next_out = (unsigned char*)heightmap; zdata.avail_out = 15000; zdata.next_in = TheRandomMissionManager->GetLandScape()->GetFlattenMap(); zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); deflate(&zdata, Z_SYNC_FLUSH); MSG_WriteShort ( msg, (unsigned short)zdata.total_out ); MSG_WriteBits ( msg, 1, 1 ); MSG_WriteData ( msg, heightmap, zdata.total_out); deflateEnd(&zdata); // Seed is needed for misc ents and noise MSG_WriteLong ( msg, TheRandomMissionManager->GetLandScape()->get_rand_seed ( ) ); SV_WriteRMGAutomapSymbols ( msg ); } else { MSG_WriteShort ( msg, 0 ); } }
/* ================== SV_WriteDownloadToClient Check to see if the client wants a file, open it if needed and start pumping the client Fill up msg with data ================== */ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) { int curindex; int rate; int blockspersnap; int idPack, missionPack; char errorMessage[1024]; if (!*cl->downloadName) return; // Nothing being downloaded if (!cl->download) { // We open the file here Com_Printf( "clientDownload: %d : begining \"%s\"\n", cl - svs.clients, cl->downloadName ); missionPack = FS_idPak(cl->downloadName, "missionpack"); idPack = missionPack || FS_idPak(cl->downloadName, "baseq3"); if ( !sv_allowDownload->integer || idPack || ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { // cannot auto-download file if (idPack) { Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName); if (missionPack) { Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" "The Team Arena mission pack can be found in your local game store.", cl->downloadName); } else { Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); } } else if ( !sv_allowDownload->integer ) { Com_Printf("clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName); if (sv_pure->integer) { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "You will need to get this file elsewhere before you " "can connect to this pure server.\n", cl->downloadName); } else { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "The server you are connecting to is not a pure server, " "set autodownload to No in your settings and you might be " "able to join the game anyway.\n", cl->downloadName); } } else { // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? // if the pk3 is referenced, it must have been found somewhere in the filesystem Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); } MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, 0 ); // client is expecting block zero MSG_WriteLong( msg, -1 ); // illegal file size MSG_WriteString( msg, errorMessage ); *cl->downloadName = 0; return; } // Init cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; cl->downloadCount = 0; cl->downloadEOF = qfalse; } // Perform any reads that we need to while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && cl->downloadSize != cl->downloadCount) { curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); if (!cl->downloadBlocks[curindex]) cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); if (cl->downloadBlockSize[curindex] < 0) { // EOF right now cl->downloadCount = cl->downloadSize; break; } cl->downloadCount += cl->downloadBlockSize[curindex]; // Load in next block cl->downloadCurrentBlock++; } // Check to see if we have eof condition and add the EOF block if (cl->downloadCount == cl->downloadSize && !cl->downloadEOF && cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; cl->downloadCurrentBlock++; cl->downloadEOF = qtrue; // We have added the EOF block } // Loop up to window size times based on how many blocks we can fit in the // client snapMsec and rate // based on the rate, how many bytes can we fit in the snapMsec time of the client // normal rate / snapshotMsec calculation rate = cl->rate; if ( sv_maxRate->integer ) { if ( sv_maxRate->integer < 1000 ) { Cvar_Set( "sv_MaxRate", "1000" ); } if ( sv_maxRate->integer < rate ) { rate = sv_maxRate->integer; } } if (!rate) { blockspersnap = 1; } else { blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / MAX_DOWNLOAD_BLKSIZE; } if (blockspersnap < 0) blockspersnap = 1; while (blockspersnap--) { // Write out the next section of the file, if we have already reached our window, // automatically start retransmitting if (cl->downloadClientBlock == cl->downloadCurrentBlock) return; // Nothing to transmit if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { // We have transmitted the complete window, should we start resending? //FIXME: This uses a hardcoded one second timeout for lost blocks //the timeout should be based on client rate somehow if (svs.time - cl->downloadSendTime > 1000) cl->downloadXmitBlock = cl->downloadClientBlock; else return; } // Send current block curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, cl->downloadXmitBlock ); // block zero is special, contains file size if ( cl->downloadXmitBlock == 0 ) MSG_WriteLong( msg, cl->downloadSize ); MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); // Write the block if ( cl->downloadBlockSize[curindex] ) { MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); } Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock ); // Move on to the next block // It will get sent with next snap shot. The rate will keep us in line. cl->downloadXmitBlock++; cl->downloadSendTime = svs.time; } }
/* ================== SV_WriteDownloadToClient Check to see if the client wants a file, open it if needed and start pumping the client Fill up msg with data ================== */ void SV_WriteDownloadToClient( client_t *cl, msg_t *msg ) { int curindex; int rate; int blockspersnap; char errorMessage[ 1024 ]; int download_flag; bool bTellRate = false; // verbosity if ( !*cl->downloadName ) { return; // Nothing being downloaded } if ( cl->bWWWing ) { return; // The client acked and is downloading with ftp/http } if ( !cl->download ) { // We open the file here //bani - prevent duplicate download notifications if ( cl->downloadnotify & DLNOTIFY_BEGIN ) { cl->downloadnotify &= ~DLNOTIFY_BEGIN; Log::Notice( "clientDownload: %d : beginning \"%s\"\n", ( int )( cl - svs.clients ), cl->downloadName ); } if ( !sv_allowDownload->integer ) { Log::Notice( "clientDownload: %d : \"%s\" download disabled\n", ( int )( cl - svs.clients ), cl->downloadName ); if ( sv_pure->integer ) { Com_sprintf( errorMessage, sizeof( errorMessage ), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "You will need to get this file elsewhere before you " "can connect to this pure server.\n", cl->downloadName ); } else { Com_sprintf( errorMessage, sizeof( errorMessage ), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "Set autodownload to No in your settings and you might be " "able to connect even if you don't have the file.\n", cl->downloadName ); } SV_BadDownload( cl, msg ); MSG_WriteString( msg, errorMessage ); // (could SV_DropClient instead?) return; } // www download redirect protocol // NOTE: this is called repeatedly while a client connects. Maybe we should sort of cache the message or something // FIXME: we need to abstract this to an independent module for maximum configuration/usability by server admins // FIXME: I could rework that, it's crappy if ( sv_wwwDownload->integer ) { std::string name, version; Util::optional<uint32_t> checksum; int downloadSize = 0; bool success = FS::ParsePakName(cl->downloadName, cl->downloadName + strlen(cl->downloadName), name, version, checksum); if (success) { const FS::PakInfo* pak = checksum ? FS::FindPak(name, version) : FS::FindPak(name, version, *checksum); if (pak) { try { downloadSize = FS::RawPath::OpenRead(pak->path).Length(); } catch (std::system_error&) { success = false; } } else success = false; } std::string pakName = name + "_" + version + ".pk3"; if ( !cl->bFallback ) { if ( success ) { Q_strncpyz( cl->downloadURL, va("%s/%s", sv_wwwBaseURL->string, pakName.c_str()), sizeof( cl->downloadURL ) ); //bani - prevent multiple download notifications if ( cl->downloadnotify & DLNOTIFY_REDIRECT ) { cl->downloadnotify &= ~DLNOTIFY_REDIRECT; Log::Notice( "Redirecting client '%s' to %s\n", cl->name, cl->downloadURL ); } // once cl->downloadName is set (and possibly we have our listening socket), let the client know cl->bWWWDl = true; MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, -1 ); // block -1 means ftp/http download // compatible with legacy svc_download protocol: [size] [size bytes] // download URL, size of the download file, download flags MSG_WriteString( msg, cl->downloadURL ); MSG_WriteLong( msg, downloadSize ); download_flag = 0; if ( sv_wwwDlDisconnected->integer ) { download_flag |= DL_FLAG_DISCON; } MSG_WriteLong( msg, download_flag ); // flags return; } else { // that should NOT happen - even regular download would fail then anyway Log::Warn("Client '%s': couldn't extract file size for %s", cl->name, cl->downloadName ); } } else { cl->bFallback = false; cl->bWWWDl = true; if ( SV_CheckFallbackURL( cl, pakName.c_str(), msg ) ) { return; } Log::Warn("Client '%s': falling back to regular downloading for failed file %s", cl->name, cl->downloadName ); } } // find file cl->bWWWDl = false; std::string name, version; Util::optional<uint32_t> checksum; bool success = FS::ParsePakName(cl->downloadName, cl->downloadName + strlen(cl->downloadName), name, version, checksum); if (success) { const FS::PakInfo* pak = checksum ? FS::FindPak(name, version) : FS::FindPak(name, version, *checksum); if (pak) { try { cl->download = new FS::File(FS::RawPath::OpenRead(pak->path)); cl->downloadSize = cl->download->Length(); } catch (std::system_error&) { success = false; } } else success = false; } if ( !success ) { Log::Notice( "clientDownload: %d : \"%s\" file not found on server\n", ( int )( cl - svs.clients ), cl->downloadName ); Com_sprintf( errorMessage, sizeof( errorMessage ), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName ); SV_BadDownload( cl, msg ); MSG_WriteString( msg, errorMessage ); // (could SV_DropClient instead?) return; } // is valid source, init cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; cl->downloadCount = 0; cl->downloadEOF = false; bTellRate = true; } // Perform any reads that we need to while ( cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && cl->downloadSize != cl->downloadCount ) { curindex = ( cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW ); if ( !cl->downloadBlocks[ curindex ] ) { cl->downloadBlocks[ curindex ] = ( byte* ) Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); } try { cl->downloadBlockSize[ curindex ] = cl->download->Read(cl->downloadBlocks[ curindex ], MAX_DOWNLOAD_BLKSIZE); } catch (std::system_error&) { // EOF right now cl->downloadCount = cl->downloadSize; break; } cl->downloadCount += cl->downloadBlockSize[ curindex ]; // Load in next block cl->downloadCurrentBlock++; } // Check to see if we have eof condition and add the EOF block if ( cl->downloadCount == cl->downloadSize && !cl->downloadEOF && cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW ) { cl->downloadBlockSize[ cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW ] = 0; cl->downloadCurrentBlock++; cl->downloadEOF = true; // We have added the EOF block } // Loop up to window size times based on how many blocks we can fit in the // client snapMsec and rate // based on the rate, how many bytes can we fit in the snapMsec time of the client rate = cl->rate; // show_bug.cgi?id=509 // for autodownload, we use a separate max rate value // we do this everytime because the client might change its rate during the download if ( sv_dl_maxRate->integer < rate ) { rate = sv_dl_maxRate->integer; if ( bTellRate ) { Log::Notice( "'%s' downloading at sv_dl_maxrate (%d)\n", cl->name, sv_dl_maxRate->integer ); } } else if ( bTellRate ) { Log::Notice( "'%s' downloading at rate %d\n", cl->name, rate ); } if ( !rate ) { blockspersnap = 1; } else { blockspersnap = ( ( rate * cl->snapshotMsec ) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / MAX_DOWNLOAD_BLKSIZE; } if ( blockspersnap < 0 ) { blockspersnap = 1; } while ( blockspersnap-- ) { // Write out the next section of the file, if we have already reached our window, // automatically start retransmitting if ( cl->downloadClientBlock == cl->downloadCurrentBlock ) { return; // Nothing to transmit } if ( cl->downloadXmitBlock == cl->downloadCurrentBlock ) { // We have transmitted the complete window, should we start resending? //FIXME: This uses a hardcoded one second timeout for lost blocks //the timeout should be based on client rate somehow if ( svs.time - cl->downloadSendTime > 1000 ) { cl->downloadXmitBlock = cl->downloadClientBlock; } else { return; } } // Send current block curindex = ( cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW ); MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, cl->downloadXmitBlock ); // block zero is special, contains file size if ( cl->downloadXmitBlock == 0 ) { MSG_WriteLong( msg, cl->downloadSize ); } MSG_WriteShort( msg, cl->downloadBlockSize[ curindex ] ); // Write the block if ( cl->downloadBlockSize[ curindex ] ) { MSG_WriteData( msg, cl->downloadBlocks[ curindex ], cl->downloadBlockSize[ curindex ] ); } Log::Debug( "clientDownload: %d: writing block %d", ( int )( cl - svs.clients ), cl->downloadXmitBlock ); // Move on to the next block // It will get sent with next snap shot. The rate will keep us in line. cl->downloadXmitBlock++; cl->downloadSendTime = svs.time; } }
/* ================= Netchan_TransmitNextFragment Send one fragment of the current message ================= */ void Netchan_TransmitNextFragment( netchan_t* chan ) { msg_s send; byte send_buf[MAX_PACKETLEN]; int fragmentLength; int outgoingSequence; // write the packet header MSG_InitOOB( &send, send_buf, sizeof( send_buf ) ); // <-- only do the oob here outgoingSequence = chan->outgoingSequence | FRAGMENT_BIT; MSG_WriteLong( &send, outgoingSequence ); // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } #ifdef LEGACY_PROTOCOL if ( !chan->compat ) #endif MSG_WriteLong( &send, NETCHAN_GENCHECKSUM( chan->challenge, chan->outgoingSequence ) ); // copy the reliable message to the packet first fragmentLength = FRAGMENT_SIZE; if ( chan->unsentFragmentStart + fragmentLength > chan->unsentLength ) { fragmentLength = chan->unsentLength - chan->unsentFragmentStart; } MSG_WriteShort( &send, chan->unsentFragmentStart ); MSG_WriteShort( &send, fragmentLength ); MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); // Store send time and size of this packet for rate control chan->lastSentTime = Sys_Milliseconds(); chan->lastSentSize = send.cursize; if ( showpackets->integer ) { Com_Printf( "%s send %4i : s=%i fragment=%i,%i\n" , netsrcString[ chan->sock ] , send.cursize , chan->outgoingSequence , chan->unsentFragmentStart, fragmentLength ); } chan->unsentFragmentStart += fragmentLength; // this exit condition is a little tricky, because a packet // that is exactly the fragment length still needs to send // a second packet of zero length so that the other side // can tell there aren't more to follow if ( chan->unsentFragmentStart == chan->unsentLength && fragmentLength != FRAGMENT_SIZE ) { chan->outgoingSequence++; chan->unsentFragments = false; } }
/* ================== SV_WriteSnapshotToClient ================== */ static void SV_WriteSnapshotToClient( client_t *client, msg_t *msg ) { clientSnapshot_t *frame, *oldframe; int lastframe; int i; int snapFlags; // this is the snapshot we are creating frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; // try to use a previous frame as the source for delta compressing the snapshot if ( client->deltaMessage <= 0 || client->state != CS_ACTIVE ) { // client is asking for a retransmit oldframe = NULL; lastframe = 0; } else if ( client->netchan.outgoingSequence - client->deltaMessage >= (PACKET_BACKUP - 3) ) { // client hasn't gotten a good message through in a long time Com_DPrintf ("%s: Delta request from out of date packet.\n", client->name); oldframe = NULL; lastframe = 0; } else { // we have a valid snapshot to delta from oldframe = &client->frames[ client->deltaMessage & PACKET_MASK ]; lastframe = client->netchan.outgoingSequence - client->deltaMessage; // the snapshot's entities may still have rolled off the buffer, though if ( oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities ) { Com_DPrintf ("%s: Delta request from out of date entities.\n", client->name); oldframe = NULL; lastframe = 0; } } MSG_WriteByte (msg, svc_snapshot); // NOTE, MRE: now sent at the start of every message from server to client // let the client know which reliable clientCommands we have received //MSG_WriteLong( msg, client->lastClientCommand ); // send over the current server time so the client can drift // its view of time to try to match MSG_WriteLong (msg, svs.time); // what we are delta'ing from MSG_WriteByte (msg, lastframe); snapFlags = svs.snapFlagServerBit; if ( client->rateDelayed ) { snapFlags |= SNAPFLAG_RATE_DELAYED; } if ( client->state != CS_ACTIVE ) { snapFlags |= SNAPFLAG_NOT_ACTIVE; } MSG_WriteByte (msg, snapFlags); // send over the areabits MSG_WriteByte (msg, frame->areabytes); MSG_WriteData (msg, frame->areabits, frame->areabytes); // 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 SV_EmitPacketEntities (oldframe, frame, msg); // padding for rate debugging if ( sv_padPackets->integer ) { for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { MSG_WriteByte (msg, svc_nop); } } }
/* ================== SV_WriteDownloadToClient Check to see if the client wants a file, open it if needed and start pumping the client Fill up msg with data, return number of download blocks added ================== */ int SV_WriteDownloadToClient(client_t *cl, msg_t *msg) { int curindex; int unreferenced = 1; char errorMessage[1024]; char pakbuf[MAX_QPATH], *pakptr; int numRefPaks; if (!*cl->downloadName) return 0; // Nothing being downloaded if(!cl->download) { qboolean idPack = qfalse; // Chop off filename extension. Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); pakptr = strrchr(pakbuf, '.'); if(pakptr) { *pakptr = '\0'; // Check for pk3 filename extension if(!Q_stricmp(pakptr + 1, "pk3")) { const char *referencedPaks = FS_ReferencedPakNames(); // Check whether the file appears in the list of referenced // paks to prevent downloading of arbitrary files. Cmd_TokenizeStringIgnoreQuotes(referencedPaks); numRefPaks = Cmd_Argc(); for(curindex = 0; curindex < numRefPaks; curindex++) { if(!FS_FilenameCompare(Cmd_Argv(curindex), pakbuf)) { unreferenced = 0; // now that we know the file is referenced, // check whether it's legal to download it. idPack = idPack || FS_idPak(pakbuf, BASEGAME, NUM_RV_PAKS); break; } } } } cl->download = 0; // We open the file here if ( !(sv_allowDownload->integer & DLF_ENABLE) || (sv_allowDownload->integer & DLF_NO_UDP) || idPack || unreferenced || ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) < 0 ) { // cannot auto-download file if(unreferenced) { Com_Printf("clientDownload: %d : \"%s\" is not referenced and cannot be downloaded.\n", (int) (cl - svs.clients), cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" is not referenced and cannot be downloaded.", cl->downloadName); } else if (idPack) { Com_Printf("clientDownload: %d : \"%s\" cannot download raven jk3 pk3 files\n", (int) (cl - svs.clients), cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload raven jk3 pk3 file \"%s\"", cl->downloadName); } else if ( !(sv_allowDownload->integer & DLF_ENABLE) || (sv_allowDownload->integer & DLF_NO_UDP) ) { Com_Printf("clientDownload: %d : \"%s\" download disabled\n", (int) (cl - svs.clients), cl->downloadName); if (sv_pure->integer) { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "You will need to get this file elsewhere before you " "can connect to this pure server.\n", cl->downloadName); } else { Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" "The server you are connecting to is not a pure server, " "set autodownload to No in your settings and you might be " "able to join the game anyway.\n", cl->downloadName); } } else { // NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme? // if the pk3 is referenced, it must have been found somewhere in the filesystem Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", (int) (cl - svs.clients), cl->downloadName); Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); } MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, 0 ); // client is expecting block zero MSG_WriteLong( msg, -1 ); // illegal file size MSG_WriteString( msg, errorMessage ); *cl->downloadName = 0; if(cl->download) FS_FCloseFile(cl->download); return 0; } Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); // Init cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; cl->downloadCount = 0; cl->downloadEOF = qfalse; } // Perform any reads that we need to while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && cl->downloadSize != cl->downloadCount) { curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); if (!cl->downloadBlocks[curindex]) cl->downloadBlocks[curindex] = Z_Malloc(MAX_DOWNLOAD_BLKSIZE); cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); if (cl->downloadBlockSize[curindex] < 0) { // EOF right now cl->downloadCount = cl->downloadSize; break; } cl->downloadCount += cl->downloadBlockSize[curindex]; // Load in next block cl->downloadCurrentBlock++; } // Check to see if we have eof condition and add the EOF block if (cl->downloadCount == cl->downloadSize && !cl->downloadEOF && cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; cl->downloadCurrentBlock++; cl->downloadEOF = qtrue; // We have added the EOF block } if (cl->downloadClientBlock == cl->downloadCurrentBlock) return 0; // Nothing to transmit // Write out the next section of the file, if we have already reached our window, // automatically start retransmitting if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { // We have transmitted the complete window, should we start resending? if (svs.time - cl->downloadSendTime > 1000) cl->downloadXmitBlock = cl->downloadClientBlock; else return 0; } // Send current block curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); MSG_WriteByte( msg, svc_download ); MSG_WriteShort( msg, cl->downloadXmitBlock ); // block zero is special, contains file size if ( cl->downloadXmitBlock == 0 ) MSG_WriteLong( msg, cl->downloadSize ); MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); // Write the block if(cl->downloadBlockSize[curindex]) MSG_WriteData(msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex]); Com_DPrintf( "clientDownload: %d : writing block %d\n", (int) (cl - svs.clients), cl->downloadXmitBlock ); // Move on to the next block // It will get sent with next snap shot. The rate will keep us in line. cl->downloadXmitBlock++; cl->downloadSendTime = svs.time; return 1; }
/* ================== SV_WriteSnapshotToClient ================== */ static void SV_WriteSnapshotToClient(client_t *client, msg_t *msg) { clientSnapshot_t *frame, *oldframe; int lastframe; int i; int snapFlags; // this is the snapshot we are creating frame = &client->frames[client->netchan.outgoingSequence & PACKET_MASK]; // try to use a previous frame as the source for delta compressing the snapshot if (client->deltaMessage <= 0 || client->state != CS_ACTIVE) { // client is asking for a retransmit oldframe = NULL; lastframe = 0; } else if (client->netchan.outgoingSequence - client->deltaMessage >= (PACKET_BACKUP - 3)) { // client hasn't gotten a good message through in a long time Com_DPrintf("%s: Delta request from out of date packet.\n", client->name); oldframe = NULL; lastframe = 0; } else { // we have a valid snapshot to delta from oldframe = &client->frames[client->deltaMessage & PACKET_MASK]; lastframe = client->netchan.outgoingSequence - client->deltaMessage; // the snapshot's entities may still have rolled off the buffer, though if (oldframe->first_entity <= svs.nextSnapshotEntities - svs.numSnapshotEntities) { Com_DPrintf("%s: Delta request from out of date entities.\n", client->name); oldframe = NULL; lastframe = 0; } } MSG_WriteByte(msg, svc_snapshot); // NOTE, MRE: now sent at the start of every message from server to client // let the client know which reliable clientCommands we have received // MSG_WriteLong( msg, client->lastClientCommand ); // send over the current server time so the client can drift // its view of time to try to match if (client->oldServerTime) { // The server has not yet got an acknowledgement of the // new gamestate from this client, so continue to send it // a time as if the server has not restarted. Note from // the client's perspective this time is strictly speaking // incorrect, but since it'll be busy loading a map at // the time it doesn't really matter. MSG_WriteLong(msg, sv.time + client->oldServerTime); } else { MSG_WriteLong(msg, sv.time); } // what we are delta'ing from MSG_WriteByte(msg, lastframe); snapFlags = svs.snapFlagServerBit; if (client->rateDelayed) { snapFlags |= SNAPFLAG_RATE_DELAYED; } if (client->state != CS_ACTIVE) { snapFlags |= SNAPFLAG_NOT_ACTIVE; } MSG_WriteByte(msg, snapFlags); // send over the areabits MSG_WriteByte(msg, frame->areabytes); MSG_WriteData(msg, frame->areabits, frame->areabytes); // delta encode the playerstate if (oldframe) { MSG_WriteDeltaPlayerstate(client->netchan.alternateProtocol, msg, &oldframe->ps, &frame->ps); } else { MSG_WriteDeltaPlayerstate(client->netchan.alternateProtocol, msg, NULL, &frame->ps); } // delta encode the entities SV_EmitPacketEntities(client->netchan.alternateProtocol, oldframe, frame, msg); // padding for rate debugging if (sv_padPackets->integer) { for (i = 0; i < sv_padPackets->integer; i++) { MSG_WriteByte(msg, svc_nop); } } }
/* =============== Netchan_Transmit Sends a message to a connection, fragmenting if necessary A 0 length will still generate a packet. ================ */ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { msg_t send; byte send_buf[MAX_PACKETLEN]; int fragmentStart, fragmentLength; fragmentStart = 0; // stop warning message fragmentLength = 0; // fragment large reliable messages if ( length >= FRAGMENT_SIZE ) { fragmentStart = 0; do { // write the packet header MSG_Init (&send, send_buf, sizeof(send_buf)); MSG_WriteLong( &send, chan->outgoingSequence | FRAGMENT_BIT ); MSG_WriteLong( &send, chan->incomingSequence ); // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } // copy the reliable message to the packet first fragmentLength = FRAGMENT_SIZE; if ( fragmentStart + fragmentLength > length ) { fragmentLength = length - fragmentStart; } MSG_WriteShort( &send, fragmentStart ); MSG_WriteShort( &send, fragmentLength ); MSG_WriteData( &send, data + fragmentStart, fragmentLength ); // XOR scramble all data in the packet after the header Netchan_ScramblePacket( &send ); // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); if ( showpackets->integer ) { Com_Printf ("%s send %4i : s=%i ack=%i fragment=%i,%i\n" , netsrcString[ chan->sock ] , send.cursize , chan->outgoingSequence - 1 , chan->incomingSequence , fragmentStart, fragmentLength); } fragmentStart += fragmentLength; // this exit condition is a little tricky, because a packet // that is exactly the fragment length still needs to send // a second packet of zero length so that the other side // can tell there aren't more to follow } while ( fragmentStart != length || fragmentLength == FRAGMENT_SIZE ); chan->outgoingSequence++; return; } // write the packet header MSG_Init (&send, send_buf, sizeof(send_buf)); MSG_WriteLong( &send, chan->outgoingSequence ); MSG_WriteLong( &send, chan->incomingSequence ); chan->outgoingSequence++; // send the qport if we are a client if ( chan->sock == NS_CLIENT ) { MSG_WriteShort( &send, qport->integer ); } MSG_WriteData( &send, data, length ); // XOR scramble all data in the packet after the header Netchan_ScramblePacket( &send ); // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); if ( showpackets->integer ) { Com_Printf( "%s send %4i : s=%i ack=%i\n" , netsrcString[ chan->sock ] , send.cursize , chan->outgoingSequence - 1 , chan->incomingSequence ); } }
/* =================== 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); }
/* ================== SV_WriteFrameToClient ================== */ void SV_WriteFrameToClient( sv_client_t *cl, sizebuf_t *msg ) { client_frame_t *frame, *oldframe; int lastframe; // this is the frame we are creating frame = &cl->frames[sv.framenum & SV_UPDATE_MASK]; if( cl->lastframe <= 0 ) { // client is asking for a retransmit oldframe = NULL; lastframe = -1; } else if( sv.framenum - cl->lastframe >= (SV_UPDATE_BACKUP - 3)) { // client hasn't gotten a good message through in a long time oldframe = NULL; lastframe = -1; } else { // we have a valid message to delta from oldframe = &cl->frames[cl->lastframe & SV_UPDATE_MASK]; lastframe = cl->lastframe; // the snapshot's entities may still have rolled off the buffer, though if( oldframe->first_entity <= svs.next_client_entities - svs.num_client_entities ) { MsgDev( D_WARN, "%s: ^7delta request from out of date entities.\n", cl->name ); oldframe = NULL; lastframe = 0; } } // refresh physinfo if needs if( cl->physinfo_modified ) { cl->physinfo_modified = false; MSG_WriteByte( msg, svc_physinfo ); MSG_WriteString( msg, cl->physinfo ); } // delta encode the events SV_EmitEvents( cl, frame, msg ); MSG_WriteByte( msg, svc_frame ); MSG_WriteLong( msg, sv.framenum ); MSG_WriteLong( msg, sv.time ); // send a servertime each frame MSG_WriteLong( msg, sv.frametime ); MSG_WriteLong( msg, lastframe ); // what we are delta'ing from MSG_WriteByte( msg, cl->surpressCount ); // rate dropped packets MSG_WriteByte( msg, frame->index ); // send a client index cl->surpressCount = 0; // send over the areabits MSG_WriteByte( msg, frame->areabits_size ); // never more than 255 bytes MSG_WriteData( msg, frame->areabits, frame->areabits_size ); // delta encode the entities SV_EmitPacketEntities( oldframe, frame, msg ); }