/* ============== CL_Netchan_Encode // first 12 bytes of the data are always: long serverId; long messageAcknowledge; long reliableAcknowledge; ============== */ static void CL_Netchan_Encode(msg_t * msg) { int serverId, messageAcknowledge, reliableAcknowledge; int i, index, srdc, sbit, soob; byte key, *string; if(msg->cursize <= CL_ENCODE_START) { return; } srdc = msg->readcount; sbit = msg->bit; soob = msg->oob; msg->bit = 0; msg->readcount = 0; msg->oob = qfalse; serverId = MSG_ReadLong(msg); messageAcknowledge = MSG_ReadLong(msg); reliableAcknowledge = MSG_ReadLong(msg); msg->oob = (qboolean)soob; msg->bit = sbit; msg->readcount = srdc; string = (byte *)CL_GetReliableServerCommand( reliableAcknowledge ); index = 0; // key = clc.challenge ^ serverId ^ messageAcknowledge; for(i = CL_ENCODE_START; i < msg->cursize; i++) { // modify the key with the last received now acknowledged server command if(!string[index]) { index = 0; } if(string[index] > 127 || string[index] == '%') { key ^= '.' << (i & 1); } else { key ^= string[index] << (i & 1); } index++; // encode the data with this key *(msg->data + i) = (*(msg->data + i)) ^ key; } }
/* =================== 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; } 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, CL_GetReliableCommand( i ) ); } // 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_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 ^= Com_HashKey( CL_GetReliableServerCommand( clc.serverCommandSequence ), 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) { if(cl_showSend->integer) { Com_Printf("WARNING: unsent fragments (not supposed to happen!)\n"); } CL_Netchan_TransmitNextFragment(&clc.netchan); } }
/* =================== CL_GetServerCommand Set up argc/argv for the given command =================== */ qboolean CL_GetServerCommand( int serverCommandNumber ) { char *s; char *cmd; static char bigConfigString[BIG_INFO_STRING]; int argc; // if we have irretrievably lost a reliable command, drop the connection if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { // when a demo record was started after the client got a whole bunch of // reliable commands then the client never got those first reliable commands if ( clc.demoplaying ) return qfalse; Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); return qfalse; } if ( serverCommandNumber > clc.serverCommandSequence ) { Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); return qfalse; } s = CL_GetReliableServerCommand( serverCommandNumber ); clc.lastExecutedServerCommand = serverCommandNumber; Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); rescan: Cmd_TokenizeString( s ); cmd = Cmd_Argv(0); argc = Cmd_Argc(); if ( !strcmp( cmd, "disconnect" ) ) { // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 // allow server to indicate why they were disconnected if ( argc >= 2 ) Com_Error (ERR_SERVERDISCONNECT, va( "Server Disconnected - %s", Cmd_Argv( 1 ) ) ); else Com_Error (ERR_SERVERDISCONNECT,"Server disconnected\n"); } if ( !strcmp( cmd, "bcs0" ) ) { Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); return qfalse; } if ( !strcmp( cmd, "bcs1" ) ) { s = Cmd_Argv(2); if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); } strcat( bigConfigString, s ); return qfalse; } if ( !strcmp( cmd, "bcs2" ) ) { s = Cmd_Argv(2); if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); } strcat( bigConfigString, s ); strcat( bigConfigString, "\"" ); s = bigConfigString; goto rescan; } if ( !strcmp( cmd, "cs" ) ) { CL_ConfigstringModified(); // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() Cmd_TokenizeString( s ); return qtrue; } if ( !strcmp( cmd, "up" ) ) { if( serverCommandNumber <= clc.lastSnapServerCmdNum ) return qtrue; CL_TableUpdateRow(); return qtrue; } if ( !strcmp( cmd, "de" ) ) { if( serverCommandNumber <= clc.lastSnapServerCmdNum ) return qtrue; CL_TableDeleteRow(); return qtrue; } if ( !strcmp( cmd, "in" ) ) { if( serverCommandNumber <= clc.lastSnapServerCmdNum ) return qtrue; CL_TableInsertRow(); return qtrue; } if ( !strcmp( cmd, "map_restart" ) ) { // clear notify lines and outgoing commands before passing // the restart to the cgame Con_ClearNotify(); Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) ); return qtrue; } // the clientLevelShot command is used during development // to generate 128*128 screenshots from the intermission // point of levels for the menu system to use // we pass it along to the cgame to make apropriate adjustments, // but we also clear the console and notify lines here if ( !strcmp( cmd, "clientLevelShot" ) ) { // don't do it if we aren't running the server locally, // otherwise malicious remote servers could overwrite // the existing thumbnails if ( !com_sv_running->integer ) { return qfalse; } // close the console Con_Close(); // take a special screenshot next frame Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); return qtrue; } // we may want to put a "connect to other server" command here // cgame can now act on the command return qtrue; }