/* ================= CL_ReadNetMessage ================= */ void CL_ReadNetMessage( void ) { size_t curSize; while( CL_GetMessage( net_message_buffer, &curSize )) { BF_Init( &net_message, "ServerData", net_message_buffer, curSize ); // check for connectionless packet (0xffffffff) first if( BF_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) { CL_ConnectionlessPacket( net_from, &net_message ); continue; } // can't be a valid sequenced packet if( cls.state < ca_connected ) continue; if( BF_GetMaxBytes( &net_message ) < 8 ) { MsgDev( D_WARN, "%s: runt packet\n", NET_AdrToString( net_from )); continue; } // packet from server if( !cls.demoplayback && !NET_CompareAdr( net_from, cls.netchan.remote_address )) { MsgDev( D_ERROR, "CL_ReadPackets: %s:sequenced packet without connection\n", NET_AdrToString( net_from )); continue; } if( !cls.demoplayback && !Netchan_Process( &cls.netchan, &net_message )) continue; // wasn't accepted for some reason CL_ParseServerMessage( &net_message ); } // check for fragmentation/reassembly related packets. if( cls.state != ca_disconnected && Netchan_IncomingReady( &cls.netchan )) { // the header is different lengths for reliable and unreliable messages int headerBytes = BF_GetNumBytesRead( &net_message ); // process the incoming buffer(s) if( Netchan_CopyNormalFragments( &cls.netchan, &net_message )) { CL_ParseServerMessage( &net_message ); } if( Netchan_CopyFileFragments( &cls.netchan, &net_message )) { // remove from resource request stuff. CL_ProcessFile( true, cls.netchan.incomingfilename ); } } Netchan_UpdateProgress( &cls.netchan ); }
/* ===================== CL_WriteMessageHistory list last 32 messages for debugging net troubleshooting ===================== */ void CL_WriteMessageHistory( void ) { int i, thecmd; oldcmd_t *old, *failcommand; sizebuf_t *msg = &net_message; if( !cls.initialized || cls.state == ca_disconnected ) return; if( !cls_message_debug.parsing ) return; MsgDev( D_INFO, "Last %i messages parsed.\n", MSG_COUNT ); // finish here thecmd = cls_message_debug.currentcmd - 1; thecmd -= ( MSG_COUNT - 1 ); // back up to here for( i = 0; i < MSG_COUNT - 1; i++ ) { thecmd &= MSG_MASK; old = &cls_message_debug.oldcmd[thecmd]; MsgDev( D_INFO,"%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); thecmd++; } failcommand = &cls_message_debug.oldcmd[thecmd]; MsgDev( D_INFO, "BAD: %3i:%s\n", BF_GetNumBytesRead( msg ) - 1, CL_MsgInfo( failcommand->command )); if( host.developer >= 3 ) { CL_WriteErrorMessage( BF_GetNumBytesRead( msg ) - 1, msg ); } cls_message_debug.parsing = false; }
/* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( sizebuf_t *msg ) { char *s; int i, j, cmd; int param1, param2; int bufStart; cls_message_debug.parsing = true; // begin parsing starting_count = BF_GetNumBytesRead( msg ); // updates each frame // parse the message while( 1 ) { if( BF_CheckOverflow( msg )) { Host_Error( "CL_ParseServerMessage: overflow!\n" ); return; } // mark start position bufStart = BF_GetNumBytesRead( msg ); // end of message if( BF_GetNumBitsLeft( msg ) < 8 ) break; cmd = BF_ReadByte( msg ); // record command for debugging spew on parse problem CL_Parse_RecordCommand( cmd, bufStart ); // other commands switch( cmd ) { case svc_bad: Host_Error( "svc_bad\n" ); break; case svc_nop: // this does nothing break; case svc_disconnect: MsgDev( D_INFO, "Disconnected from server\n" ); CL_Drop (); Host_AbortCurrentFrame (); break; case svc_changing: if( BF_ReadOneBit( msg )) { cls.changelevel = true; S_StopAllSounds(); if( cls.demoplayback ) { SCR_BeginLoadingPlaque( cl.background ); cls.changedemo = true; } } else MsgDev( D_INFO, "Server disconnected, reconnecting\n" ); CL_ClearState (); CL_InitEdicts (); // re-arrange edicts if( cls.demoplayback ) { cl.background = (cls.demonum != -1) ? true : false; cls.state = ca_connected; } else cls.state = ca_connecting; cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately break; case svc_setview: cl.refdef.viewentity = BF_ReadWord( msg ); break; case svc_sound: CL_ParseSoundPacket( msg, false ); break; case svc_time: // shuffle timestamps cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = BF_ReadFloat( msg ); break; case svc_print: i = BF_ReadByte( msg ); MsgDev( D_INFO, "^6%s", BF_ReadString( msg )); if( i == PRINT_CHAT ) S_StartLocalSound( "common/menu2.wav", VOL_NORM, false ); break; case svc_stufftext: CL_ParseStuffText( msg ); break; case svc_lightstyle: CL_ParseLightStyle( msg ); break; case svc_setangle: CL_ParseSetAngle( msg ); break; case svc_serverdata: Cbuf_Execute(); // make sure any stuffed commands are done CL_ParseServerData( msg ); break; case svc_addangle: CL_ParseAddAngle( msg ); break; case svc_clientdata: CL_ParseClientData( msg ); break; case svc_packetentities: CL_ParsePacketEntities( msg, false ); break; case svc_deltapacketentities: CL_ParsePacketEntities( msg, true ); break; case svc_updatepings: CL_UpdateUserPings( msg ); break; case svc_usermessage: CL_RegisterUserMessage( msg ); break; case svc_particle: CL_ParseParticles( msg ); break; case svc_restoresound: CL_ParseRestoreSoundPacket( msg ); break; case svc_spawnstatic: CL_ParseStaticEntity( msg ); break; case svc_ambientsound: CL_ParseSoundPacket( msg, true ); break; case svc_crosshairangle: CL_ParseCrosshairAngle( msg ); break; case svc_spawnbaseline: CL_ParseBaseline( msg ); break; case svc_temp_entity: CL_ParseTempEntity( msg ); break; case svc_setpause: cl.refdef.paused = ( BF_ReadOneBit( msg ) != 0 ); break; case svc_deltamovevars: CL_ParseMovevars( msg ); break; case svc_customization: CL_ParseCustomization( msg ); break; case svc_centerprint: CL_CenterPrint( BF_ReadString( msg ), 0.25f ); break; case svc_event: CL_ParseEvent( msg ); break; case svc_event_reliable: CL_ParseReliableEvent( msg ); break; case svc_updateuserinfo: CL_UpdateUserinfo( msg ); break; case svc_intermission: cl.refdef.intermission = true; break; case svc_modelindex: CL_PrecacheModel( msg ); break; case svc_soundindex: CL_PrecacheSound( msg ); break; case svc_soundfade: CL_ParseSoundFade( msg ); break; case svc_cdtrack: param1 = BF_ReadByte( msg ); param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum param2 = BF_ReadByte( msg ); param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0 ); break; case svc_serverinfo: CL_ServerInfo( msg ); break; case svc_eventindex: CL_PrecacheEvent( msg ); break; case svc_deltatable: Delta_ParseTableField( msg ); break; case svc_weaponanim: param1 = BF_ReadByte( msg ); // iAnim param2 = BF_ReadByte( msg ); // body CL_WeaponAnim( param1, param2 ); break; case svc_bspdecal: CL_ParseStaticDecal( msg ); break; case svc_roomtype: param1 = BF_ReadShort( msg ); Cvar_SetFloat( "room_type", param1 ); break; case svc_chokecount: i = BF_ReadByte( msg ); j = cls.netchan.incoming_acknowledged - 1; for( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- ) { if( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 ) { cl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0; i--; } } break; case svc_resourcelist: CL_ParseResourceList( msg ); break; case svc_director: CL_ParseDirector( msg ); break; case svc_studiodecal: CL_ParseStudioDecal( msg ); break; case svc_querycvarvalue: CL_ParseCvarValue( msg ); break; case svc_querycvarvalue2: CL_ParseCvarValue2( msg ); break; default: CL_ParseUserMessage( msg, cmd ); break; } } cls_message_debug.parsing = false; // done // we don't know if it is ok to save a demo message until // after we have parsed the frame if( !cls.demoplayback ) { if( cls.demorecording && !cls.demowaiting ) { CL_WriteDemoMessage( false, starting_count, msg ); } else if( cls.state != ca_active ) { CL_WriteDemoMessage( true, starting_count, msg ); } } }
/* ============================== 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; }