/* ======================= SV_SendClientDatagram ======================= */ void SV_SendClientDatagram( sv_client_t *cl ) { byte msg_buf[NET_MAX_PAYLOAD]; sizebuf_t msg; svs.currentPlayer = cl; svs.currentPlayerNum = (cl - svs.clients); BF_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf )); // always send servertime at new frame BF_WriteByte( &msg, svc_time ); BF_WriteFloat( &msg, sv.time ); SV_WriteClientdataToMessage( cl, &msg ); SV_WriteEntitiesToClient( cl, &msg ); // copy the accumulated multicast datagram // for this client out to the message if( BF_CheckOverflow( &cl->datagram )) MsgDev( D_WARN, "datagram overflowed for %s\n", cl->name ); else BF_WriteBits( &msg, BF_GetData( &cl->datagram ), BF_GetNumBitsWritten( &cl->datagram )); BF_Clear( &cl->datagram ); if( BF_CheckOverflow( &msg )) { // must have room left for the packet header MsgDev( D_WARN, "msg overflowed for %s\n", cl->name ); BF_Clear( &msg ); } // send the datagram Netchan_TransmitBits( &cl->netchan, BF_GetNumBitsWritten( &msg ), BF_GetData( &msg )); }
/* ============================== Netchan_CreateFragments ============================== */ void Netchan_CreateFragments( qboolean server, netchan_t *chan, sizebuf_t *msg ) { // always queue any pending reliable data ahead of the fragmentation buffer if( BF_GetNumBytesWritten( &chan->message ) > 0 ) { Netchan_CreateFragments_( server, chan, &chan->message ); BF_Clear( &chan->message ); } Netchan_CreateFragments_( server, chan, msg ); }
/* ============================== Netchan_CreateFragments_ ============================== */ void Netchan_CreateFragments_( qboolean server, netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *buf; int chunksize; int send, pos; int remaining; int bufferid = 1; fragbufwaiting_t *wait, *p; if( BF_GetNumBytesWritten( msg ) == 0 ) return; chunksize = bound( 16, net_blocksize->integer, 1400 ); wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); remaining = BF_GetNumBytesWritten( msg ); pos = 0; while( remaining > 0 ) { send = min( remaining, chunksize ); remaining -= send; buf = Netchan_AllocFragbuf(); buf->bufferid = bufferid++; // Copy in data BF_Clear( &buf->frag_message ); BF_WriteBits( &buf->frag_message, msg->pData + pos, send << 3 ); pos += send; Netchan_AddFragbufToTail( wait, buf ); } // now add waiting list item to end of buffer queue if( !chan->waitlist[FRAG_NORMAL_STREAM] ) { chan->waitlist[FRAG_NORMAL_STREAM] = wait; } else { p = chan->waitlist[FRAG_NORMAL_STREAM]; while( p->next ) { p = p->next; } p->next = wait; } }
/* ============================== Netchan_FlushIncoming ============================== */ void Netchan_FlushIncoming( netchan_t *chan, int stream ) { fragbuf_t *p, *n; BF_Clear( &net_message ); p = chan->incomingbufs[ stream ]; while( p ) { n = p->next; Mem_Free( p ); p = n; } chan->incomingbufs[stream] = NULL; chan->incomingready[stream] = false; }
/* ================= CL_Reconnect_f The server is changing levels ================= */ void CL_Reconnect_f( void ) { if( cls.state == ca_disconnected ) return; S_StopAllSounds (); if( cls.state == ca_connected ) { cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connected; // clear channel and stuff Netchan_Clear( &cls.netchan ); BF_Clear( &cls.netchan.message ); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "new" ); cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away CL_StartupDemoHeader (); return; } if( cls.servername[0] ) { if( cls.state >= ca_connected ) { CL_Disconnect(); cls.connect_time = host.realtime - 1.5; } else cls.connect_time = MAX_HEARTBEAT; // fire immediately cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connecting; Msg( "reconnecting...\n" ); } }
/* ===================== CL_ClearState ===================== */ void CL_ClearState( void ) { S_StopAllSounds (); CL_ClearEffects (); CL_FreeEdicts (); CL_ClearPhysEnts (); // wipe the entire cl structure Q_memset( &cl, 0, sizeof( cl )); BF_Clear( &cls.netchan.message ); Q_memset( &clgame.fade, 0, sizeof( clgame.fade )); Q_memset( &clgame.shake, 0, sizeof( clgame.shake )); Cvar_FullSet( "cl_background", "0", CVAR_READ_ONLY ); cl.refdef.movevars = &clgame.movevars; cl.maxclients = 1; // allow to drawing player in menu Cvar_SetFloat( "scr_download", 0.0f ); Cvar_SetFloat( "scr_loading", 0.0f ); // restore real developer level host.developer = host.old_developer; }
/* ======================= SV_InactivateClients Purpose: Prepare for level transition, etc. ======================= */ void SV_InactivateClients( void ) { int i; sv_client_t *cl; if( sv.state == ss_dead ) return; // send a message to each connected client for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state || !cl->edict ) continue; if( !cl->edict || (cl->edict->v.flags & FL_FAKECLIENT)) continue; if( svs.clients[i].state > cs_connected ) svs.clients[i].state = cs_connected; // clear netchan message (but keep other buffers) BF_Clear( &cl->netchan.message ); } }
/* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages( void ) { sv_client_t *cl; int i; svs.currentPlayer = NULL; svs.currentPlayerNum = 0; if( sv.state == ss_dead ) return; SV_UpdateToReliableMessages (); // send a message to each connected client for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->state || cl->fakeclient ) continue; if( cl->skip_message ) { cl->skip_message = false; continue; } if( !host_limitlocal->integer && NET_IsLocalAddress( cl->netchan.remote_address )) cl->send_message = true; if( cl->state == cs_spawned ) { // Try to send a message as soon as we can. // If the target time for sending is within the next frame interval ( based on last frame ), // trigger the send now. Note that in single player, // send_message is also set to true any time a packet arrives from the client. float time_unti_next_message = cl->next_messagetime - (host.realtime + host.frametime); if( time_unti_next_message <= 0.0f ) cl->send_message = true; // something got hosed if( time_unti_next_message > 2.0f ) cl->send_message = true; } // if the reliable message overflowed, drop the client if( BF_CheckOverflow( &cl->netchan.message )) { BF_Clear( &cl->netchan.message ); BF_Clear( &cl->datagram ); SV_BroadcastPrintf( PRINT_HIGH, "%s overflowed\n", cl->name ); MsgDev( D_WARN, "reliable overflow for %s\n", cl->name ); SV_DropClient( cl ); cl->send_message = true; cl->netchan.cleartime = 0; // don't choke this message } else if( cl->send_message ) { // If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client // until we get another packet in from the client. This prevents crash/drop and reconnect where they are // being hosed with "sequenced packet without connection" packets. if(( host.realtime - cl->netchan.last_received ) > sv_failuretime->value ) cl->send_message = false; } // only send messages if the client has sent one // and the bandwidth is not choked if( !cl->send_message ) continue; // Bandwidth choke active? if( !Netchan_CanPacket( &cl->netchan )) { cl->chokecount++; continue; } cl->send_message = false; // Now that we were able to send, reset timer to point to next possible send time. cl->next_messagetime = host.realtime + host.frametime + cl->cl_updaterate; if( cl->state == cs_spawned ) { SV_SendClientDatagram( cl ); } else { // just update reliable Netchan_Transmit( &cl->netchan, 0, NULL ); } } // reset current client svs.currentPlayer = NULL; svs.currentPlayerNum = 0; }
/* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages( void ) { int i; sv_client_t *cl; // check for changes to be sent over the reliable streams to all clients for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->edict ) continue; // not in game yet if( cl->state != cs_spawned ) continue; if( cl->sendinfo ) { cl->sendinfo = false; SV_FullClientUpdate( cl, &sv.reliable_datagram ); } if( cl->sendmovevars ) { cl->sendmovevars = false; SV_FullUpdateMovevars( cl, &cl->netchan.message ); } } // 1% chanse for simulate random network bugs if( sv.write_bad_message && Com_RandomLong( 0, 512 ) == 404 ) { // just for network debugging (send only for local client) BF_WriteByte( &sv.datagram, svc_bad ); BF_WriteLong( &sv.datagram, rand( )); // send some random data BF_WriteString( &sv.datagram, host.finalmsg ); // send final message sv.write_bad_message = false; } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.datagram )) { MsgDev( D_ERROR, "sv.datagram overflowed!\n" ); BF_Clear( &sv.datagram ); } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.spectator_datagram )) { MsgDev( D_ERROR, "sv.spectator_datagram overflowed!\n" ); BF_Clear( &sv.spectator_datagram ); } // now send the reliable and server datagrams to all clients. for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state < cs_connected || cl->fakeclient ) continue; // reliables go to all connected or spawned BF_WriteBits( &cl->netchan.message, BF_GetData( &sv.reliable_datagram ), BF_GetNumBitsWritten( &sv.reliable_datagram )); BF_WriteBits( &cl->datagram, BF_GetData( &sv.datagram ), BF_GetNumBitsWritten( &sv.datagram )); if( cl->hltv_proxy ) { BF_WriteBits( &cl->datagram, BF_GetData( &sv.spectator_datagram ), BF_GetNumBitsWritten( &sv.spectator_datagram )); } } // now clear the reliable and datagram buffers. BF_Clear( &sv.spectator_datagram ); BF_Clear( &sv.reliable_datagram ); BF_Clear( &sv.datagram ); }
/* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { char *args; char *c, buf[MAX_SYSPATH]; int len = sizeof( buf ); int dataoffset = 0; netadr_t servadr; BF_Clear( msg ); BF_ReadLong( msg ); // skip the -1 args = BF_ReadStringLine( msg ); Cmd_TokenizeString( args ); c = Cmd_Argv( 0 ); MsgDev( D_NOTE, "CL_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), c ); // server connection if( !Q_strcmp( c, "client_connect" )) { if( cls.state == ca_connected ) { MsgDev( D_INFO, "dup connect received. ignored\n"); return; } Netchan_Setup( NS_CLIENT, &cls.netchan, from, Cvar_VariableValue( "net_qport" )); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "new" ); cls.state = ca_connected; cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away CL_StartupDemoHeader (); UI_SetActiveMenu( false ); } else if( !Q_strcmp( c, "info" )) { // server responding to a status broadcast CL_ParseStatusMessage( from, msg ); } else if( !Q_strcmp( c, "netinfo" )) { // server responding to a status broadcast CL_ParseNETInfoMessage( from, msg ); } else if( !Q_strcmp( c, "cmd" )) { // remote command from gui front end if( !NET_IsLocalAddress( from )) { Msg( "Command packet from remote host. Ignored.\n" ); return; } ShowWindow( host.hWnd, SW_RESTORE ); SetForegroundWindow ( host.hWnd ); args = BF_ReadString( msg ); Cbuf_AddText( args ); Cbuf_AddText( "\n" ); } else if( !Q_strcmp( c, "print" )) { // print command from somewhere args = BF_ReadString( msg ); Msg( args ); } else if( !Q_strcmp( c, "ping" )) { // ping from somewhere Netchan_OutOfBandPrint( NS_CLIENT, from, "ack" ); } else if( !Q_strcmp( c, "challenge" )) { // challenge from the server we are connecting to cls.challenge = Q_atoi( Cmd_Argv( 1 )); CL_SendConnectPacket(); return; } else if( !Q_strcmp( c, "echo" )) { // echo request from server Netchan_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 )); } else if( !Q_strcmp( c, "disconnect" )) { // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us CL_Disconnect(); } else if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, buf ); } else if( msg->pData[0] == 0xFF && msg->pData[1] == 0xFF && msg->pData[2] == 0xFF && msg->pData[3] == 0xFF && msg->pData[4] == 0x66 && msg->pData[5] == 0x0A ) { dataoffset = 6; while( 1 ) { servadr.type = NA_IP; servadr.ip[0] = msg->pData[dataoffset + 0]; servadr.ip[1] = msg->pData[dataoffset + 1]; servadr.ip[2] = msg->pData[dataoffset + 2]; servadr.ip[3] = msg->pData[dataoffset + 3]; servadr.port = *(word *)&msg->pData[dataoffset + 4]; if( !servadr.port ) break; MsgDev( D_INFO, "Found server: %s\n", NET_AdrToString( servadr )); NET_Config( true ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); dataoffset += 6; } } else MsgDev( D_ERROR, "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); }
/* ============================== Netchan_CopyFileFragments ============================== */ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *p, *n; char filename[CS_SIZE]; int nsize; byte *buffer; int pos; if( !chan->incomingready[FRAG_FILE_STREAM] ) return false; if( !chan->incomingbufs[FRAG_FILE_STREAM] ) { MsgDev( D_WARN, "Netchan_CopyFileFragments: Called with no fragments readied\n" ); chan->incomingready[FRAG_FILE_STREAM] = false; return false; } p = chan->incomingbufs[FRAG_FILE_STREAM]; BF_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); // copy in first chunk so we can get filename out BF_WriteBits( msg, BF_GetData( &p->frag_message ), BF_GetNumBitsWritten( &p->frag_message )); BF_SeekToBit( msg, 0 ); // rewind buffer //Q_strncpy( filename, BF_ReadString( msg ), sizeof( filename )); Q_snprintf( filename, sizeof( filename ), "downloaded/%s", BF_ReadString( msg ) ); if( Q_strlen( filename ) <= 0 ) { MsgDev( D_ERROR, "File fragment received with no filename\nFlushing input queue\n" ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return false; } else if( Q_strstr( filename, ".." )) { MsgDev( D_ERROR, "File fragment received with relative path, ignoring\n" ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return false; } Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); if( FS_FileExists( filename, false )) { MsgDev( D_ERROR, "Can't download %s, already exists\n", filename ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return true; } // create file from buffers nsize = 0; while ( p ) { nsize += BF_GetNumBytesWritten( &p->frag_message ); // Size will include a bit of slop, oh well if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) { nsize -= BF_GetNumBytesRead( msg ); } p = p->next; } buffer = Mem_Alloc( net_mempool, nsize + 1 ); p = chan->incomingbufs[ FRAG_FILE_STREAM ]; pos = 0; while( p ) { int cursize; n = p->next; cursize = BF_GetNumBytesWritten( &p->frag_message ); // first message has the file name, don't write that into the data stream, // just write the rest of the actual data if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) { // copy it in cursize -= BF_GetNumBytesRead( msg ); Q_memcpy( &buffer[pos], &p->frag_message.pData[BF_GetNumBytesRead( msg )], cursize ); } else { Q_memcpy( &buffer[pos], p->frag_message.pData, cursize ); } pos += cursize; Mem_Free( p ); p = n; } FS_WriteFile( filename, buffer, pos ); Mem_Free( buffer ); // clear remnants BF_Clear( msg ); chan->incomingbufs[FRAG_FILE_STREAM] = NULL; // reset flag chan->incomingready[FRAG_FILE_STREAM] = false; return true; }
/* ============================== Netchan_CreateFileFragments ============================== */ int Netchan_CreateFileFragments( qboolean server, netchan_t *chan, const char *filename ) { int chunksize; int send, pos; int remaining; int bufferid = 1; int filesize = 0; qboolean firstfragment = true; fragbufwaiting_t *wait, *p; fragbuf_t *buf; chunksize = bound( 16, net_blocksize->integer, 512 ); filesize = FS_FileSize( filename, false ); if( filesize <= 0 ) { MsgDev( D_WARN, "Unable to open %s for transfer\n", filename ); return 0; } wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); remaining = filesize; pos = 0; while( remaining > 0 ) { send = min( remaining, chunksize ); buf = Netchan_AllocFragbuf(); buf->bufferid = bufferid++; // copy in data BF_Clear( &buf->frag_message ); if( firstfragment ) { firstfragment = false; // Write filename BF_WriteString( &buf->frag_message, filename ); // Send a bit less on first package send -= BF_GetNumBytesWritten( &buf->frag_message ); } buf->isfile = true; buf->size = send; buf->foffset = pos; Q_strncpy( buf->filename, filename, sizeof( buf->filename )); pos += send; remaining -= send; Netchan_AddFragbufToTail( wait, buf ); } // now add waiting list item to end of buffer queue if( !chan->waitlist[FRAG_FILE_STREAM] ) { chan->waitlist[FRAG_FILE_STREAM] = wait; } else { p = chan->waitlist[FRAG_FILE_STREAM]; while( p->next ) { p = p->next; } p->next = wait; } return 1; }
/* ============================== Netchan_CreateFileFragmentsFromBuffer ============================== */ void Netchan_CreateFileFragmentsFromBuffer( qboolean server, netchan_t *chan, char *filename, byte *pbuf, int size ) { int chunksize; int send, pos; int remaining; int bufferid = 1; qboolean firstfragment = true; fragbufwaiting_t *wait, *p; fragbuf_t *buf; if( !size ) return; chunksize = bound( 16, net_blocksize->integer, 512 ); wait = ( fragbufwaiting_t * )Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); remaining = size; pos = 0; while( remaining > 0 ) { send = min( remaining, chunksize ); buf = Netchan_AllocFragbuf(); buf->bufferid = bufferid++; // copy in data BF_Clear( &buf->frag_message ); if( firstfragment ) { firstfragment = false; // write filename BF_WriteString( &buf->frag_message, filename ); // send a bit less on first package send -= BF_GetNumBytesWritten( &buf->frag_message ); } buf->isbuffer = true; buf->isfile = true; buf->size = send; buf->foffset = pos; BF_WriteBits( &buf->frag_message, pbuf + pos, send << 3 ); pos += send; remaining -= send; Netchan_AddFragbufToTail( wait, buf ); } // now add waiting list item to end of buffer queue if( !chan->waitlist[FRAG_FILE_STREAM] ) { chan->waitlist[FRAG_FILE_STREAM] = wait; } else { p = chan->waitlist[FRAG_FILE_STREAM]; while( p->next ) { p = p->next; } p->next = wait; } }
/* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { char *args; char *c, buf[MAX_SYSPATH]; int len = sizeof( buf ), i = 0; netadr_t servadr; BF_Clear( msg ); BF_ReadLong( msg ); // skip the -1 args = BF_ReadStringLine( msg ); Cmd_TokenizeString( args ); c = Cmd_Argv( 0 ); MsgDev( D_NOTE, "CL_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), c ); // server connection if( !Q_strcmp( c, "client_connect" )) { if( cls.state == ca_connected ) { MsgDev( D_INFO, "Dup connect received. Ignored.\n"); return; } Netchan_Setup( NS_CLIENT, &cls.netchan, from, net_qport->integer); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "new" ); cls.state = ca_connected; cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away CL_StartupDemoHeader (); } else if( !Q_strcmp( c, "info" )) { // server responding to a status broadcast CL_ParseStatusMessage( from, msg ); } else if( !Q_strcmp( c, "netinfo" )) { // server responding to a status broadcast CL_ParseNETInfoMessage( from, msg ); } else if( !Q_strcmp( c, "cmd" )) { // remote command from gui front end if( !NET_IsLocalAddress( from )) { Msg( "Command packet from remote host. Ignored.\n" ); return; } #ifdef XASH_SDL SDL_RestoreWindow( host.hWnd ); #endif args = BF_ReadString( msg ); Cbuf_AddText( args ); Cbuf_AddText( "\n" ); } else if( !Q_strcmp( c, "print" )) { // print command from somewhere Msg("remote: %s\n", BF_ReadString( msg ) ); } else if( !Q_strcmp( c, "ping" )) { // ping from somewhere Netchan_OutOfBandPrint( NS_CLIENT, from, "ack" ); } else if( !Q_strcmp( c, "challenge" )) { // challenge from the server we are connecting to cls.challenge = Q_atoi( Cmd_Argv( 1 )); CL_SendConnectPacket(); return; } else if( !Q_strcmp( c, "echo" )) { // echo request from server Netchan_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 )); } else if( !Q_strcmp( c, "disconnect" )) { // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us CL_Disconnect(); CL_ClearEdicts(); } else if( !Q_strcmp( c, "f") ) { // serverlist got from masterserver while( !msg->bOverflow ) { servadr.type = NA_IP; // 4 bytes for IP BF_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 2 bytes for Port servadr.port = BF_ReadShort( msg ); if( !servadr.port ) break; MsgDev( D_INFO, "Found server: %s\n", NET_AdrToString( servadr )); NET_Config( true ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); } // execute at next frame preventing relation on fps Cbuf_AddText("menu_resetping\n"); } else if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, (byte *)buf ); } else MsgDev( D_ERROR, "Bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); }
/* ================= SV_ReadPackets ================= */ void SV_ReadPackets( void ) { sv_client_t *cl; int i, qport, curSize; while( NET_GetPacket( NS_SERVER, &net_from, net_message_buffer, &curSize )) { BF_Init( &net_message, "ClientPacket", net_message_buffer, curSize ); // check for connectionless packet (0xffffffff) first if( BF_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) { SV_ConnectionlessPacket( net_from, &net_message ); continue; } // read the qport out of the message so we can fix up // stupid address translating routers BF_Clear( &net_message ); BF_ReadLong( &net_message ); // sequence number BF_ReadLong( &net_message ); // sequence number qport = (int)BF_ReadShort( &net_message ) & 0xffff; // check for packets from connected clients for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state == cs_free || cl->fakeclient ) continue; if( !NET_CompareBaseAdr( net_from, cl->netchan.remote_address )) continue; if( cl->netchan.qport != qport ) continue; if( cl->netchan.remote_address.port != net_from.port ) { MsgDev( D_INFO, "SV_ReadPackets: fixing up a translated port\n"); cl->netchan.remote_address.port = net_from.port; } if( Netchan_Process( &cl->netchan, &net_message )) { cl->send_message = true; // reply at end of frame // this is a valid, sequenced packet, so process it if( cl->state != cs_zombie ) { cl->lastmessage = host.realtime; // don't timeout SV_ExecuteClientMessage( cl, &net_message ); svgame.globals->frametime = host.frametime; svgame.globals->time = sv.time; } } // fragmentation/reassembly sending takes priority over all game messages, want this in the future? if( Netchan_IncomingReady( &cl->netchan )) { if( Netchan_CopyNormalFragments( &cl->netchan, &net_message )) { BF_Clear( &net_message ); SV_ExecuteClientMessage( cl, &net_message ); } if( Netchan_CopyFileFragments( &cl->netchan, &net_message )) { SV_ProcessFile( cl, cl->netchan.incomingfilename ); } } break; } if( i != sv_maxclients->integer ) continue; } }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds =================== */ void CL_WritePacket( void ) { sizebuf_t buf; qboolean send_command = false; byte data[MAX_CMD_BUFFER]; int i, from, to, key, size; int numbackup = 2; int numcmds; int newcmds; int cmdnumber; // don't send anything if playing back a demo if( cls.demoplayback || cls.state == ca_cinematic ) return; if( cls.state == ca_disconnected || cls.state == ca_connecting ) return; CL_ComputePacketLoss (); #ifndef _DEBUG if( cl_cmdrate->value < MIN_CMD_RATE ) { Cvar_SetFloat( "cl_cmdrate", MIN_CMD_RATE ); } #endif Q_memset( data, 0, MAX_CMD_BUFFER ); BF_Init( &buf, "ClientData", data, sizeof( data )); // Determine number of backup commands to send along numbackup = bound( 0, cl_cmdbackup->integer, MAX_BACKUP_COMMANDS ); if( cls.state == ca_connected ) numbackup = 0; // Check to see if we can actually send this command // In single player, send commands as fast as possible // Otherwise, only send when ready and when not choking bandwidth if(( cl.maxclients == 1 ) || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal->integer )) send_command = true; else if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan )) send_command = true; if( cl.force_send_usercmd ) { send_command = true; cl.force_send_usercmd = false; } if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) { if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME ) { Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); cl.validsequence = 0; } } if( cl_nodelta->integer ) { cl.validsequence = 0; } // send a userinfo update if needed if( userinfo->modified ) { BF_WriteByte( &cls.netchan.message, clc_userinfo ); BF_WriteString( &cls.netchan.message, Cvar_Userinfo( )); } if( send_command ) { int outgoing_sequence; if( cl_cmdrate->integer > 0 ) cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate->value ); else cls.nextcmdtime = host.realtime; // always able to send right away if( cls.lastoutgoingcommand == -1 ) { outgoing_sequence = cls.netchan.outgoing_sequence; cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; } else outgoing_sequence = cls.lastoutgoingcommand + 1; // begin a client move command BF_WriteByte( &buf, clc_move ); // save the position for a checksum byte key = BF_GetRealBytesWritten( &buf ); BF_WriteByte( &buf, 0 ); // write packet lossage percentation BF_WriteByte( &buf, cls.packet_loss ); // say how many backups we'll be sending BF_WriteByte( &buf, numbackup ); // how many real commands have queued up newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand ); // put an upper/lower bound on this newcmds = bound( 0, newcmds, MAX_TOTAL_CMDS ); if( cls.state == ca_connected ) newcmds = 0; BF_WriteByte( &buf, newcmds ); numcmds = newcmds + numbackup; from = -1; for( i = numcmds - 1; i >= 0; i-- ) { cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; to = cmdnumber; CL_WriteUsercmd( &buf, from, to ); from = to; if( BF_CheckOverflow( &buf )) Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // calculate a checksum over the move commands size = BF_GetRealBytesWritten( &buf ) - key - 1; buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence ); // message we are constructing. i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; // determine if we need to ask for a new set of delta's. if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting )) { cl.delta_sequence = cl.validsequence; BF_WriteByte( &buf, clc_delta ); BF_WriteByte( &buf, cl.validsequence & 0xFF ); } else { // request delta compression of entities cl.delta_sequence = -1; } if( BF_CheckOverflow( &buf )) { Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // remember outgoing command that we are sending cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; // composite the rest of the datagram.. if( BF_GetNumBitsWritten( &cls.datagram ) <= BF_GetNumBitsLeft( &buf )) BF_WriteBits( &buf, BF_GetData( &cls.datagram ), BF_GetNumBitsWritten( &cls.datagram )); BF_Clear( &cls.datagram ); // deliver the message (or update reliable) Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); } else { // increment sequence number so we can detect that we've held back packets. cls.netchan.outgoing_sequence++; } if( cls.demorecording ) { // Back up one because we've incremented outgoing_sequence each frame by 1 unit cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK; CL_WriteDemoUserCmd( cmdnumber ); } // update download/upload slider. Netchan_UpdateProgress( &cls.netchan ); }
/* =============== Netchan_TransmitBits tries to send an unreliable message to a connection, and handles the transmition / retransmition of the reliable messages. A 0 length will still generate a packet and deal with the reliable messages. ================ */ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) { sizebuf_t send; byte send_buf[NET_MAX_MESSAGE]; qboolean send_reliable_fragment; qboolean send_resending = false; qboolean send_reliable; size_t size1, size2; uint w1, w2, hdr_size; int i, j; float fRate; // check for message overflow // check for message overflow if( BF_CheckOverflow( &chan->message )) { MsgDev( D_ERROR, "%s:outgoing message overflow\n", NET_AdrToString( chan->remote_address )); return; } // if the remote side dropped the last reliable message, resend it send_reliable = false; if( chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence ) { send_reliable = true; send_resending = true; } // A packet can have "reliable payload + frag payload + unreliable payload // frag payload can be a file chunk, if so, it needs to be parsed on the receiving end and reliable payload + unreliable payload need // to be passed on to the message queue. The processing routine needs to be able to handle the case where a message comes in and a file // transfer completes // if the reliable transmit buffer is empty, copy the current message out if( !chan->reliable_length ) { qboolean send_frag = false; fragbuf_t *pbuf; // will be true if we are active and should let chan->message get some bandwidth int send_from_frag[MAX_STREAMS] = { 0, 0 }; int send_from_regular = false; // if we have data in the waiting list(s) and we have cleared the current queue(s), then // push the waitlist(s) into the current queue(s) Netchan_FragSend( chan ); // sending regular payload send_from_regular = BF_GetNumBytesWritten( &chan->message ) ? 1 : 0; // check to see if we are sending a frag payload for( i = 0; i < MAX_STREAMS; i++ ) { if( chan->fragbufs[i] ) { send_from_frag[i] = 1; } } // stall reliable payloads if sending from frag buffer if( send_from_regular && ( send_from_frag[FRAG_NORMAL_STREAM] )) { send_from_regular = false; // if the reliable buffer has gotten too big, queue it at the end of everything and clear out buffer if( BF_GetNumBytesWritten( &chan->message ) > MAX_RELIABLE_PAYLOAD ) { Netchan_CreateFragments(( chan->sock == NS_SERVER ), chan, &chan->message ); BF_Clear( &chan->message ); } } // startpos will be zero if there is no regular payload for( i = 0; i < MAX_STREAMS; i++ ) { chan->frag_startpos[i] = 0; // assume no fragment is being sent chan->reliable_fragment[i] = 0; chan->reliable_fragid[i] = 0; chan->frag_length[i] = 0; if( send_from_frag[i] ) { send_frag = true; } } if( send_from_regular || send_frag ) { chan->reliable_sequence ^= 1; send_reliable = true; } if( send_from_regular ) { Q_memcpy( chan->reliable_buf, chan->message_buf, BF_GetNumBytesWritten( &chan->message )); chan->reliable_length = BF_GetNumBitsWritten( &chan->message ); BF_Clear( &chan->message ); // if we send fragments, this is where they'll start for( i = 0; i < MAX_STREAMS; i++ ) { chan->frag_startpos[i] = chan->reliable_length; } } for( i = 0; i < MAX_STREAMS; i++ ) { int fragment_size; int newpayloadsize; // is there someting in the fragbuf? pbuf = chan->fragbufs[i]; fragment_size = 0; if( pbuf ) { fragment_size = BF_GetNumBytesWritten( &pbuf->frag_message ); // files set size a bit differently. if( pbuf->isfile && !pbuf->isbuffer ) { fragment_size = pbuf->size; } } newpayloadsize = (( chan->reliable_length + ( fragment_size << 3 )) + 7 ) >> 3; // make sure we have enought space left if( send_from_frag[i] && pbuf && ( newpayloadsize < MAX_RELIABLE_PAYLOAD )) { sizebuf_t temp; // which buffer are we sending ? chan->reliable_fragid[i] = MAKE_FRAGID( pbuf->bufferid, chan->fragbufcount[i] ); // if it's not in-memory, then we'll need to copy it in frame the file handle. if( pbuf->isfile && !pbuf->isbuffer ) { byte filebuffer[2048]; file_t *hfile; hfile = FS_Open( pbuf->filename, "rb", false ); FS_Seek( hfile, pbuf->foffset, SEEK_SET ); FS_Read( hfile, filebuffer, pbuf->size ); BF_WriteBits( &pbuf->frag_message, filebuffer, pbuf->size << 3 ); FS_Close( hfile ); } // copy frag stuff on top of current buffer BF_StartWriting( &temp, chan->reliable_buf, sizeof( chan->reliable_buf ), chan->reliable_length, -1 ); BF_WriteBits( &temp, BF_GetData( &pbuf->frag_message ), BF_GetNumBitsWritten( &pbuf->frag_message )); chan->reliable_length += BF_GetNumBitsWritten( &pbuf->frag_message ); chan->frag_length[i] = BF_GetNumBitsWritten( &pbuf->frag_message ); // unlink pbuf Netchan_UnlinkFragment( pbuf, &chan->fragbufs[i] ); chan->reliable_fragment[i] = 1; // offset the rest of the starting positions for( j = i + 1; j < MAX_STREAMS; j++ ) { chan->frag_startpos[j] += chan->frag_length[i]; } } } }