void pfnUpdateServerInfo( const char *szKey, const char *szValue, const char *unused, void *unused2 ) { convar_t *cv = Cvar_FindVar( szKey ); if( !cv || !cv->modified ) return; // this cvar not changed BF_WriteByte( &sv.reliable_datagram, svc_serverinfo ); BF_WriteString( &sv.reliable_datagram, szKey ); BF_WriteString( &sv.reliable_datagram, szValue ); cv->modified = false; // reset state }
/* ============== CL_ParseCvarValue2 Find the client cvar value and sent it back to the server ============== */ void CL_ParseCvarValue2( sizebuf_t *msg ) { int requestID = BF_ReadLong( msg ); const char *cvarName = BF_ReadString( msg ); convar_t *cvar = Cvar_FindVar( cvarName ); // build the answer BF_WriteByte( &cls.netchan.message, clc_requestcvarvalue2 ); BF_WriteLong( &cls.netchan.message, requestID ); BF_WriteString( &cls.netchan.message, cvarName ); BF_WriteString( &cls.netchan.message, cvar ? cvar->string : "Not Found" ); }
/* ============== CL_ParseResourceList ============== */ void CL_ParseResourceList( sizebuf_t *msg ) { int i = 0; Q_memset( &reslist, 0, sizeof( resourcelist_t )); reslist.rescount = BF_ReadWord( msg ) - 1; for( i = 0; i < reslist.rescount; i++ ) { reslist.restype[i] = BF_ReadWord( msg ); Q_strncpy( reslist.resnames[i], BF_ReadString( msg ), CS_SIZE ); } cls.downloadcount = 0; for( i = 0; i < reslist.rescount; i++ ) { if( reslist.restype[i] == t_sound ) CL_CheckingSoundResFile( reslist.resnames[i] ); else CL_CheckingResFile( reslist.resnames[i] ); } if( !cls.downloadcount ) { BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); } }
/* ============== CL_CheckingResFile ============== */ void CL_CheckingResFile( char *pResFileName ) { sizebuf_t buf; byte data[32]; if( FS_FileExists( pResFileName, false )) return; // already exists cls.downloadcount++; if( cl_allow_fragment->integer ) { Msg( "Starting file download: %s\n", pResFileName ); if( cls.state == ca_disconnected ) return; BF_Init( &buf, "ClientPacket", data, sizeof( data )); BF_WriteByte( &buf, clc_resourcelist ); BF_WriteString( &buf, pResFileName ); if( !cls.netchan.remote_address.type ) // download in singleplayer ??? cls.netchan.remote_address.type = NA_LOOPBACK; // make sure message will be delivered Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); } else HTTP_AddDownload( pResFileName, -1, true ); }
/* ================== SV_FinalMessage Used by SV_Shutdown to send a final message to all connected clients before the server goes down. The messages are sent immediately, not just stuck on the outgoing message list, because the server is going to totally exit after returning from this function. ================== */ void SV_FinalMessage( char *message, qboolean reconnect ) { sv_client_t *cl; byte msg_buf[1024]; sizebuf_t msg; int i; BF_Init( &msg, "FinalMessage", msg_buf, sizeof( msg_buf )); BF_WriteByte( &msg, svc_print ); BF_WriteByte( &msg, PRINT_HIGH ); BF_WriteString( &msg, va( "%s\n", message )); if( reconnect ) { BF_WriteByte( &msg, svc_changing ); if( sv.loadgame || sv_maxclients->integer > 1 || sv.changelevel ) BF_WriteOneBit( &msg, 1 ); // changelevel else BF_WriteOneBit( &msg, 0 ); } else { BF_WriteByte( &msg, svc_disconnect ); } // send it twice // stagger the packets to crutch operating system limited buffers for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) if( cl->state >= cs_connected && !cl->fakeclient ) Netchan_Transmit( &cl->netchan, BF_GetNumBytesWritten( &msg ), BF_GetData( &msg )); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) if( cl->state >= cs_connected && !cl->fakeclient ) Netchan_Transmit( &cl->netchan, BF_GetNumBytesWritten( &msg ), BF_GetData( &msg )); }
/* ================= SV_BroadcastPrintf Sends text to all active clients ================= */ void SV_BroadcastPrintf( int level, char *fmt, ... ) { char string[MAX_SYSPATH]; va_list argptr; sv_client_t *cl; int i; if( !sv.state ) return; va_start( argptr, fmt ); Q_vsprintf( string, fmt, argptr ); va_end( argptr ); // echo to console if( host.type == HOST_DEDICATED ) Msg( "%s", string ); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( level < cl->messagelevel ) continue; if( cl->state != cs_spawned ) continue; if( cl->fakeclient ) continue; BF_WriteByte( &cl->netchan.message, svc_print ); BF_WriteByte( &cl->netchan.message, level ); BF_WriteString( &cl->netchan.message, string ); } }
/* =================== Cmd_ForwardToServer adds the current command line as a clc_stringcmd to the client message. things like godmode, noclip, etc, are commands directed to the server, so when they are typed in at the console, they will need to be forwarded. =================== */ void Cmd_ForwardToServer( void ) { char str[MAX_CMD_BUFFER]; if( cls.demoplayback ) { if( !Q_stricmp( Cmd_Argv( 0 ), "pause" )) cl.refdef.paused ^= 1; return; } if( cls.state != ca_connected && cls.state != ca_active ) { MsgDev( D_INFO, "Can't \"%s\", not connected\n", Cmd_Argv( 0 )); return; // not connected } BF_WriteByte( &cls.netchan.message, clc_stringcmd ); str[0] = 0; if( Q_stricmp( Cmd_Argv( 0 ), "cmd" )) { Q_strcat( str, Cmd_Argv( 0 )); Q_strcat( str, " " ); } if( Cmd_Argc() > 1 ) Q_strcat( str, Cmd_Args( )); else Q_strcat( str, "\n" ); BF_WriteString( &cls.netchan.message, str ); }
/* ================= CL_Precache_f The server will send this command right before allowing the client into the server ================= */ void CL_Precache_f( void ) { int spawncount; spawncount = Q_atoi( Cmd_Argv( 1 )); CL_PrepSound(); CL_PrepVideo(); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, va( "begin %i\n", spawncount )); }
/* ================= SV_BroadcastCommand Sends text to all active clients ================= */ void SV_BroadcastCommand( char *fmt, ... ) { va_list argptr; char string[MAX_SYSPATH]; if( !sv.state ) return; va_start( argptr, fmt ); Q_vsprintf( string, fmt, argptr ); va_end( argptr ); BF_WriteByte( &sv.reliable_datagram, svc_stufftext ); BF_WriteString( &sv.reliable_datagram, string ); }
/* ==================== CL_ProcessFile A file has been received via the fragmentation/reassembly layer, put it in the right spot and see if we have finished downloading files. ==================== */ void CL_ProcessFile( BOOL successfully_received, const char *filename ) { MsgDev( D_INFO, "Received %s, but file processing is not hooked up!!!\n", filename ); if( cls.downloadfileid == cls.downloadcount - 1 ) { MsgDev( D_INFO, "All Files downloaded\n" ); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); } cls.downloadfileid++; }
/* ================= SV_ClientPrintf Sends text across to be displayed if the level passes ================= */ void SV_ClientPrintf( sv_client_t *cl, int level, char *fmt, ... ) { va_list argptr; char string[MAX_SYSPATH]; if( level < cl->messagelevel || cl->fakeclient ) return; va_start( argptr, fmt ); Q_vsprintf( string, fmt, argptr ); va_end( argptr ); BF_WriteByte( &cl->netchan.message, svc_print ); BF_WriteByte( &cl->netchan.message, level ); BF_WriteString( &cl->netchan.message, string ); }
/* ===================== CL_SendDisconnectMessage Sends a disconnect message to the server ===================== */ void CL_SendDisconnectMessage( void ) { sizebuf_t buf; byte data[32]; if( cls.state == ca_disconnected ) return; BF_Init( &buf, "LastMessage", data, sizeof( data )); BF_WriteByte( &buf, clc_stringcmd ); BF_WriteString( &buf, "disconnect" ); if( !cls.netchan.remote_address.type ) cls.netchan.remote_address.type = NA_LOOPBACK; // make sure message will be delivered Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); }
/* ================= 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_ParseResourceList ============== */ void CL_ParseResourceList( sizebuf_t *msg ) { int i = 0; Q_memset( &reslist, 0, sizeof( resourcelist_t )); reslist.rescount = BF_ReadWord( msg ) - 1; for( i = 0; i < reslist.rescount; i++ ) { reslist.restype[i] = BF_ReadWord( msg ); Q_strncpy( reslist.resnames[i], BF_ReadString( msg ), CS_SIZE ); } cls.downloadcount = 0; HTTP_ResetProcessState(); for( i = 0; i < reslist.rescount; i++ ) { // skip some types #if 0 if( reslist.restype[i] == t_model && !Q_strchr( download_types->latched_string, 'm' ) ) continue; if( reslist.restype[i] == t_sound && !Q_strchr( download_types->latched_string, 's' ) ) continue; if( reslist.restype[i] == t_eventscript && !Q_strchr( download_types->latched_string, 'e' ) ) continue; if( reslist.restype[i] == t_generic && !Q_strchr( download_types->latched_string, 'c' ) ) continue; #endif if( reslist.restype[i] == t_sound ) CL_CheckingSoundResFile( reslist.resnames[i] ); else CL_CheckingResFile( reslist.resnames[i] ); } if( !cls.downloadcount ) { BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); } }
/* ==================== CL_ProcessFile A file has been received via the fragmentation/reassembly layer, put it in the right spot and see if we have finished downloading files. ==================== */ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { if( successfully_received) MsgDev( D_INFO, "Received %s\n", filename ); else MsgDev( D_WARN, "Failed to download %s\n", filename ); if( cls.downloadfileid == cls.downloadcount - 1 ) { MsgDev( D_INFO, "Download completed, resuming connection\n" ); FS_Rescan(); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); cls.downloadfileid = 0; cls.downloadcount = 0; return; } cls.downloadfileid++; }
int SV_ModelIndex( const char *filename ) { char name[64]; int i; if( !filename || !filename[0] ) return 0; if( *filename == '!' ) filename++; Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) { if( !Q_stricmp( sv.model_precache[i], name )) return i; } if( i == MAX_MODELS ) { Host_Error( "SV_ModelIndex: MAX_MODELS limit exceeded\n" ); return 0; } // register new model Q_strncpy( sv.model_precache[i], name, sizeof( sv.model_precache[i] )); if( sv.state != ss_loading ) { // send the update to everyone BF_WriteByte( &sv.reliable_datagram, svc_modelindex ); BF_WriteUBitLong( &sv.reliable_datagram, i, MAX_MODEL_BITS ); BF_WriteString( &sv.reliable_datagram, name ); } return i; }
int SV_SoundIndex( const char *filename ) { char name[64]; int i; // don't precache sentence names! if( !filename || !filename[0] || filename[0] == '!' ) return 0; Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); for( i = 1; i < MAX_SOUNDS && sv.sound_precache[i][0]; i++ ) { if( !Q_stricmp( sv.sound_precache[i], name )) return i; } if( i == MAX_SOUNDS ) { Host_Error( "SV_SoundIndex: MAX_SOUNDS limit exceeded\n" ); return 0; } // register new sound Q_strncpy( sv.sound_precache[i], name, sizeof( sv.sound_precache[i] )); if( sv.state != ss_loading ) { // send the update to everyone BF_WriteByte( &sv.reliable_datagram, svc_soundindex ); BF_WriteUBitLong( &sv.reliable_datagram, i, MAX_SOUND_BITS ); BF_WriteString( &sv.reliable_datagram, name ); } return i; }
/* ============================== 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 ); 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 ); }
/* ===================== Delta_WriteField write fields by offsets assume from and to is valid ===================== */ qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, float timebase ) { qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; float flValue, flAngle, flTime; uint iValue; const char *pStr; if( Delta_CompareField( pField, from, to, timebase )) { BF_WriteOneBit( msg, 0 ); // unchanged return false; } BF_WriteOneBit( msg, 1 ); // changed if( pField->flags & DT_BYTE ) { iValue = *(byte *)((byte *)to + pField->offset ); iValue = Delta_ClampIntegerField( iValue, bSigned, pField->bits ); if ( pField->multiplier != 1.0f ) iValue *= pField->multiplier; BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_SHORT ) { iValue = *(word *)((byte *)to + pField->offset ); iValue = Delta_ClampIntegerField( iValue, bSigned, pField->bits ); if ( pField->multiplier != 1.0f ) iValue *= pField->multiplier; BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_INTEGER ) { iValue = *(uint *)((byte *)to + pField->offset ); iValue = Delta_ClampIntegerField( iValue, bSigned, pField->bits ); if ( pField->multiplier != 1.0f ) iValue *= pField->multiplier; BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_FLOAT ) { #ifdef __arm__ memcpy(&flValue,(byte *)to + pField->offset, sizeof(float) ); #else flValue = *(float *)((byte *)to + pField->offset ); #endif iValue = (int)(flValue * pField->multiplier); BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_ANGLE ) { #ifdef __arm__ memcpy(&flAngle,(byte *)to + pField->offset, sizeof(float) ); #else flAngle = *(float *)((byte *)to + pField->offset ); #endif // NOTE: never applies multipliers to angle because // result may be wrong on client-side BF_WriteBitAngle( msg, flAngle, pField->bits ); } else if( pField->flags & DT_TIMEWINDOW_8 ) { #ifdef __arm__ memcpy(&flValue,(byte *)to + pField->offset, sizeof(float) ); #else flValue = *(float *)((byte *)to + pField->offset ); #endif flTime = (timebase * 100.0f) - (flValue * 100.0f); #if 1 iValue = (uint)fabs( flTime ); #else iValue = (uint)fabs( flTime ); if (flTime<0.0f) { iValue |= 0x80000000; } #endif BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_TIMEWINDOW_BIG ) { #ifdef __arm__ memcpy(&flValue,(byte *)to + pField->offset, sizeof(float) ); #else flValue = *(float *)((byte *)to + pField->offset ); #endif flTime = (timebase * pField->multiplier) - (flValue * pField->multiplier); #if 1 iValue = (uint)fabs( flTime ); #else iValue = (uint)fabs( flTime ); if (flTime<0.0f) { iValue |= 0x80000000; } #endif BF_WriteBitLong( msg, iValue, pField->bits, bSigned ); } else if( pField->flags & DT_STRING ) { pStr = (char *)((byte *)to + pField->offset ); BF_WriteString( msg, pStr ); } return true; }
/* ======================= 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 ); }
/* ============================== 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; }
/* =================== 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 ); }
/* ================= 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 ); }