/* ================== SV_UserMove The message usually contains all the movement commands that were in the last three packets, so that the information in dropped packets can be recovered. On very fast clients, there may be multiple usercmd packed into each of the backup packets. ================== */ static void SV_UserMove( client_t *cl, msg_t *msg ) { int i, start; int cmdNum; int firstNum; int cmdCount; usercmd_t nullcmd; usercmd_t cmds[MAX_PACKET_USERCMDS]; usercmd_t *cmd, *oldcmd; int clientTime; int serverId; cl->reliableAcknowledge = MSG_ReadLong( msg ); serverId = MSG_ReadLong( msg ); clientTime = MSG_ReadLong( msg ); cl->deltaMessage = MSG_ReadLong( msg ); // cmdNum is the command number of the most recent included usercmd cmdNum = MSG_ReadLong( msg ); cmdCount = MSG_ReadByte( msg ); if ( cmdCount < 1 ) { Com_Printf( "cmdCount < 1\n" ); return; } if ( cmdCount > MAX_PACKET_USERCMDS ) { Com_Printf( "cmdCount > MAX_PACKET_USERCMDS\n" ); return; } memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; for ( i = 0 ; i < cmdCount ; i++ ) { cmd = &cmds[i]; MSG_ReadDeltaUsercmd( msg, oldcmd, cmd ); oldcmd = cmd; } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate if ( serverId != sv.serverId ) { // if we can tell that the client has dropped the last // gamestate we sent them, resend it if ( cl->netchan.incomingAcknowledged > cl->gamestateMessageNum ) { Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); SV_SendClientGameState( cl ); } return; } // if this is the first usercmd we have received // this gamestate, put the client into the world if ( cl->state == CS_PRIMED ) { SV_ClientEnterWorld( cl, &cmds[0], eSavedGameJustLoaded ); #ifndef _XBOX // No auto-saving for now? if ( sv_mapname->string[0]!='_' ) { char savename[MAX_QPATH]; if ( eSavedGameJustLoaded == eNO ) { SG_WriteSavegame("auto",qtrue); if ( strnicmp(sv_mapname->string, "academy", 7) != 0) { Com_sprintf (savename, sizeof(savename), "auto_%s",sv_mapname->string); SG_WriteSavegame(savename,qtrue);//can't use va becuase it's nested } } else if ( qbLoadTransition == qtrue ) { Com_sprintf (savename, sizeof(savename), "hub/%s", sv_mapname->string ); SG_WriteSavegame( savename, qfalse );//save a full one SG_WriteSavegame( "auto", qfalse );//need a copy for auto, too } } #endif eSavedGameJustLoaded = eNO; // the moves can be processed normaly } if ( cl->state != CS_ACTIVE ) { cl->deltaMessage = -1; return; } // if there is a time gap from the last packet to this packet, // fill in with the first command in the packet // with a packetdup of 0, firstNum == cmdNum firstNum = cmdNum - ( cmdCount - 1 ); if ( cl->cmdNum < firstNum - 1 ) { cl->droppedCommands = qtrue; if ( sv_showloss->integer ) { Com_Printf("Lost %i usercmds from %s\n", firstNum - 1 - cl->cmdNum, cl->name); } if ( cl->cmdNum < firstNum - 6 ) { cl->cmdNum = firstNum - 6; // don't generate too many } while ( cl->cmdNum < firstNum - 1 ) { cl->cmdNum++; SV_ClientThink( cl, &cmds[0] ); } } // skip over any usercmd_t we have already executed start = cl->cmdNum - ( firstNum - 1 ); for ( i = start ; i < cmdCount ; i++ ) { SV_ClientThink (cl, &cmds[ i ]); } cl->cmdNum = cmdNum; }
/* =================== CL_ParsePlayerstate =================== */ void CL_ParsePlayerstate (frame_t *oldframe, frame_t *newframe) { int flags; player_state_t *state; int i; int statbits; state = &newframe->playerstate; // clear to old value before delta parsing if (oldframe) *state = oldframe->playerstate; else memset (state, 0, sizeof(*state)); flags = MSG_ReadShort (&net_message); // // parse the pmove_state_t // if (flags & PS_M_TYPE) state->pmove.pm_type = MSG_ReadByte(&net_message); if (flags & PS_M_ORIGIN) { state->pmove.origin[0] = MSG_ReadShort (&net_message); state->pmove.origin[1] = MSG_ReadShort (&net_message); state->pmove.origin[2] = MSG_ReadShort (&net_message); } if (flags & PS_M_VELOCITY) { state->pmove.velocity[0] = MSG_ReadShort (&net_message); state->pmove.velocity[1] = MSG_ReadShort (&net_message); state->pmove.velocity[2] = MSG_ReadShort (&net_message); } if (flags & PS_M_TIME) state->pmove.pm_time = MSG_ReadByte(&net_message); if (flags & PS_M_FLAGS) state->pmove.pm_flags = MSG_ReadByte(&net_message); if (flags & PS_M_GRAVITY) state->pmove.gravity = MSG_ReadShort (&net_message); if (flags & PS_M_DELTA_ANGLES) { state->pmove.delta_angles[0] = MSG_ReadShort (&net_message); state->pmove.delta_angles[1] = MSG_ReadShort (&net_message); state->pmove.delta_angles[2] = MSG_ReadShort (&net_message); } if (cl.attractloop) state->pmove.pm_type = PM_FREEZE; // demo playback // // parse the rest of the player_state_t // if (flags & PS_VIEWOFFSET) { state->viewoffset[0] = MSG_ReadChar(&net_message) * 0.25; state->viewoffset[1] = MSG_ReadChar(&net_message) * 0.25; state->viewoffset[2] = MSG_ReadChar(&net_message) * 0.25; } if (flags & PS_VIEWANGLES) { state->viewangles[0] = MSG_ReadAngle16(&net_message); state->viewangles[1] = MSG_ReadAngle16(&net_message); state->viewangles[2] = MSG_ReadAngle16(&net_message); } if (flags & PS_KICKANGLES) { state->kick_angles[0] = MSG_ReadChar(&net_message) * 0.25; state->kick_angles[1] = MSG_ReadChar(&net_message) * 0.25; state->kick_angles[2] = MSG_ReadChar(&net_message) * 0.25; } if (flags & PS_WEAPONINDEX) { state->gunindex = MSG_ReadByte(&net_message); } if (flags & PS_WEAPONFRAME) { state->gunframe = MSG_ReadByte(&net_message); state->gunoffset[0] = MSG_ReadChar(&net_message)*0.25; state->gunoffset[1] = MSG_ReadChar(&net_message)*0.25; state->gunoffset[2] = MSG_ReadChar(&net_message)*0.25; state->gunangles[0] = MSG_ReadChar(&net_message)*0.25; state->gunangles[1] = MSG_ReadChar(&net_message)*0.25; state->gunangles[2] = MSG_ReadChar(&net_message)*0.25; } if (flags & PS_BLEND) { state->blend[0] = MSG_ReadByte(&net_message)*0.003921568627450980392156862745098; state->blend[1] = MSG_ReadByte(&net_message)*0.003921568627450980392156862745098; state->blend[2] = MSG_ReadByte(&net_message)*0.003921568627450980392156862745098; state->blend[3] = MSG_ReadByte(&net_message)*0.003921568627450980392156862745098; } if (flags & PS_FOV) state->fov = MSG_ReadByte(&net_message); if (flags & PS_RDFLAGS) state->rdflags = MSG_ReadByte(&net_message); // parse stats statbits = MSG_ReadLong (&net_message); for (i=0 ; i<MAX_STATS ; i++) if (statbits & (1<<i) ) state->stats[i] = MSG_ReadShort(&net_message); }
/* ================== CL_ParseDelta Can go from either a baseline or a previous packet_entity ================== */ void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int bits) { // set everything to the state we are delta'ing from *to = *from; VectorCopy(from->origin, to->old_origin); to->number = number; if (bits & U_MODEL) to->modelindex = MSG_ReadByte(&net_message); if (bits & U_MODEL2) to->modelindex2 = MSG_ReadByte(&net_message); if (bits & U_MODEL3) to->modelindex3 = MSG_ReadByte(&net_message); if (bits & U_MODEL4) to->modelindex4 = MSG_ReadByte(&net_message); if (bits & U_FRAME8) to->frame = MSG_ReadByte(&net_message); if (bits & U_FRAME16) to->frame = MSG_ReadShort(&net_message); assert(to->skinnum >= 0); // jitdebug if ((bits & U_SKIN8) && (bits & U_SKIN16)) //used for laser colors to->skinnum = MSG_ReadLong(&net_message); else if (bits & U_SKIN8) to->skinnum = MSG_ReadByte(&net_message); else if (bits & U_SKIN16) to->skinnum = MSG_ReadShort(&net_message); assert(to->skinnum >= 0); if ((bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16)) to->effects = MSG_ReadLong(&net_message); else if (bits & U_EFFECTS8) to->effects = MSG_ReadByte(&net_message); else if (bits & U_EFFECTS16) to->effects = MSG_ReadShort(&net_message); if ((bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16)) to->renderfx = MSG_ReadLong(&net_message); else if (bits & U_RENDERFX8) to->renderfx = MSG_ReadByte(&net_message); else if (bits & U_RENDERFX16) to->renderfx = MSG_ReadShort(&net_message); if (bits & U_ORIGIN1) to->origin[0] = MSG_ReadCoord(&net_message); if (bits & U_ORIGIN2) to->origin[1] = MSG_ReadCoord(&net_message); if (bits & U_ORIGIN3) to->origin[2] = MSG_ReadCoord(&net_message); if (bits & U_ANGLE1) to->angles[0] = MSG_ReadAngle(&net_message); if (bits & U_ANGLE2) to->angles[1] = MSG_ReadAngle(&net_message); if (bits & U_ANGLE3) to->angles[2] = MSG_ReadAngle(&net_message); if (bits & U_OLDORIGIN) MSG_ReadPos(&net_message, to->old_origin); if (bits & U_SOUND) to->sound = MSG_ReadByte(&net_message); if (bits & U_EVENT) to->event = MSG_ReadByte(&net_message); else to->event = 0; if (bits & U_SOLID) to->solid = MSG_ReadShort(&net_message); }
/* ================= Netchan_Process Returns qfalse if the message should not be processed due to being out of order or a fragment. Msg must be large enough to hold MAX_MSGLEN, because if this is the final fragment of a multi-part message, the entire thing will be copied out. ================= */ qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { int sequence; //int qport; int fragmentStart, fragmentLength; qboolean fragmented; // get sequence numbers MSG_BeginReadingOOB( msg ); sequence = MSG_ReadLong( msg ); // check for fragment information if ( sequence & FRAGMENT_BIT ) { sequence &= ~FRAGMENT_BIT; fragmented = qtrue; } else { fragmented = qfalse; } // read the qport if we are a server if ( chan->sock == NS_SERVER ) { /*qport = */MSG_ReadShort( msg ); } // read the fragment information if ( fragmented ) { fragmentStart = (unsigned short)MSG_ReadShort( msg ); fragmentLength = (unsigned short)MSG_ReadShort( msg ); } else { fragmentStart = 0; // stop warning message fragmentLength = 0; } if ( showpackets->integer ) { if ( fragmented ) { Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" , netsrcString[ chan->sock ] , msg->cursize , sequence , fragmentStart, fragmentLength ); } else { Com_Printf( "%s recv %4i : s=%i\n" , netsrcString[ chan->sock ] , msg->cursize , sequence ); } } // // discard out of order or duplicated packets // if ( sequence <= chan->incomingSequence ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Out of order packet %i at %i\n" , NET_AdrToString( chan->remoteAddress ) , sequence , chan->incomingSequence ); } return qfalse; } // // dropped packets don't keep the message from being used // chan->dropped = sequence - (chan->incomingSequence+1); if ( chan->dropped > 0 ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Dropped %i packets at %i\n" , NET_AdrToString( chan->remoteAddress ) , chan->dropped , sequence ); } } // // if this is the final framgent of a reliable message, // bump incoming_reliable_sequence // if ( fragmented ) { // make sure we if ( sequence != chan->fragmentSequence ) { chan->fragmentSequence = sequence; chan->fragmentLength = 0; } // if we missed a fragment, dump the message if ( fragmentStart != chan->fragmentLength ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Dropped a message fragment\n" , NET_AdrToString( chan->remoteAddress ) , sequence); } // we can still keep the part that we have so far, // so we don't need to clear chan->fragmentLength //rww - not clearing this will allow us to piece together fragments belonging to other packets //that happen to have the same sequence (or so it seems). I am just going to clear it and force //the packet to be dropped. // hell yeah we have to dump the whole thing -gil // but I am scared - mw /* chan->fragmentLength = 0; chan->incomingSequence = sequence; chan->fragmentSequence = 0; */ return qfalse; } // copy the fragment to the fragment buffer if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || chan->fragmentLength + fragmentLength > (int)sizeof( chan->fragmentBuffer ) ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf ("%s:illegal fragment length\n" , NET_AdrToString (chan->remoteAddress ) ); } return qfalse; } Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength, msg->data + msg->readcount, fragmentLength ); chan->fragmentLength += fragmentLength; // if this wasn't the last fragment, don't process anything if ( fragmentLength == FRAGMENT_SIZE ) { return qfalse; } if ( chan->fragmentLength+4 > msg->maxsize ) { Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" , NET_AdrToString (chan->remoteAddress ), chan->fragmentLength+4 ); return qfalse; } // copy the full message over the partial fragment // make sure the sequence number is still there *(int *)msg->data = LittleLong( sequence ); Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); msg->cursize = chan->fragmentLength + 4; chan->fragmentLength = 0; msg->readcount = 4; // past the sequence number msg->bit = 32; // past the sequence number // but I am a wuss -mw // chan->incomingSequence = sequence; // lets not accept any more with this sequence number -gil return qtrue; } // // the message can now be read from the current message pointer // chan->incomingSequence = sequence; return qtrue; }
/* ================== CL_ParseClientdata Server information pertaining to this client only ================== */ void CL_ParseClientdata (void) { int i, j; int bits; //johnfitz bits = (unsigned short)MSG_ReadShort (); //johnfitz -- read bits here isntead of in CL_ParseServerMessage() //johnfitz -- PROTOCOL_FITZQUAKE if (bits & SU_EXTEND1) bits |= (MSG_ReadByte() << 16); if (bits & SU_EXTEND2) bits |= (MSG_ReadByte() << 24); //johnfitz if (bits & SU_VIEWHEIGHT) cl.viewheight = MSG_ReadChar (); else cl.viewheight = DEFAULT_VIEWHEIGHT; if (bits & SU_IDEALPITCH) cl.idealpitch = MSG_ReadChar (); else cl.idealpitch = 0; VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); for (i = 0; i < 3; i++) { if (bits & (SU_PUNCH1<<i) ) cl.punchangle[i] = MSG_ReadChar(); else cl.punchangle[i] = 0; if (bits & (SU_VELOCITY1<<i) ) cl.mvelocity[0][i] = MSG_ReadChar()*16; else cl.mvelocity[0][i] = 0; } //johnfitz -- update v_punchangles if (v_punchangles[0][0] != cl.punchangle[0] || v_punchangles[0][1] != cl.punchangle[1] || v_punchangles[0][2] != cl.punchangle[2]) { VectorCopy (v_punchangles[0], v_punchangles[1]); VectorCopy (cl.punchangle, v_punchangles[0]); } //johnfitz // [always sent] if (bits & SU_ITEMS) i = MSG_ReadLong (); if (cl.items != i) { // set flash times Sbar_Changed (); for (j = 0; j < 32; j++) if ( (i & (1<<j)) && !(cl.items & (1<<j))) cl.item_gettime[j] = cl.time; cl.items = i; } cl.onground = (bits & SU_ONGROUND) != 0; cl.inwater = (bits & SU_INWATER) != 0; if (bits & SU_WEAPONFRAME) cl.stats[STAT_WEAPONFRAME] = MSG_ReadByte (); else cl.stats[STAT_WEAPONFRAME] = 0; if (bits & SU_ARMOR) i = MSG_ReadByte (); else i = 0; if (cl.stats[STAT_ARMOR] != i) { cl.stats[STAT_ARMOR] = i; Sbar_Changed (); } if (bits & SU_WEAPON) i = MSG_ReadByte (); else i = 0; if (cl.stats[STAT_WEAPON] != i) { cl.stats[STAT_WEAPON] = i; Sbar_Changed (); //johnfitz -- lerping if (cl.viewent.model != cl.model_precache[cl.stats[STAT_WEAPON]]) cl.viewent.lerpflags |= LERP_RESETANIM; //don't lerp animation across model changes //johnfitz } i = MSG_ReadShort (); if (cl.stats[STAT_HEALTH] != i) { cl.stats[STAT_HEALTH] = i; Sbar_Changed (); } i = MSG_ReadByte (); if (cl.stats[STAT_AMMO] != i) { cl.stats[STAT_AMMO] = i; Sbar_Changed (); } for (i = 0; i < 4; i++) { j = MSG_ReadByte (); if (cl.stats[STAT_SHELLS+i] != j) { cl.stats[STAT_SHELLS+i] = j; Sbar_Changed (); } } i = MSG_ReadByte (); if (standard_quake) { if (cl.stats[STAT_ACTIVEWEAPON] != i) { cl.stats[STAT_ACTIVEWEAPON] = i; Sbar_Changed (); } } else { if (cl.stats[STAT_ACTIVEWEAPON] != (1<<i)) { cl.stats[STAT_ACTIVEWEAPON] = (1<<i); Sbar_Changed (); } } //johnfitz -- PROTOCOL_FITZQUAKE if (bits & SU_WEAPON2) cl.stats[STAT_WEAPON] |= (MSG_ReadByte() << 8); if (bits & SU_ARMOR2) cl.stats[STAT_ARMOR] |= (MSG_ReadByte() << 8); if (bits & SU_AMMO2) cl.stats[STAT_AMMO] |= (MSG_ReadByte() << 8); if (bits & SU_SHELLS2) cl.stats[STAT_SHELLS] |= (MSG_ReadByte() << 8); if (bits & SU_NAILS2) cl.stats[STAT_NAILS] |= (MSG_ReadByte() << 8); if (bits & SU_ROCKETS2) cl.stats[STAT_ROCKETS] |= (MSG_ReadByte() << 8); if (bits & SU_CELLS2) cl.stats[STAT_CELLS] |= (MSG_ReadByte() << 8); if (bits & SU_WEAPONFRAME2) cl.stats[STAT_WEAPONFRAME] |= (MSG_ReadByte() << 8); if (bits & SU_WEAPONALPHA) cl.viewent.alpha = MSG_ReadByte(); else cl.viewent.alpha = ENTALPHA_DEFAULT; //johnfitz }
/* ================== CL_ParseGamestate ================== */ void CL_ParseGamestate( msg_t *msg ) { int i; entityState_t *es; int newnum; entityState_t nullstate; int cmd; char *s; char oldGame[MAX_QPATH]; Con_Close(); clc.connectPacketCount = 0; // wipe local client state CL_ClearState(); // a gamestate always marks a server command sequence clc.serverCommandSequence = MSG_ReadLong( msg ); // parse all the configstrings and baselines cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings while ( 1 ) { cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF ) { break; } if ( cmd == svc_configstring ) { int len; i = MSG_ReadShort( msg ); if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); } s = MSG_ReadBigString( msg ); len = strlen( s ); if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); } // append it to the gameState string buffer cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); cl.gameState.dataCount += len + 1; } else if ( cmd == svc_baseline ) { newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); if ( newnum < 0 || newnum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); } memset( &nullstate, 0, sizeof( nullstate ) ); es = &cl.entityBaselines[ newnum ]; MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); } else { Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); } } clc.clientNum = MSG_ReadLong( msg ); // read the checksum feed clc.checksumFeed = MSG_ReadLong( msg ); // save old gamedir Cvar_VariableStringBuffer("fs_game", oldGame, sizeof(oldGame)); // parse useful values out of CS_SERVERINFO CL_ParseServerInfo(); // parse serverId and other cvars CL_SystemInfoChanged(); // stop recording now so the demo won't have an unnecessary level load at the end. if(cl_autoRecordDemo->integer && clc.demorecording) CL_StopRecord_f(); // reinitialize the filesystem if the game directory has changed if(!cl_oldGameSet && (Cvar_Flags("fs_game") & CVAR_MODIFIED)) { cl_oldGameSet = qtrue; Q_strncpyz(cl_oldGame, oldGame, sizeof(cl_oldGame)); } FS_ConditionalRestart(clc.checksumFeed, qfalse); // This used to call CL_StartHunkUsers, but now we enter the download state before loading the // cgame CL_InitDownloads(); // make sure the game starts Cvar_Set( "cl_paused", "0" ); }
/* ===================== CL_ParseVoip A VoIP message has been received from the server ===================== */ static void CL_ParseVoip ( msg_t *msg, qboolean ignoreData ) { static short decoded[VOIP_MAX_PACKET_SAMPLES*4]; // !!! FIXME: don't hard code const int sender = MSG_ReadShort(msg); const int generation = MSG_ReadByte(msg); const int sequence = MSG_ReadLong(msg); const int frames = MSG_ReadByte(msg); const int packetsize = MSG_ReadShort(msg); const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT); unsigned char encoded[4000]; int numSamples; int seqdiff; int written = 0; int i; Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender); if (sender < 0) return; // short/invalid packet, bail. else if (generation < 0) return; // short/invalid packet, bail. else if (sequence < 0) return; // short/invalid packet, bail. else if (frames < 0) return; // short/invalid packet, bail. else if (packetsize < 0) return; // short/invalid packet, bail. if (packetsize > sizeof (encoded)) { // overlarge packet? int bytesleft = packetsize; while (bytesleft) { int br = bytesleft; if (br > sizeof (encoded)) br = sizeof (encoded); MSG_ReadData(msg, encoded, br); bytesleft -= br; } return; // overlarge packet, bail. } MSG_ReadData(msg, encoded, packetsize); if (ignoreData) { return; // just ignore legacy speex voip data } else if (!clc.voipCodecInitialized) { return; // can't handle VoIP without libopus! } else if (sender >= MAX_CLIENTS) { return; // bogus sender. } else if (CL_ShouldIgnoreVoipSender(sender)) { return; // Channel is muted, bail. } // !!! FIXME: make sure data is narrowband? Does decoder handle this? Com_DPrintf("VoIP: packet accepted!\n"); seqdiff = sequence - clc.voipIncomingSequence[sender]; // This is a new "generation" ... a new recording started, reset the bits. if (generation != clc.voipIncomingGeneration[sender]) { Com_DPrintf("VoIP: new generation %d!\n", generation); opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE); clc.voipIncomingGeneration[sender] = generation; seqdiff = 0; } else if (seqdiff < 0) { // we're ahead of the sequence?! // This shouldn't happen unless the packet is corrupted or something. Com_DPrintf("VoIP: misordered sequence! %d < %d!\n", sequence, clc.voipIncomingSequence[sender]); // reset the decoder just in case. opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE); seqdiff = 0; } else if (seqdiff * VOIP_MAX_PACKET_SAMPLES*2 >= sizeof (decoded)) { // dropped more than we can handle? // just start over. Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n", seqdiff, sender); opus_decoder_ctl(clc.opusDecoder[sender], OPUS_RESET_STATE); seqdiff = 0; } if (seqdiff != 0) { Com_DPrintf("VoIP: Dropped %d frames from client #%d\n", seqdiff, sender); // tell opus that we're missing frames... for (i = 0; i < seqdiff; i++) { assert((written + VOIP_MAX_PACKET_SAMPLES) * 2 < sizeof (decoded)); numSamples = opus_decode(clc.opusDecoder[sender], NULL, 0, decoded + written, VOIP_MAX_PACKET_SAMPLES, 0); if ( numSamples <= 0 ) { Com_DPrintf("VoIP: Error decoding frame %d from client #%d\n", i, sender); continue; } written += numSamples; } } numSamples = opus_decode(clc.opusDecoder[sender], encoded, packetsize, decoded + written, ARRAY_LEN(decoded) - written, 0); if ( numSamples <= 0 ) { Com_DPrintf("VoIP: Error decoding voip data from client #%d\n", sender); numSamples = 0; } #if 0 static FILE *encio = NULL; if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb"); if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); } static FILE *decio = NULL; if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb"); if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); } #endif written += numSamples; Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", written * 2, written, frames); if(written > 0) CL_PlayVoip(sender, written, (const byte *) decoded, flags); clc.voipIncomingSequence[sender] = sequence + frames; }
/* ================= Netchan_Process Returns qfalse if the message should not be processed due to being out of order or a fragment. Msg must be large enough to hold MAX_MSGLEN, because if this is the final fragment of a multi-part message, the entire thing will be copied out. ================= */ qboolean Netchan_Process( netchan_t *chan, msg_t *msg ) { int sequence; int qport; int fragmentStart, fragmentLength; qboolean fragmented; // XOR unscramble all data in the packet after the header // Netchan_UnScramblePacket( msg ); // get sequence numbers MSG_BeginReadingOOB( msg ); sequence = MSG_ReadLong( msg ); // check for fragment information if ( sequence & FRAGMENT_BIT ) { sequence &= ~FRAGMENT_BIT; fragmented = qtrue; } else { fragmented = qfalse; } // read the qport if we are a server if ( chan->sock == NS_SERVER ) { qport = MSG_ReadShort( msg ); } // read the fragment information if ( fragmented ) { fragmentStart = MSG_ReadShort( msg ); fragmentLength = MSG_ReadShort( msg ); } else { fragmentStart = 0; // stop warning message fragmentLength = 0; } if ( showpackets->integer ) { if ( fragmented ) { Com_Printf( "%s recv %4i : s=%i fragment=%i,%i\n" , netsrcString[ chan->sock ] , msg->cursize , sequence , fragmentStart, fragmentLength ); } else { Com_Printf( "%s recv %4i : s=%i\n" , netsrcString[ chan->sock ] , msg->cursize , sequence ); } } // // discard out of order or duplicated packets // if ( sequence <= chan->incomingSequence ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Out of order packet %i at %i\n" , NET_AdrToString( chan->remoteAddress ) , sequence , chan->incomingSequence ); } return qfalse; } // // dropped packets don't keep the message from being used // chan->dropped = sequence - (chan->incomingSequence+1); if ( chan->dropped > 0 ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Dropped %i packets at %i\n" , NET_AdrToString( chan->remoteAddress ) , chan->dropped , sequence ); } } // // if this is the final framgent of a reliable message, // bump incoming_reliable_sequence // if ( fragmented ) { // TTimo // make sure we add the fragments in correct order // either a packet was dropped, or we received this one too soon // we don't reconstruct the fragments. we will wait till this fragment gets to us again // (NOTE: we could probably try to rebuild by out of order chunks if needed) if ( sequence != chan->fragmentSequence ) { chan->fragmentSequence = sequence; chan->fragmentLength = 0; } // if we missed a fragment, dump the message if ( fragmentStart != chan->fragmentLength ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf( "%s:Dropped a message fragment\n" , NET_AdrToString( chan->remoteAddress ) , sequence); } // we can still keep the part that we have so far, // so we don't need to clear chan->fragmentLength return qfalse; } // copy the fragment to the fragment buffer if ( fragmentLength < 0 || msg->readcount + fragmentLength > msg->cursize || chan->fragmentLength + fragmentLength > sizeof( chan->fragmentBuffer ) ) { if ( showdrop->integer || showpackets->integer ) { Com_Printf ("%s:illegal fragment length\n" , NET_AdrToString (chan->remoteAddress ) ); } return qfalse; } Com_Memcpy( chan->fragmentBuffer + chan->fragmentLength, msg->data + msg->readcount, fragmentLength ); chan->fragmentLength += fragmentLength; // if this wasn't the last fragment, don't process anything if ( fragmentLength == FRAGMENT_SIZE ) { return qfalse; } if ( chan->fragmentLength > msg->maxsize ) { Com_Printf( "%s:fragmentLength %i > msg->maxsize\n" , NET_AdrToString (chan->remoteAddress ), chan->fragmentLength ); return qfalse; } // copy the full message over the partial fragment // make sure the sequence number is still there *(int *)msg->data = LittleLong( sequence ); Com_Memcpy( msg->data + 4, chan->fragmentBuffer, chan->fragmentLength ); msg->cursize = chan->fragmentLength + 4; chan->fragmentLength = 0; msg->readcount = 4; // past the sequence number msg->bit = 32; // past the sequence number // TTimo // clients were not acking fragmented messages chan->incomingSequence = sequence; return qtrue; } // // the message can now be read from the current message pointer // chan->incomingSequence = sequence; return qtrue; }
/* ===================== CL_ParseVoip A VoIP message has been received from the server ===================== */ static void CL_ParseVoip ( msg_t *msg ) { static short decoded[4096]; // !!! FIXME: don't hardcode. const int sender = MSG_ReadShort(msg); const int generation = MSG_ReadByte(msg); const int sequence = MSG_ReadLong(msg); const int frames = MSG_ReadByte(msg); const int packetsize = MSG_ReadShort(msg); const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT); char encoded[1024]; int seqdiff = sequence - clc.voipIncomingSequence[sender]; int written = 0; int i; Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender); if (sender < 0) return; // short/invalid packet, bail. else if (generation < 0) return; // short/invalid packet, bail. else if (sequence < 0) return; // short/invalid packet, bail. else if (frames < 0) return; // short/invalid packet, bail. else if (packetsize < 0) return; // short/invalid packet, bail. if (packetsize > sizeof (encoded)) { // overlarge packet? int bytesleft = packetsize; while (bytesleft) { int br = bytesleft; if (br > sizeof (encoded)) br = sizeof (encoded); MSG_ReadData(msg, encoded, br); bytesleft -= br; } return; // overlarge packet, bail. } if (!clc.speexInitialized) { MSG_ReadData(msg, encoded, packetsize); // skip payload. return; // can't handle VoIP without libspeex! } else if (sender >= MAX_CLIENTS) { MSG_ReadData(msg, encoded, packetsize); // skip payload. return; // bogus sender. } else if (CL_ShouldIgnoreVoipSender(sender)) { MSG_ReadData(msg, encoded, packetsize); // skip payload. return; // Channel is muted, bail. } // !!! FIXME: make sure data is narrowband? Does decoder handle this? Com_DPrintf("VoIP: packet accepted!\n"); // This is a new "generation" ... a new recording started, reset the bits. if (generation != clc.voipIncomingGeneration[sender]) { Com_DPrintf("VoIP: new generation %d!\n", generation); speex_bits_reset(&clc.speexDecoderBits[sender]); clc.voipIncomingGeneration[sender] = generation; seqdiff = 0; } else if (seqdiff < 0) { // we're ahead of the sequence?! // This shouldn't happen unless the packet is corrupted or something. Com_DPrintf("VoIP: misordered sequence! %d < %d!\n", sequence, clc.voipIncomingSequence[sender]); // reset the bits just in case. speex_bits_reset(&clc.speexDecoderBits[sender]); seqdiff = 0; } else if (seqdiff > 100) { // more than 2 seconds of audio dropped? // just start over. Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n", seqdiff, sender); speex_bits_reset(&clc.speexDecoderBits[sender]); seqdiff = 0; } if (seqdiff != 0) { Com_DPrintf("VoIP: Dropped %d frames from client #%d\n", seqdiff, sender); // tell speex that we're missing frames... for (i = 0; i < seqdiff; i++) { assert((written + clc.speexFrameSize) * 2 < sizeof (decoded)); speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written); written += clc.speexFrameSize; } } for (i = 0; i < frames; i++) { char encoded[256]; const int len = MSG_ReadByte(msg); if (len < 0) { Com_DPrintf("VoIP: Short packet!\n"); break; } MSG_ReadData(msg, encoded, len); // shouldn't happen, but just in case... if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) { Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", written * 2, written, i); CL_PlayVoip(sender, written, (const byte *) decoded, flags); written = 0; } speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len); speex_decode_int(clc.speexDecoder[sender], &clc.speexDecoderBits[sender], decoded + written); #if 0 static FILE *encio = NULL; if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb"); if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); } static FILE *decio = NULL; if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb"); if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); } #endif written += clc.speexFrameSize; } Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", written * 2, written, i); if(written > 0) CL_PlayVoip(sender, written, (const byte *) decoded, flags); clc.voipIncomingSequence[sender] = sequence + frames; }
/* ================== CL_ParseGamestate ================== */ void CL_ParseGamestate( msg_t *msg ) { int i; entityState_t *es; int newnum; entityState_t nullstate; int cmd; Con_Close(); clc.connectPacketCount = 0; // wipe local client state CL_ClearState(); // a gamestate always marks a server command sequence clc.serverCommandSequence = MSG_ReadLong( msg ); // parse all the configstrings and baselines while ( 1 ) { cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF ) { break; } if ( cmd == svc_configstring ) { i = MSG_ReadShort( msg ); if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); } const char* str = MSG_ReadBigString( msg ); std::string s = str; cl.gameState[i] = str; } else if ( cmd == svc_baseline ) { newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); if ( newnum < 0 || newnum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); } memset( &nullstate, 0, sizeof( nullstate ) ); es = &cl.entityBaselines[ newnum ]; MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); } else { Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); } } clc.clientNum = MSG_ReadLong( msg ); // read the checksum feed clc.checksumFeed = MSG_ReadLong( msg ); // parse serverId and other cvars CL_SystemInfoChanged(); // This used to call CL_StartHunkUsers, but now we enter the download state before loading the // cgame CL_InitDownloads(); // make sure the game starts Cvar_Set( "cl_paused", "0" ); }
/* ===================== CL_ParseDownload A download message has been received from the server ===================== */ void CL_ParseDownload( msg_t *msg ) { int size; unsigned char data[ MAX_MSGLEN ]; int block; if ( !*cls.downloadTempName ) { Com_Printf( "Server sending download, but no download was requested\n" ); // Eat the packet anyway block = MSG_ReadShort( msg ); if (block == -1) { MSG_ReadString( msg ); MSG_ReadLong( msg ); MSG_ReadLong( msg ); } else if (block != 0) { size = MSG_ReadShort( msg ); if ( size < 0 || size > (int) sizeof( data ) ) { Com_Error( ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size ); } MSG_ReadData( msg, data, size ); } CL_AddReliableCommand( "stopdl" ); return; } // read the data block = MSG_ReadShort( msg ); // TTimo - www dl // if we haven't acked the download redirect yet if ( block == -1 ) { if ( !clc.bWWWDl ) { // server is sending us a www download Q_strncpyz( cls.originalDownloadName, cls.downloadName, sizeof( cls.originalDownloadName ) ); Q_strncpyz( cls.downloadName, MSG_ReadString( msg ), sizeof( cls.downloadName ) ); clc.downloadSize = MSG_ReadLong( msg ); clc.downloadFlags = MSG_ReadLong( msg ); downloadLogger.Debug("Server sent us a new WWW DL '%s', size %i, flags %i", cls.downloadName, clc.downloadSize, clc.downloadFlags); Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); clc.bWWWDl = true; // activate wwwdl client loop CL_AddReliableCommand( "wwwdl ack" ); cls.state = CA_DOWNLOADING; // make sure the server is not trying to redirect us again on a bad checksum if ( strstr( clc.badChecksumList, va( "@%s", cls.originalDownloadName ) ) ) { Com_Printf( "refusing redirect to %s by server (bad checksum)\n", cls.downloadName ); CL_AddReliableCommand( "wwwdl fail" ); clc.bWWWDlAborting = true; return; } if ( !DL_BeginDownload( cls.downloadTempName, cls.downloadName ) ) { // setting bWWWDl to false after sending the wwwdl fail doesn't work // not sure why, but I suspect we have to eat all remaining block -1 that the server has sent us // still leave a flag so that CL_WWWDownload is inactive // we count on server sending us a gamestate to start up clean again CL_AddReliableCommand( "wwwdl fail" ); clc.bWWWDlAborting = true; Com_Printf( "Failed to initialize download for '%s'\n", cls.downloadName ); } // Check for a disconnected download // we'll let the server disconnect us when it gets the bbl8r message if ( clc.downloadFlags & ( 1 << DL_FLAG_DISCON ) ) { CL_AddReliableCommand( "wwwdl bbl8r" ); cls.bWWWDlDisconnected = true; } return; } else { // server keeps sending that message till we ack it, eat and ignore //MSG_ReadLong( msg ); MSG_ReadString( msg ); MSG_ReadLong( msg ); MSG_ReadLong( msg ); return; } } if ( !block ) { // block zero is special, contains file size clc.downloadSize = MSG_ReadLong( msg ); downloadLogger.Debug("Starting new direct download of size %i for '%s'", clc.downloadSize, cls.downloadTempName); Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); if ( clc.downloadSize < 0 ) { Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) ); } } size = MSG_ReadShort( msg ); if ( size < 0 || size > (int) sizeof( data ) ) { Com_Error( ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size ); } downloadLogger.Debug("Received block of size %i", size); MSG_ReadData( msg, data, size ); if ( clc.downloadBlock != block ) { downloadLogger.Debug( "CL_ParseDownload: Expected block %i, got %i", clc.downloadBlock, block ); return; } // open the file if not opened yet if ( !clc.download ) { clc.download = FS_SV_FOpenFileWrite( cls.downloadTempName ); if ( !clc.download ) { Com_Printf( "Could not create %s\n", cls.downloadTempName ); CL_AddReliableCommand( "stopdl" ); CL_NextDownload(); return; } } if ( size ) { FS_Write( data, size, clc.download ); } CL_AddReliableCommand( va( "nextdl %d", clc.downloadBlock ) ); clc.downloadBlock++; clc.downloadCount += size; // So UI gets access to it Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); if ( !size ) { downloadLogger.Debug("Received EOF, closing '%s'", cls.downloadTempName); // A zero length block means EOF if ( clc.download ) { FS_FCloseFile( clc.download ); clc.download = 0; // rename the file FS_SV_Rename( cls.downloadTempName, cls.downloadName ); } *cls.downloadTempName = *cls.downloadName = 0; Cvar_Set( "cl_downloadName", "" ); // send intentions now // We need this because without it, we would hold the last nextdl and then start // loading right away. If we take a while to load, the server is happily trying // to send us that last block over and over. // Write it twice to help make sure we acknowledge the download CL_WritePacket(); CL_WritePacket(); // get another file if needed CL_NextDownload(); } }
static qsocket_t *_Datagram_CheckNewConnections (void) { struct qsockaddr clientaddr; struct qsockaddr newaddr; int newsock; int acceptsock; qsocket_t *sock; qsocket_t *s; int len; int command; int control; int ret; acceptsock = dfunc.CheckNewConnections(); if (acceptsock == -1) return NULL; SZ_Clear(&net_message); len = dfunc.Read (acceptsock, net_message.data, net_message.maxsize, &clientaddr); if (len < sizeof(int)) return NULL; net_message.cursize = len; MSG_BeginReading (); control = BigLong(*((int *)net_message.data)); MSG_ReadLong(); if (control == -1) return NULL; if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL) return NULL; if ((control & NETFLAG_LENGTH_MASK) != len) return NULL; command = MSG_ReadByte(); if (command == CCREQ_SERVER_INFO) { if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) return NULL; SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_SERVER_INFO); dfunc.GetSocketAddr(acceptsock, &newaddr); MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); MSG_WriteString(&net_message, hostname.string); MSG_WriteString(&net_message, sv.name); MSG_WriteByte(&net_message, net_activeconnections); MSG_WriteByte(&net_message, svs.maxclients); MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); SZ_Clear(&net_message); return NULL; } if (command == CCREQ_PLAYER_INFO) { int playerNumber; int activeNumber; int clientNumber; client_t *client; playerNumber = MSG_ReadByte(); activeNumber = -1; for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) { if (client->active) { activeNumber++; if (activeNumber == playerNumber) break; } } if (clientNumber == svs.maxclients) return NULL; SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_PLAYER_INFO); MSG_WriteByte(&net_message, playerNumber); MSG_WriteString(&net_message, client->name); MSG_WriteLong(&net_message, client->colors); MSG_WriteLong(&net_message, (int)client->edict->v.frags); MSG_WriteLong(&net_message, (int)(net_time - client->netconnection->connecttime)); MSG_WriteString(&net_message, client->netconnection->address); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); SZ_Clear(&net_message); return NULL; } if (command == CCREQ_RULE_INFO) { char *prevCvarName; cvar_t *var; // find the search start location prevCvarName = MSG_ReadString(); if (*prevCvarName) { var = Cvar_FindVar (prevCvarName); if (!var) return NULL; var = var->next; } else var = cvar_vars; // search for the next server cvar while (var) { if (var->server) break; var = var->next; } // send the response SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_RULE_INFO); if (var) { MSG_WriteString(&net_message, var->name); MSG_WriteString(&net_message, var->string); } *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); SZ_Clear(&net_message); return NULL; } if (command != CCREQ_CONNECT) return NULL; if (Q_strcmp(MSG_ReadString(), "QUAKE") != 0) return NULL; if (MSG_ReadByte() != NET_PROTOCOL_VERSION) return Datagram_Reject ("Incompatible version.\n", acceptsock, &clientaddr); #ifdef BAN_TEST // check for a ban if (clientaddr.sa_family == AF_INET) { unsigned long testAddr; testAddr = ((struct sockaddr_in *)&clientaddr)->sin_addr.s_addr; if ((testAddr & banMask) == banAddr) return Datagram_Reject ("You have been banned.\n", acceptsock, &clientaddr); } #endif // see if this guy is already connected for (s = net_activeSockets; s; s = s->next) { if (s->driver != net_driverlevel) continue; ret = dfunc.AddrCompare(&clientaddr, &s->addr); if (ret >= 0) { // is this a duplicate connection reqeust? if (ret == 0 && net_time - s->connecttime < 2.0) { // yes, so send a duplicate reply SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_ACCEPT); dfunc.GetSocketAddr(s->socket, &newaddr); MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); SZ_Clear(&net_message); return NULL; } // it's somebody coming back in from a crash/disconnect // so close the old qsocket and let their retry get them back in // ProQuake fix // NET_Close(s); // return NULL; } } // allocate a QSocket sock = NET_NewQSocket (); if (sock == NULL) // no room; try to let him know return Datagram_Reject ("Server is full.\n", acceptsock, &clientaddr); // allocate a network socket newsock = dfunc.OpenSocket(0); if (newsock == -1) { NET_FreeQSocket(sock); return NULL; } // connect to the client if (dfunc.Connect (newsock, &clientaddr) == -1) { dfunc.CloseSocket(newsock); NET_FreeQSocket(sock); return NULL; } // everything is allocated, just fill in the details sock->socket = newsock; sock->landriver = net_landriverlevel; sock->addr = clientaddr; Q_strcpy(sock->address, dfunc.AddrToString(&clientaddr)); // send him back the info about the server connection he has been allocated SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREP_ACCEPT); dfunc.GetSocketAddr(newsock, &newaddr); MSG_WriteLong(&net_message, dfunc.GetSocketPort(&newaddr)); // MSG_WriteString(&net_message, dfunc.AddrToString(&newaddr)); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Write (acceptsock, net_message.data, net_message.cursize, &clientaddr); SZ_Clear(&net_message); return sock; }
static qsocket_t *_Datagram_Connect (char *host) { struct qsockaddr sendaddr; struct qsockaddr readaddr; qsocket_t *sock; int newsock; int ret; int reps; double start_time; int control; char *reason; // see if we can resolve the host name if (dfunc.GetAddrFromName(host, &sendaddr) == -1) return NULL; newsock = dfunc.OpenSocket (0); if (newsock == -1) return NULL; sock = NET_NewQSocket (); if (sock == NULL) goto ErrorReturn2; sock->socket = newsock; sock->landriver = net_landriverlevel; // connect to the host if (dfunc.Connect (newsock, &sendaddr) == -1) goto ErrorReturn; // send the connection request Con_Printf("trying...\n"); SCR_UpdateScreen (); start_time = net_time; for (reps = 0; reps < 3; reps++) { SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREQ_CONNECT); MSG_WriteString(&net_message, "QUAKE"); MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Write (newsock, net_message.data, net_message.cursize, &sendaddr); SZ_Clear(&net_message); do { ret = dfunc.Read (newsock, net_message.data, net_message.maxsize, &readaddr); // if we got something, validate it if (ret > 0) { // is it from the right place? if (sfunc.AddrCompare(&readaddr, &sendaddr) != 0) { #ifdef DEBUG Con_Printf("wrong reply address\n"); Con_Printf("Expected: %s\n", StrAddr (&sendaddr)); Con_Printf("Received: %s\n", StrAddr (&readaddr)); SCR_UpdateScreen (); #endif ret = 0; continue; } if (ret < sizeof(int)) { ret = 0; continue; } net_message.cursize = ret; MSG_BeginReading (); control = BigLong(*((int *)net_message.data)); MSG_ReadLong(); if (control == -1) { ret = 0; continue; } if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL) { ret = 0; continue; } if ((control & NETFLAG_LENGTH_MASK) != ret) { ret = 0; continue; } } } while (ret == 0 && (SetNetTime() - start_time) < 2.5); if (ret) break; Con_Printf("still trying...\n"); SCR_UpdateScreen (); start_time = SetNetTime(); } if (ret == 0) { reason = "No Response"; Con_Printf("%s\n", reason); Q_strcpy(m_return_reason, reason); goto ErrorReturn; } if (ret == -1) { reason = "Network Error"; Con_Printf("%s\n", reason); Q_strcpy(m_return_reason, reason); goto ErrorReturn; } ret = MSG_ReadByte(); if (ret == CCREP_REJECT) { reason = MSG_ReadString(); Con_Printf(reason); Q_strncpy(m_return_reason, reason, 31); goto ErrorReturn; } if (ret == CCREP_ACCEPT) { Q_memcpy(&sock->addr, &sendaddr, sizeof(struct qsockaddr)); dfunc.SetSocketPort (&sock->addr, MSG_ReadLong()); } else { reason = "Bad Response"; Con_Printf("%s\n", reason); Q_strcpy(m_return_reason, reason); goto ErrorReturn; } dfunc.GetNameFromAddr (&sendaddr, sock->address); Con_Printf ("Connection accepted\n"); sock->lastMessageTime = SetNetTime(); // switch the connection to the specified address if (dfunc.Connect (newsock, &sock->addr) == -1) { reason = "Connect to Game failed"; Con_Printf("%s\n", reason); Q_strcpy(m_return_reason, reason); goto ErrorReturn; } m_return_onerror = false; return sock; ErrorReturn: NET_FreeQSocket(sock); ErrorReturn2: dfunc.CloseSocket(newsock); if (m_return_onerror) { key_dest = key_menu; m_state = m_return_state; m_return_onerror = false; } return NULL; }
static void _Datagram_SearchForHosts (qboolean xmit) { int ret; int n; int i; struct qsockaddr readaddr; struct qsockaddr myaddr; int control; dfunc.GetSocketAddr (dfunc.controlSock, &myaddr); if (xmit) { SZ_Clear(&net_message); // save space for the header, filled in later MSG_WriteLong(&net_message, 0); MSG_WriteByte(&net_message, CCREQ_SERVER_INFO); MSG_WriteString(&net_message, "QUAKE"); MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION); *((int *)net_message.data) = BigLong(NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK)); dfunc.Broadcast(dfunc.controlSock, net_message.data, net_message.cursize); SZ_Clear(&net_message); } while ((ret = dfunc.Read (dfunc.controlSock, net_message.data, net_message.maxsize, &readaddr)) > 0) { if (ret < sizeof(int)) continue; net_message.cursize = ret; // don't answer our own query if (dfunc.AddrCompare(&readaddr, &myaddr) >= 0) continue; // is the cache full? if (hostCacheCount == HOSTCACHESIZE) continue; MSG_BeginReading (); control = BigLong(*((int *)net_message.data)); MSG_ReadLong(); if (control == -1) continue; if ((control & (~NETFLAG_LENGTH_MASK)) != NETFLAG_CTL) continue; if ((control & NETFLAG_LENGTH_MASK) != ret) continue; if (MSG_ReadByte() != CCREP_SERVER_INFO) continue; dfunc.GetAddrFromName(MSG_ReadString(), &readaddr); // search the cache for this server for (n = 0; n < hostCacheCount; n++) if (dfunc.AddrCompare(&readaddr, &hostcache[n].addr) == 0) break; // is it already there? if (n < hostCacheCount) continue; // add it hostCacheCount++; Q_strcpy(hostcache[n].name, MSG_ReadString()); Q_strcpy(hostcache[n].map, MSG_ReadString()); hostcache[n].users = MSG_ReadByte(); hostcache[n].maxusers = MSG_ReadByte(); if (MSG_ReadByte() != NET_PROTOCOL_VERSION) { Q_strcpy(hostcache[n].cname, hostcache[n].name); hostcache[n].cname[14] = 0; Q_strcpy(hostcache[n].name, "*"); Q_strcat(hostcache[n].name, hostcache[n].cname); } Q_memcpy(&hostcache[n].addr, &readaddr, sizeof(struct qsockaddr)); hostcache[n].driver = net_driverlevel; hostcache[n].ldriver = net_landriverlevel; Q_strcpy(hostcache[n].cname, dfunc.AddrToString(&readaddr)); // check for a name conflict for (i = 0; i < hostCacheCount; i++) { if (i == n) continue; if (Q_strcasecmp (hostcache[n].name, hostcache[i].name) == 0) { i = Q_strlen(hostcache[n].name); if (i < 15 && hostcache[n].name[i-1] > '8') { hostcache[n].name[i] = '0'; hostcache[n].name[i+1] = 0; } else hostcache[n].name[i-1]++; i = -1; } } } }
/* ===================== CL_ParseCommandString Command strings are just saved off until cgame asks for them when it transitions a snapshot ===================== */ void CL_ParseCommandString( msg_t *msg ) { char *s; int seq; int index; seq = MSG_ReadLong( msg ); s = MSG_ReadString( msg ); // see if we have already executed stored it off if ( clc.serverCommandSequence >= seq ) { return; } clc.serverCommandSequence = seq; index = seq & (MAX_RELIABLE_COMMANDS-1); /* if (s[0] == 'c' && s[1] == 's' && s[2] == ' ' && s[3] == '0' && s[4] == ' ') { //yes.. we seem to have an incoming server info. char *f = strstr(s, "g_debugMelee"); if (f) { while (*f && *f != '\\') { //find the \ after it f++; } if (*f == '\\') { //got it int i = 0; f++; while (*f && *f != '\\' && i < 128) { hiddenCvarVal[i] = *f; i++; f++; } hiddenCvarVal[i] = 0; //don't worry about backing over beginning of string I guess, //we already know we successfully strstr'd the initial string //which exceeds this length. //MSG_ReadString appears to just return a static buffer so I //can stomp over its contents safely. f--; *f = '\"'; f--; *f = ' '; f--; *f = '0'; f--; *f = ' '; f--; *f = 's'; f--; *f = 'c'; //the normal configstring gets to start here... s = f; } } } */ Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); }
/* =================== SV_ExecuteClientMessage Parse a client packet =================== */ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { int c; int serverId; MSG_Bitstream(msg); serverId = MSG_ReadLong( msg ); cl->messageAcknowledge = MSG_ReadLong( msg ); if (cl->messageAcknowledge < 0) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG SV_DropClient( cl, "DEBUG: illegible client message" ); #endif return; } cl->reliableAcknowledge = MSG_ReadLong( msg ); // NOTE: when the client message is fux0red the acknowledgement numbers // can be out of range, this could cause the server to send thousands of server // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { // usually only hackers create messages like this // it is more annoying for them to let them hanging #ifndef NDEBUG SV_DropClient( cl, "DEBUG: illegible client message" ); #endif cl->reliableAcknowledge = cl->reliableSequence; return; } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate // // if the client was downloading, let it stay at whatever serverId and // gamestate it was at. This allows it to keep downloading even when // the gamestate changes. After the download is finished, we'll // notice and send it a new game state // // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 // don't drop as long as previous command was a nextdl, after a dl is done, downloadName is set back to "" // but we still need to read the next message to move to next download or send gamestate // I don't like this hack though, it must have been working fine at some point, suspecting the fix is somewhere else if ( serverId != sv.serverId && !*cl->downloadName && !strstr(cl->lastClientCommandString, "nextdl") ) { if ( serverId >= sv.restartedServerId && serverId < sv.serverId ) { // TTimo - use a comparison here to catch multiple map_restart // they just haven't caught the map_restart yet Com_DPrintf("%s : ignoring pre map_restart / outdated client message\n", cl->name); return; } // if we can tell that the client has dropped the last // gamestate we sent them, resend it if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); SV_SendClientGameState( cl ); } return; } // read optional clientCommand strings do { c = MSG_ReadByte( msg ); if ( c == clc_EOF ) { break; } if ( c != clc_clientCommand ) { break; } if ( !SV_ClientCommand( cl, msg ) ) { return; // we couldn't execute it because of the flood protection } if (cl->state == CS_ZOMBIE) { return; // disconnect command } } while ( 1 ); // read the usercmd_t if ( c == clc_move ) { SV_UserMove( cl, msg, qtrue ); } else if ( c == clc_moveNoDelta ) { SV_UserMove( cl, msg, qfalse ); } else if ( c != clc_EOF ) { Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients ); } // if ( msg->readcount != msg->cursize ) { // Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); // } }
/* ================ CL_ParseSnapshot If the snapshot is parsed properly, it will be copied to cl.snap and saved in cl.snapshots[]. If the snapshot is invalid for any reason, no changes to the state will be made at all. ================ */ void CL_ParseSnapshot( msg_t *msg ) { int len; clSnapshot_t *old; clSnapshot_t newSnap; int deltaNum; int oldMessageNum; int i, packetNum; // get the reliable sequence acknowledge number // NOTE: now sent with all server to client messages //clc.reliableAcknowledge = MSG_ReadLong( msg ); // read in the new snapshot to a temporary buffer // we will only copy to cl.snap if it is valid memset( &newSnap, 0, sizeof( newSnap ) ); // we will have read any new server commands in this // message before we got to svc_snapshot newSnap.serverCommandNum = clc.serverCommandSequence; newSnap.serverTime = MSG_ReadLong( msg ); // if we were just unpaused, we can only *now* really let the // change come into effect or the client hangs. cl_paused->modified = 0; newSnap.messageNum = clc.serverMessageSequence; deltaNum = MSG_ReadByte( msg ); if ( !deltaNum ) { newSnap.deltaNum = -1; } else { newSnap.deltaNum = newSnap.messageNum - deltaNum; } newSnap.snapFlags = MSG_ReadByte( msg ); // If the frame is delta compressed from data that we // no longer have available, we must suck up the rest of // the frame, but not use it, then ask for a non-compressed // message if ( newSnap.deltaNum <= 0 ) { newSnap.valid = qtrue; // uncompressed frame old = NULL; clc.demowaiting = qfalse; // we can start recording now } else { old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; if ( !old->valid ) { // should never happen Com_Printf( "Delta from invalid frame (not supposed to happen!).\n" ); } else if ( old->messageNum != newSnap.deltaNum ) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_Printf( "Delta frame too old.\n" ); } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - MAX_SNAPSHOT_ENTITIES ) { Com_Printf( "Delta parseEntitiesNum too old.\n" ); } else { newSnap.valid = qtrue; // valid delta parse } } // read areamask len = MSG_ReadByte( msg ); if(len > sizeof(newSnap.areamask)) { Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask", len); return; } MSG_ReadData( msg, &newSnap.areamask, len ); // read playerinfo SHOWNET( msg, "playerstate" ); if ( old ) { MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); } else { MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); } // read packet entities SHOWNET( msg, "packet entities" ); CL_ParsePacketEntities( msg, old, &newSnap ); // if not valid, dump the entire thing now that it has // been properly read if ( !newSnap.valid ) { return; } // clear the valid flags of any snapshots between the last // received and this one, so if there was a dropped packet // it won't look like something valid to delta from next // time we wrap around in the buffer oldMessageNum = cl.snap.messageNum + 1; if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); } for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; } // copy to the current good spot cl.snap = newSnap; cl.snap.ping = 999; // calculate ping time for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; break; } } // save the frame off in the backup array for later delta comparisons cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; if ( cl_shownet->integer == 3 ) { Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, cl.snap.deltaNum, cl.snap.ping ); } cl.newSnapshots = qtrue; }
// // CL_Download // denis - get a little chunk of the file and store it, much like a hampster. Well, hamster; but hampsters can dance and sing. Also much like Scraps, the Ice Age squirrel thing, stores his acorn. Only with a bit more success. Actually, quite a bit more success, specifically as in that the world doesn't crack apart when we store our chunk and it does when Scraps stores his (or her?) acorn. But when Scraps does it, it is funnier. The rest of Ice Age mostly sucks. // void CL_Download() { DWORD offset = MSG_ReadLong(); size_t len = MSG_ReadShort(); size_t left = MSG_BytesLeft(); void *p = MSG_ReadChunk(len); if(gamestate != GS_DOWNLOAD) return; if (download.buf == NULL) { // We must have not received the svc_wadinfo message Printf(PRINT_HIGH, "Unable to start download, aborting\n"); download.clear(); CL_QuitNetGame(); return; } // check ranges if(offset + len > download.buf->maxsize() || len > left || p == NULL) { Printf(PRINT_HIGH, "Bad download packet (%d, %d) encountered (%d), aborting\n", (int)offset, (int)left, (int)download.buf->size()); download.clear(); CL_QuitNetGame(); return; } // check for missing packet, re-request if(offset < download.got_bytes || offset > download.got_bytes) { DPrintf("Missed a packet after/before %d bytes (got %d), re-requesting\n", download.got_bytes, offset); MSG_WriteMarker(&net_buffer, clc_wantwad); MSG_WriteString(&net_buffer, download.filename.c_str()); MSG_WriteString(&net_buffer, download.md5.c_str()); MSG_WriteLong(&net_buffer, download.got_bytes); NET_SendPacket(net_buffer, serveraddr); return; } // send keepalive NET_SendPacket(net_buffer, serveraddr); // copy into downloaded buffer memcpy(download.buf->ptr() + offset, p, len); download.got_bytes += len; // calculate percentage for the user static int old_percent = 0; int percent = (download.got_bytes*100)/download.buf->maxsize(); if(percent != old_percent) { SetDownloadPercentage(percent); old_percent = percent; } // check for completion // [Russell] - We go over the boundary, because sometimes the download will // pause at 100% if the server disconnected you previously, you can // reconnect a couple of times and this will let the checksum system do its // work if(download.got_bytes >= download.buf->maxsize()) { IntDownloadComplete(); } }
/* ===================== CL_ParseDownload A download message has been received from the server ===================== */ void CL_ParseDownload( msg_t *msg ) { int size; unsigned char data[MAX_MSGLEN]; uint16_t block; if (!*clc.downloadTempName) { Com_Printf("Server sending download, but no download was requested\n"); CL_AddReliableCommand("stopdl", qfalse); return; } // read the data block = MSG_ReadShort( msg ); if(!block && !clc.downloadBlock) { // block zero is special, contains file size clc.downloadSize = MSG_ReadLong( msg ); Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); if ( clc.downloadSize < 0 ) { Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) ); return; } } size = MSG_ReadShort( msg ); if (size < 0 || size > sizeof(data)) { Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk", size); return; } MSG_ReadData(msg, data, size); if((clc.downloadBlock & 0xFFFF) != block) { Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", (clc.downloadBlock & 0xFFFF), block); return; } // open the file if not opened yet if ( !clc.download ) { clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); if ( !clc.download ) { Com_Printf( "Could not create %s\n", clc.downloadTempName ); CL_AddReliableCommand("stopdl", qfalse); CL_NextDownload(); return; } } if ( size ) { FS_Write( data, size, clc.download ); } CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse); clc.downloadBlock++; clc.downloadCount += size; // So UI gets access to it Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); if ( !size ) { // A zero length block means EOF if ( clc.download ) { FS_FCloseFile( clc.download ); clc.download = 0; // rename the file FS_SV_Rename( clc.downloadTempName, clc.downloadName, qfalse ); } // send intentions now // We need this because without it, we would hold the last nextdl and then start // loading right away. If we take a while to load, the server is happily trying // to send us that last block over and over. // Write it twice to help make sure we acknowledge the download CL_WritePacket(); CL_WritePacket(); // get another file if needed CL_NextDownload(); } }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage(msg_t *msg) { int cmd; if (cl_shownet->integer == 1) { Com_Printf("%i ", msg->cursize); } else if (cl_shownet->integer >= 2) { Com_Printf("------------------\n"); } MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong(msg); if (clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS) { clc.reliableAcknowledge = clc.reliableSequence; } // parse the message while (1) { if (msg->readcount > msg->cursize) { Com_Error(ERR_DROP, "CL_ParseServerMessage: read past end of server message"); break; } cmd = MSG_ReadByte(msg); if (cmd == svc_EOF) { SHOWNET(msg, "END OF MESSAGE"); break; } if (cl_shownet->integer >= 2) { if (cmd < 0 || cmd > svc_EOF) // MSG_ReadByte might return -1 and we can't access our svc_strings array ... { Com_Printf("%3i:BAD BYTE %i\n", msg->readcount - 1, cmd); // -> ERR_DROP } else { if (!svc_strings[cmd]) { Com_Printf("%3i:BAD CMD %i\n", msg->readcount - 1, cmd); } else { SHOWNET(msg, svc_strings[cmd]); } } } // other commands switch (cmd) { default: Com_Error(ERR_DROP, "CL_ParseServerMessage: Illegible server message %d", cmd); break; case svc_nop: break; case svc_serverCommand: CL_ParseCommandString(msg); break; case svc_gamestate: CL_ParseGamestate(msg); break; case svc_snapshot: CL_ParseSnapshot(msg); break; case svc_download: CL_ParseDownload(msg); break; } } CL_ParseBinaryMessage(msg); }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; if ( cl_shownet->integer == 1 ) { Com_Printf( "%i ",msg->cursize ); } else if ( cl_shownet->integer >= 2 ) { Com_Printf( "------------------\n" ); } MSG_Bitstream( msg ); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong( msg ); // if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { clc.reliableAcknowledge = clc.reliableSequence; } // // parse the message // while ( 1 ) { if ( msg->readcount > msg->cursize ) { Com_Error( ERR_DROP,"CL_ParseServerMessage: read past end of server message" ); break; } cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF ) { SHOWNET( msg, "END OF MESSAGE" ); break; } if ( cl_shownet->integer >= 2 ) { if ( (cmd < 0) || (!svc_strings[cmd]) ) { Com_Printf( "%3i:BAD CMD %i\n", msg->readcount - 1, cmd ); } else { SHOWNET( msg, svc_strings[cmd] ); } } // other commands switch ( cmd ) { default: Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message"); break; case svc_nop: break; case svc_serverCommand: CL_ParseCommandString( msg ); break; case svc_gamestate: CL_ParseGamestate( msg ); break; case svc_snapshot: CL_ParseSnapshot( msg ); break; case svc_download: CL_ParseDownload( msg ); break; case svc_voipSpeex: #ifdef USE_VOIP CL_ParseVoip( msg, qtrue ); #endif break; case svc_voipOpus: #ifdef USE_VOIP CL_ParseVoip( msg, !clc.voipEnabled ); #endif break; } } }
/** * @brief If the snapshot is parsed properly, it will be copied to * cl.snap and saved in cl.snapshots[]. If the snapshot is invalid * for any reason, no changes to the state will be made at all. */ void CL_ParseSnapshot(msg_t *msg) { int len; clSnapshot_t *old; clSnapshot_t newSnap; int deltaNum; int oldMessageNum; int i, packetNum; // get the reliable sequence acknowledge number // NOTE: now sent with all server to client messages //clc.reliableAcknowledge = MSG_ReadLong( msg ); // read in the new snapshot to a temporary buffer // we will only copy to cl.snap if it is valid memset(&newSnap, 0, sizeof(newSnap)); // we will have read any new server commands in this // message before we got to svc_snapshot newSnap.serverCommandNum = clc.serverCommandSequence; newSnap.serverTime = MSG_ReadLong(msg); newSnap.messageNum = clc.serverMessageSequence; deltaNum = MSG_ReadByte(msg); if (!deltaNum) { newSnap.deltaNum = -1; } else { newSnap.deltaNum = newSnap.messageNum - deltaNum; } newSnap.snapFlags = MSG_ReadByte(msg); // If the frame is delta compressed from data that we // no longer have available, we must suck up the rest of // the frame, but not use it, then ask for a non-compressed // message if (newSnap.deltaNum <= 0) { newSnap.valid = qtrue; // uncompressed frame old = NULL; if (clc.demorecording) { clc.demowaiting = qfalse; // we can start recording now //if(cl_autorecord->integer) { // Cvar_Set( "g_synchronousClients", "0" ); //} } else { if (cl_autorecord->integer /*&& Cvar_VariableValue( "g_synchronousClients")*/) { char name[256]; char mapname[MAX_QPATH]; char *period; qtime_t time; Com_RealTime(&time); Q_strncpyz(mapname, cl.mapname, MAX_QPATH); for (period = mapname; *period; period++) { if (*period == '.') { *period = '\0'; break; } } for (period = mapname; *period; period++) { if (*period == '/') { break; } } if (*period) { period++; } Com_sprintf(name, sizeof(name), "demos/%s_%i_%i.dm_%d", period, time.tm_mday, time.tm_mon + 1, PROTOCOL_VERSION); CL_Record(name); } } } else { old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; if (!old->valid) { // should never happen Com_Printf("Delta from invalid frame (not supposed to happen!).\n"); } else if (old->messageNum != newSnap.deltaNum) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_DPrintf("Delta frame too old.\n"); } else if (cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES - 128) { Com_DPrintf("Delta parseEntitiesNum too old.\n"); } else { newSnap.valid = qtrue; // valid delta parse } } // read areamask len = MSG_ReadByte(msg); if (len < 0 || len > sizeof(newSnap.areamask)) { Com_Error(ERR_DROP, "CL_ParseSnapshot: Invalid size %d for areamask.", len); return; } MSG_ReadData(msg, &newSnap.areamask, len); // read playerinfo SHOWNET(msg, "playerstate"); if (old) { MSG_ReadDeltaPlayerstate(msg, &old->ps, &newSnap.ps); } else { MSG_ReadDeltaPlayerstate(msg, NULL, &newSnap.ps); } // read packet entities SHOWNET(msg, "packet entities"); CL_ParsePacketEntities(msg, old, &newSnap); // if not valid, dump the entire thing now that it has // been properly read if (!newSnap.valid) { return; } // clear the valid flags of any snapshots between the last // received and this one, so if there was a dropped packet // it won't look like something valid to delta from next // time we wrap around in the buffer oldMessageNum = cl.snap.messageNum + 1; if (newSnap.messageNum - oldMessageNum >= PACKET_BACKUP) { oldMessageNum = newSnap.messageNum - (PACKET_BACKUP - 1); } for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++) { cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; } // copy to the current good spot cl.snap = newSnap; cl.snap.ping = 999; // calculate ping time for (i = 0 ; i < PACKET_BACKUP ; i++) { packetNum = (clc.netchan.outgoingSequence - 1 - i) & PACKET_MASK; if (cl.snap.ps.commandTime >= cl.outPackets[packetNum].p_serverTime) { cl.snap.ping = cls.realtime - cl.outPackets[packetNum].p_realtime; break; } } // save the frame off in the backup array for later delta comparisons cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; if (cl_shownet->integer == 3) { Com_Printf(" snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, cl.snap.deltaNum, cl.snap.ping); } cl.newSnapshots = qtrue; }
/* ================== CL_ParseServerInfo ================== */ void CL_ParseServerInfo (void) { const char *str; int i; int nummodels, numsounds; char model_precache[MAX_MODELS][MAX_QPATH]; char sound_precache[MAX_SOUNDS][MAX_QPATH]; Con_DPrintf ("Serverinfo packet received.\n"); // // wipe the client_state_t struct // CL_ClearState (); // parse protocol version number i = MSG_ReadLong (); //johnfitz -- support multiple protocols if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE) { Con_Printf ("\n"); //becuase there's no newline after serverinfo print Host_Error ("Server returned version %i, not %i or %i\n", i, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE); } cl.protocol = i; //johnfitz // parse maxclients cl.maxclients = MSG_ReadByte (); if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) { Con_Printf("Bad maxclients (%u) from server\n", cl.maxclients); return; } cl.scores = (scoreboard_t *) Hunk_AllocName (cl.maxclients*sizeof(*cl.scores), "scores"); // parse gametype cl.gametype = MSG_ReadByte (); // parse signon message str = MSG_ReadString (); q_strlcpy (cl.levelname, str, sizeof(cl.levelname)); // seperate the printfs so the server message can have a color Con_Printf ("\n%s\n", Con_Quakebar(40)); //johnfitz Con_Printf ("%c%s\n", 2, str); //johnfitz -- tell user which protocol this is Con_Printf ("Using protocol %i\n", i); // first we go through and touch all of the precache data that still // happens to be in the cache, so precaching something else doesn't // needlessly purge it // precache models memset (cl.model_precache, 0, sizeof(cl.model_precache)); for (nummodels = 1 ; ; nummodels++) { str = MSG_ReadString (); if (!str[0]) break; if (nummodels==MAX_MODELS) { Con_Printf ("Server sent too many model precaches\n"); return; } q_strlcpy (model_precache[nummodels], str, MAX_QPATH); Mod_TouchModel (str); } //johnfitz -- check for excessive models if (nummodels >= 256) Con_Warning ("%i models exceeds standard limit of 256.\n", nummodels); //johnfitz // precache sounds memset (cl.sound_precache, 0, sizeof(cl.sound_precache)); for (numsounds = 1 ; ; numsounds++) { str = MSG_ReadString (); if (!str[0]) break; if (numsounds==MAX_SOUNDS) { Con_Printf ("Server sent too many sound precaches\n"); return; } q_strlcpy (sound_precache[numsounds], str, MAX_QPATH); S_TouchSound (str); } //johnfitz -- check for excessive sounds if (numsounds >= 256) Con_Warning ("%i sounds exceeds standard limit of 256.\n", numsounds); //johnfitz // // now we try to load everything else until a cache allocation fails // // copy the naked name of the map file to the cl structure -- O.S COM_StripExtension (COM_SkipPath(model_precache[1]), cl.mapname, sizeof(cl.mapname)); for (i = 1; i < nummodels; i++) { cl.model_precache[i] = Mod_ForName (model_precache[i], false); if (cl.model_precache[i] == NULL) { Con_Printf("Model %s not found\n", model_precache[i]); return; } CL_KeepaliveMessage (); } S_BeginPrecaching (); for (i = 1; i < numsounds; i++) { cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]); CL_KeepaliveMessage (); } S_EndPrecaching (); // local state cl_entities[0].model = cl.worldmodel = cl.model_precache[1]; R_NewMap (); //johnfitz -- clear out string; we don't consider identical //messages to be duplicates if the map has changed in between con_lastcenterstring[0] = 0; //johnfitz Hunk_Check (); // make sure nothing is hurt noclip_anglehack = false; // noclip is turned off at start warn_about_nehahra_protocol = true; //johnfitz -- warn about nehahra protocol hack once per server connection //johnfitz -- reset developer stats memset(&dev_stats, 0, sizeof(dev_stats)); memset(&dev_peakstats, 0, sizeof(dev_peakstats)); memset(&dev_overflows, 0, sizeof(dev_overflows)); }
/* ================== CL_ParseGamestate ================== */ void CL_ParseGamestate(msg_t *msg) { int i; entityState_t *es; int newnum; entityState_t nullstate; int cmd; char *s; // Con_Close(); clc.connectPacketCount = 0; // wipe local client state CL_ClearState(); // a gamestate always marks a server command sequence clc.serverCommandSequence = MSG_ReadLong(msg); // parse all the configstrings and baselines cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings while (1) { cmd = MSG_ReadByte(msg); if (cmd == svc_EOF) { break; } if (cmd == svc_configstring) { int len; i = MSG_ReadShort(msg); if (i < 0 || i >= MAX_CONFIGSTRINGS) { Com_Error(ERR_DROP, "configstring < 0 or configstring >= MAX_CONFIGSTRINGS"); } s = MSG_ReadBigString(msg); len = strlen(s); if (len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS) { Com_Error(ERR_DROP, "MAX_GAMESTATE_CHARS exceeded"); } // append it to the gameState string buffer cl.gameState.stringOffsets[i] = cl.gameState.dataCount; memcpy(cl.gameState.stringData + cl.gameState.dataCount, s, len + 1); cl.gameState.dataCount += len + 1; } else if (cmd == svc_baseline) { newnum = MSG_ReadBits(msg, GENTITYNUM_BITS); if (newnum < 0 || newnum >= MAX_GENTITIES) { Com_Error(ERR_DROP, "Baseline number out of range: %i", newnum); } memset(&nullstate, 0, sizeof(nullstate)); es = &cl.entityBaselines[newnum]; MSG_ReadDeltaEntity(msg, &nullstate, es, newnum); } else { Com_Error(ERR_DROP, "CL_ParseGamestate: bad command byte"); } } clc.clientNum = MSG_ReadLong(msg); // read the checksum feed clc.checksumFeed = MSG_ReadLong(msg); // parse serverId and other cvars CL_SystemInfoChanged(); // Verify if we have all official pakfiles. As we won't // be downloading them, we should be kicked for not having them. if (cl_connectedToPureServer && !FS_VerifyOfficialPaks()) { Com_Error(ERR_DROP, "Couldn't load an official pak file; verify your installation and make sure it has been updated to the latest version."); } // reinitialize the filesystem if the game directory has changed FS_ConditionalRestart(clc.checksumFeed); // This used to call CL_StartHunkUsers, but now we enter the download state before loading the // cgame Com_InitDownloads(); // make sure the game starts Cvar_Set("cl_paused", "0"); }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage (void) { int cmd; int i; const char *str; //johnfitz int total, j, lastcmd; //johnfitz // // if recording demos, copy the message out // if (cl_shownet.value == 1) Con_Printf ("%i ",net_message.cursize); else if (cl_shownet.value == 2) Con_Printf ("------------------\n"); cl.onground = false; // unless the server says otherwise // // parse the message // MSG_BeginReading (); lastcmd = 0; while (1) { if (msg_badread) Host_Error ("CL_ParseServerMessage: Bad server message"); cmd = MSG_ReadByte (); if (cmd == -1) { SHOWNET("END OF MESSAGE"); return; // end of message } // if the high bit of the command byte is set, it is a fast update if (cmd & U_SIGNAL) //johnfitz -- was 128, changed for clarity { SHOWNET("fast update"); CL_ParseUpdate (cmd&127); continue; } SHOWNET(svc_strings[cmd]); // other commands switch (cmd) { default: Host_Error ("Illegible server message, previous was %s\n", svc_strings[lastcmd]); //johnfitz -- added svc_strings[lastcmd] break; case svc_nop: // Con_Printf ("svc_nop\n"); break; case svc_time: cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = MSG_ReadFloat (); break; case svc_clientdata: CL_ParseClientdata (); //johnfitz -- removed bits parameter, we will read this inside CL_ParseClientdata() break; case svc_version: i = MSG_ReadLong (); //johnfitz -- support multiple protocols if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE) Host_Error ("Server returned version %i, not %i or %i\n", i, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE); cl.protocol = i; //johnfitz break; case svc_disconnect: Host_EndGame ("Server disconnected\n"); case svc_print: Con_Printf ("%s", MSG_ReadString ()); break; case svc_centerprint: //johnfitz -- log centerprints to console str = MSG_ReadString (); SCR_CenterPrint (str); Con_LogCenterPrint (str); //johnfitz break; case svc_stufftext: cls.stufftext_frame = host_framecount; // allow full frame update // in demo playback -- Pa3PyX Cbuf_AddText (MSG_ReadString ()); break; case svc_damage: V_ParseDamage (); break; case svc_serverinfo: CL_ParseServerInfo (); vid.recalc_refdef = true; // leave intermission full screen break; case svc_setangle: for (i=0 ; i<3 ; i++) cl.viewangles[i] = MSG_ReadAngle (); break; case svc_setview: cl.viewentity = MSG_ReadShort (); break; case svc_lightstyle: i = MSG_ReadByte (); if (i >= MAX_LIGHTSTYLES) Sys_Error ("svc_lightstyle > MAX_LIGHTSTYLES"); q_strlcpy (cl_lightstyle[i].map, MSG_ReadString(), MAX_STYLESTRING); cl_lightstyle[i].length = Q_strlen(cl_lightstyle[i].map); //johnfitz -- save extra info if (cl_lightstyle[i].length) { total = 0; cl_lightstyle[i].peak = 'a'; for (j=0; j<cl_lightstyle[i].length; j++) { total += cl_lightstyle[i].map[j] - 'a'; cl_lightstyle[i].peak = q_max(cl_lightstyle[i].peak, cl_lightstyle[i].map[j]); } cl_lightstyle[i].average = total / cl_lightstyle[i].length + 'a'; } else cl_lightstyle[i].average = cl_lightstyle[i].peak = 'm'; //johnfitz break; case svc_sound: CL_ParseStartSoundPacket(); break; case svc_stopsound: i = MSG_ReadShort(); S_StopSound(i>>3, i&7); break; case svc_updatename: Sbar_Changed (); i = MSG_ReadByte (); if (i >= cl.maxclients) Host_Error ("CL_ParseServerMessage: svc_updatename > MAX_SCOREBOARD"); q_strlcpy (cl.scores[i].name, MSG_ReadString(), MAX_SCOREBOARDNAME); break; case svc_updatefrags: Sbar_Changed (); i = MSG_ReadByte (); if (i >= cl.maxclients) Host_Error ("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD"); cl.scores[i].frags = MSG_ReadShort (); break; case svc_updatecolors: Sbar_Changed (); i = MSG_ReadByte (); if (i >= cl.maxclients) Host_Error ("CL_ParseServerMessage: svc_updatecolors > MAX_SCOREBOARD"); cl.scores[i].colors = MSG_ReadByte (); CL_NewTranslation (i); break; case svc_particle: R_ParseParticleEffect (); break; case svc_spawnbaseline: i = MSG_ReadShort (); // must use CL_EntityNum() to force cl.num_entities up CL_ParseBaseline (CL_EntityNum(i), 1); // johnfitz -- added second parameter break; case svc_spawnstatic: CL_ParseStatic (1); //johnfitz -- added parameter break; case svc_temp_entity: CL_ParseTEnt (); break; case svc_setpause: cl.paused = MSG_ReadByte (); if (cl.paused) { CDAudio_Pause (); BGM_Pause (); } else { CDAudio_Resume (); BGM_Resume (); } break; case svc_signonnum: i = MSG_ReadByte (); if (i <= cls.signon) Host_Error ("Received signon %i when at %i", i, cls.signon); cls.signon = i; //johnfitz -- if signonnum==2, signon packet has been fully parsed, so check for excessive static ents and efrags if (i == 2) { if (cl.num_statics > 128) Con_Warning ("%i static entities exceeds standard limit of 128.\n", cl.num_statics); R_CheckEfrags (); } //johnfitz CL_SignonReply (); break; case svc_killedmonster: cl.stats[STAT_MONSTERS]++; break; case svc_foundsecret: cl.stats[STAT_SECRETS]++; break; case svc_updatestat: i = MSG_ReadByte (); if (i < 0 || i >= MAX_CL_STATS) Sys_Error ("svc_updatestat: %i is invalid", i); cl.stats[i] = MSG_ReadLong ();; break; case svc_spawnstaticsound: CL_ParseStaticSound (1); //johnfitz -- added parameter break; case svc_cdtrack: cl.cdtrack = MSG_ReadByte (); cl.looptrack = MSG_ReadByte (); if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) BGM_PlayCDtrack ((byte)cls.forcetrack, true); else BGM_PlayCDtrack ((byte)cl.cdtrack, true); break; case svc_intermission: cl.intermission = 1; cl.completed_time = cl.time; vid.recalc_refdef = true; // go to full screen break; case svc_finale: cl.intermission = 2; cl.completed_time = cl.time; vid.recalc_refdef = true; // go to full screen //johnfitz -- log centerprints to console str = MSG_ReadString (); SCR_CenterPrint (str); Con_LogCenterPrint (str); //johnfitz break; case svc_cutscene: cl.intermission = 3; cl.completed_time = cl.time; vid.recalc_refdef = true; // go to full screen //johnfitz -- log centerprints to console str = MSG_ReadString (); SCR_CenterPrint (str); Con_LogCenterPrint (str); //johnfitz break; case svc_sellscreen: Cmd_ExecuteString ("help", src_command); break; //johnfitz -- new svc types case svc_skybox: Sky_LoadSkyBox (MSG_ReadString()); break; case svc_bf: Cmd_ExecuteString ("bf", src_command); break; case svc_fog: Fog_ParseServerMessage (); break; case svc_spawnbaseline2: //PROTOCOL_FITZQUAKE i = MSG_ReadShort (); // must use CL_EntityNum() to force cl.num_entities up CL_ParseBaseline (CL_EntityNum(i), 2); break; case svc_spawnstatic2: //PROTOCOL_FITZQUAKE CL_ParseStatic (2); break; case svc_spawnstaticsound2: //PROTOCOL_FITZQUAKE CL_ParseStaticSound (2); break; //johnfitz } lastcmd = cmd; //johnfitz } }
/** * @brief A download message has been received from the server */ void CL_ParseDownload(msg_t *msg) { int size; unsigned char data[MAX_MSGLEN]; int block; if (!*cls.download.downloadTempName) { Com_Printf("Server sending download, but no download was requested\n"); CL_AddReliableCommand("stopdl"); return; } // read the data block = MSG_ReadShort(msg); // unfortunately DLTYPE_WWW is -1 FIXME: change this someday! //if (block < 0) //{ //Com_Error(ERR_DROP, "CL_ParseDownload: Server sending invalid download data"); //} // www dl, if we haven't acknowledged the download redirect yet if (block == DLTYPE_WWW) { if (!cls.download.bWWWDl) { // server is sending us a www download Q_strncpyz(cls.download.originalDownloadName, cls.download.downloadName, sizeof(cls.download.originalDownloadName)); Q_strncpyz(cls.download.downloadName, MSG_ReadString(msg), sizeof(cls.download.downloadName)); cls.download.downloadSize = MSG_ReadLong(msg); cls.download.downloadFlags = MSG_ReadLong(msg); if (cls.download.downloadFlags & (1 << DL_FLAG_URL)) { Sys_OpenURL(cls.download.downloadName, qtrue); Cbuf_ExecuteText(EXEC_APPEND, "quit\n"); CL_AddReliableCommand("wwwdl bbl8r"); // not sure if that's the right msg cls.download.bWWWDlAborting = qtrue; return; } Cvar_SetValue("cl_downloadSize", cls.download.downloadSize); Com_DPrintf("Server redirected download: %s\n", cls.download.downloadName); cls.download.bWWWDl = qtrue; // activate wwwdl client loop CL_AddReliableCommand("wwwdl ack"); // make sure the server is not trying to redirect us again on a bad checksum if (strstr(cls.download.badChecksumList, va("@%s", cls.download.originalDownloadName))) { Com_Printf("refusing redirect to %s by server (bad checksum)\n", cls.download.downloadName); CL_AddReliableCommand("wwwdl fail"); cls.download.bWWWDlAborting = qtrue; return; } // make downloadTempName an OS path Q_strncpyz(cls.download.downloadTempName, FS_BuildOSPath(Cvar_VariableString("fs_homepath"), cls.download.downloadTempName, ""), sizeof(cls.download.downloadTempName)); cls.download.downloadTempName[strlen(cls.download.downloadTempName) - 1] = '\0'; if (!DL_BeginDownload(cls.download.downloadTempName, cls.download.downloadName)) { // setting bWWWDl to false after sending the wwwdl fail doesn't work // not sure why, but I suspect we have to eat all remaining block -1 that the server has sent us // still leave a flag so that CL_WWWDownload is inactive // we count on server sending us a gamestate to start up clean again CL_AddReliableCommand("wwwdl fail"); cls.download.bWWWDlAborting = qtrue; Com_Printf("Failed to initialize download for '%s'\n", cls.download.downloadName); } // Check for a disconnected download // we'll let the server disconnect us when it gets the bbl8r message if (cls.download.downloadFlags & (1 << DL_FLAG_DISCON)) { CL_AddReliableCommand("wwwdl bbl8r"); cls.download.bWWWDlDisconnected = qtrue; } return; } else { // server keeps sending that message till we acknowledge it, eat and ignore //MSG_ReadLong( msg ); MSG_ReadString(msg); MSG_ReadLong(msg); MSG_ReadLong(msg); return; } } if (!block) { // block zero is special, contains file size cls.download.downloadSize = MSG_ReadLong(msg); Cvar_SetValue("cl_downloadSize", cls.download.downloadSize); if (cls.download.downloadSize < 0) { Com_Error(ERR_DROP, "%s", MSG_ReadString(msg)); return; } } size = MSG_ReadShort(msg); if (size < 0 || size > sizeof(data)) { Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk.", size); return; } MSG_ReadData(msg, data, size); if (cls.download.downloadBlock != block) { Com_DPrintf("CL_ParseDownload: Expected block %d, got %d\n", cls.download.downloadBlock, block); return; } // open the file if not opened yet if (!cls.download.download) { cls.download.download = FS_SV_FOpenFileWrite(cls.download.downloadTempName); if (!cls.download.download) { Com_Printf("Could not create %s\n", cls.download.downloadTempName); CL_AddReliableCommand("stopdl"); Com_NextDownload(); return; } } if (size) { FS_Write(data, size, cls.download.download); } CL_AddReliableCommand(va("nextdl %d", cls.download.downloadBlock)); cls.download.downloadBlock++; cls.download.downloadCount += size; // So UI gets access to it Cvar_SetValue("cl_downloadCount", cls.download.downloadCount); if (!size) // A zero length block means EOF { if (cls.download.download) { FS_FCloseFile(cls.download.download); cls.download.download = 0; // rename the file FS_SV_Rename(cls.download.downloadTempName, cls.download.downloadName); } *cls.download.downloadTempName = *cls.download.downloadName = 0; Cvar_Set("cl_downloadName", ""); // send intentions now // We need this because without it, we would hold the last nextdl and then start // loading right away. If we take a while to load, the server is happily trying // to send us that last block over and over. // Write it twice to help make sure we acknowledge the download CL_WritePacket(); CL_WritePacket(); // get another file if needed Com_NextDownload(); } }
/* ================ CL_ParseFrame ================ */ void CL_ParseFrame (void) { int cmd; int len; frame_t *old; memset (&cl.frame, 0, sizeof(cl.frame)); cl.frame.serverframe = MSG_ReadLong(&net_message); cl.frame.deltaframe = MSG_ReadLong(&net_message); cl.frame.servertime = cl.frame.serverframe*100; // BIG HACK to let old demos continue to work if (cls.serverProtocol != 26) cl.surpressCount = MSG_ReadByte(&net_message); if (cl_shownet->value == 3) Com_Printf (" frame:%i delta:%i\n", cl.frame.serverframe, cl.frame.deltaframe); // If the frame is delta compressed from data that we // no longer have available, we must suck up the rest of // the frame, but not use it, then ask for a non-compressed // message if (cl.frame.deltaframe <= 0) { cl.frame.valid = true; // uncompressed frame old = NULL; cls.demowaiting = false; // we can start recording now } else { old = &cl.frames[cl.frame.deltaframe & UPDATE_MASK]; if (!old->valid) { // should never happen Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); } if (old->serverframe != cl.frame.deltaframe) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_Printf ("Delta frame too old.\n"); } else if (cl.parse_entities - old->parse_entities > MAX_PARSE_ENTITIES-128) { Com_Printf ("Delta parse_entities too old.\n"); } else { cl.frame.valid = true; // valid delta parse } } // clamp time if (cl.time > cl.frame.servertime) cl.time = cl.frame.servertime; else if (cl.time < cl.frame.servertime - 100) cl.time = cl.frame.servertime - 100; // read areabits len = MSG_ReadByte(&net_message); MSG_ReadData(&net_message, &cl.frame.areabits, len); // read playerinfo cmd = MSG_ReadByte(&net_message); SHOWNET(svc_strings[cmd]); if (cmd != svc_playerinfo) Com_Error(ERR_DROP, "CL_ParseFrame: not playerinfo"); CL_ParsePlayerstate(old, &cl.frame); // read packet entities cmd = MSG_ReadByte(&net_message); SHOWNET(svc_strings[cmd]); if (cmd != svc_packetentities) Com_Error(ERR_DROP, "CL_ParseFrame: not packetentities"); CL_ParsePacketEntities(old, &cl.frame); // save the frame off in the backup array for later delta comparisons cl.frames[cl.frame.serverframe & UPDATE_MASK] = cl.frame; if (cl.frame.valid) { // getting a valid frame message ends the connection process if (cls.state != ca_active) { cls.state = ca_active; //cl_scores_setinuse_all(false); // jitscores - clear scoreboard cl.force_refdef = true; cl.predicted_origin[0] = cl.frame.playerstate.pmove.origin[0]*0.125; cl.predicted_origin[1] = cl.frame.playerstate.pmove.origin[1]*0.125; cl.predicted_origin[2] = cl.frame.playerstate.pmove.origin[2]*0.125; VectorCopy(cl.frame.playerstate.viewangles, cl.predicted_angles); if (cls.disable_servercount != cl.servercount && cl.refresh_prepped) SCR_EndLoadingPlaque(); // get rid of loading plaque } cl.sound_prepped = true; // can start mixing ambient sounds // fire entity events CL_FireEntityEvents(&cl.frame); CL_CheckPredictionError(); } }
/* ================== CL_ParseGamestate ================== */ void CL_ParseGamestate( msg_t *msg ) { int i; entityState_t *es; int newnum; entityState_t nullstate; int cmd; char *s; Con_Close(); clc.connectPacketCount = 0; // wipe local client state CL_ClearState(); // a gamestate always marks a server command sequence clc.serverCommandSequence = MSG_ReadLong( msg ); // parse all the configstrings and baselines cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings while ( 1 ) { cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF ) { break; } if ( cmd == svc_configstring ) { int len, start; start = msg->readcount; i = MSG_ReadShort( msg ); if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); } s = MSG_ReadBigString( msg ); if (cl_shownet->integer >= 2) { Com_Printf("%3i: %d: %s\n", start, i, s); } /* if (i == CS_SERVERINFO) { //get the special value here char *f = strstr(s, "g_debugMelee"); if (f) { while (*f && *f != '\\') { //find the \ after it f++; } if (*f == '\\') { //got it int i = 0; f++; while (*f && *f != '\\' && i < 128) { hiddenCvarVal[i] = *f; i++; f++; } hiddenCvarVal[i] = 0; //resume here s = f; } } } */ len = strlen( s ); if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); } // append it to the gameState string buffer cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); cl.gameState.dataCount += len + 1; } else if ( cmd == svc_baseline ) { newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); if ( newnum < 0 || newnum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); } Com_Memset (&nullstate, 0, sizeof(nullstate)); es = &cl.entityBaselines[ newnum ]; MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); } else { Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); } } clc.clientNum = MSG_ReadLong(msg); // read the checksum feed clc.checksumFeed = MSG_ReadLong( msg ); // parse serverId and other cvars CL_SystemInfoChanged(); // reinitialize the filesystem if the game directory has changed if( FS_ConditionalRestart( clc.checksumFeed ) ) { // don't set to true because we yet have to start downloading // enabling this can cause double loading of a map when connecting to // a server which has a different game directory set //clc.downloadRestart = qtrue; } // This used to call CL_StartHunkUsers, but now we enter the download state before loading the // cgame CL_InitDownloads(); // make sure the game starts Cvar_Set( "cl_paused", "0" ); }
/* ================== CL_ParseServerInfo ================== */ void CL_ParseServerInfo (void) { char *str; int i; int nummodels, numsounds; char model_precache[MAX_MODELS][MAX_QPATH]; char sound_precache[MAX_SOUNDS][MAX_QPATH]; Con_DPrintf ("Serverinfo packet received.\n"); // // wipe the client_state_t struct // CL_ClearState (); // parse protocol version number i = MSG_ReadLong (); if (i != PROTOCOL_VERSION) { Con_Printf ("Server returned version %i, not %i", i, PROTOCOL_VERSION); return; } // parse maxclients cl.maxclients = MSG_ReadByte (); if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) { Con_Printf("Bad maxclients (%u) from server\n", cl.maxclients); return; } cl.scores = Hunk_AllocName (cl.maxclients*sizeof(*cl.scores), "scores"); // parse gametype cl.gametype = MSG_ReadByte (); // parse signon message str = MSG_ReadString (); strncpy (cl.levelname, str, sizeof(cl.levelname)-1); // seperate the printfs so the server message can have a color Con_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"); Con_Printf ("%c%s\n", 2, str); // // first we go through and touch all of the precache data that still // happens to be in the cache, so precaching something else doesn't // needlessly purge it // // precache models memset (cl.model_precache, 0, sizeof(cl.model_precache)); for (nummodels=1 ; ; nummodels++) { str = MSG_ReadString (); if (!str[0]) break; if (nummodels==MAX_MODELS) { Con_Printf ("Server sent too many model precaches\n"); return; } strcpy (model_precache[nummodels], str); Mod_TouchModel (str); } // precache sounds memset (cl.sound_precache, 0, sizeof(cl.sound_precache)); for (numsounds=1 ; ; numsounds++) { str = MSG_ReadString (); if (!str[0]) break; if (numsounds==MAX_SOUNDS) { Con_Printf ("Server sent too many sound precaches\n"); return; } strcpy (sound_precache[numsounds], str); S_TouchSound (str); } // // now we try to load everything else until a cache allocation fails // for (i=1 ; i<nummodels ; i++) { cl.model_precache[i] = Mod_ForName (model_precache[i], false); if (cl.model_precache[i] == NULL) { Con_Printf("Model %s not found\n", model_precache[i]); return; } CL_KeepaliveMessage (); } S_BeginPrecaching (); for (i=1 ; i<numsounds ; i++) { cl.sound_precache[i] = S_PrecacheSound (sound_precache[i]); CL_KeepaliveMessage (); } S_EndPrecaching (); // local state cl_entities[0].model = cl.worldmodel = cl.model_precache[1]; R_NewMap (); Hunk_Check (); // make sure nothing is hurt noclip_anglehack = false; // noclip is turned off at start }
/* ================= Netchan_Process called when the current net_message is from remote_address modifies net_message so that it points to the packet payload ================= */ qboolean Netchan_Process (netchan_t *chan, sizebuf_t *msg) { uint32 sequence, sequence_ack; int32 reliable_ack; uint32 reliable_message; // get sequence numbers MSG_BeginReading (msg); sequence = MSG_ReadLong (msg); sequence_ack = MSG_ReadLong (msg); // read the qport if we are a server if (chan->sock == NS_SERVER) { //suck up 2 bytes for original and old r1q2 if (chan->protocol != PROTOCOL_R1Q2 || chan->qport > 0xFF) MSG_ReadShort (msg); else if (chan->qport) MSG_ReadByte (msg); } reliable_message = sequence >> 31; reliable_ack = sequence_ack >> 31; chan->got_reliable = reliable_message; sequence &= ~(1UL<<31); sequence_ack &= ~(1UL<<31); if (showpackets->intvalue) { if (reliable_message) Com_Printf ("recv %4i : s=%i reliable=%i ack=%i rack=%i\n", LOG_NET , msg->cursize , sequence , chan->incoming_reliable_sequence ^ 1 , sequence_ack , reliable_ack); else Com_Printf ("recv %4i : s=%i ack=%i rack=%i\n", LOG_NET , msg->cursize , sequence , sequence_ack , reliable_ack); } // // discard stale or duplicated packets // if (sequence <= chan->incoming_sequence) { if (showdrop->intvalue) Com_Printf ("%s:Out of order packet %i at %i\n", LOG_NET , NET_AdrToString (&chan->remote_address) , sequence , chan->incoming_sequence); return false; } // // dropped packets don't keep the message from being used // chan->dropped = sequence - (chan->incoming_sequence+1); if (chan->dropped > 0 && showdrop->intvalue) { Com_Printf ("%s:Dropped %i packets at %i\n", LOG_NET , NET_AdrToString (&chan->remote_address) , chan->dropped , sequence); } //r1: pl stats chan->total_received++; chan->total_dropped += chan->dropped; // // if the current outgoing reliable message has been acknowledged // clear the buffer to make way for the next // if (reliable_ack == chan->reliable_sequence) chan->reliable_length = 0; // it has been received // // if this message contains a reliable message, bump incoming_reliable_sequence // chan->incoming_sequence = sequence; chan->incoming_acknowledged = sequence_ack; chan->incoming_reliable_acknowledged = reliable_ack; if (reliable_message) { chan->incoming_reliable_sequence ^= 1; } // // the message can now be read from the current message pointer // chan->last_received = curtime; return true; }