Пример #1
0
/*
==================
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 ));
}
Пример #2
0
/*
==============================
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;
	}
}
Пример #3
0
/*
==============
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 );

}
Пример #4
0
/*
====================
CL_WriteDemoMessage

Dumps the current net message, prefixed by the length
====================
*/
void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg )
{
	file_t	*file = startup ? cls.demoheader : cls.demofile;
	int	swlen;
	byte	c;

	if( !file ) return;

	// past the start but not recording a demo.
	if( !startup && !cls.demorecording )
		return;

	swlen = BF_GetNumBytesWritten( msg ) - start;
	if( swlen <= 0 ) return;

	if( !startup )
	{
		cls.demotime += host.frametime;
		demo.framecount++;
	}

	// demo playback should read this as an incoming message.
	c = (cls.state != ca_active) ? dem_norewind : dem_read;

	CL_WriteDemoCmdHeader( c, file );
	CL_WriteDemoSequence( file );

	// write the length out.
	FS_Write( file, &swlen, sizeof( int ));

	// output the buffer. Skip the network packet stuff.
	FS_Write( file, BF_GetData( msg ) + start, swlen );
}
Пример #5
0
/*
=====================
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 ));
}
Пример #6
0
/*
==============================
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 );
}
Пример #7
0
/*
===============
Netchan_OutOfBand

Sends an out-of-band datagram
================
*/
void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data )
{
	sizebuf_t	send;
	byte	send_buf[NET_MAX_PAYLOAD];

	// write the packet header
	BF_Init( &send, "SequencePacket", send_buf, sizeof( send_buf ));
	
	BF_WriteLong( &send, -1 );	// -1 sequence means out of band
	BF_WriteBytes( &send, data, length );

	if( !CL_IsPlaybackDemo( ))
	{
		// send the datagram
		NET_SendPacket( net_socket, BF_GetNumBytesWritten( &send ), BF_GetData( &send ), adr );
	}
}
Пример #8
0
/*
====================
CL_WriteDemoUserCmd

Writes the current user cmd
====================
*/
void CL_WriteDemoUserCmd( int cmdnumber )
{
	sizebuf_t	buf;
	word	bytes;
	byte	data[1024];

	if( !cls.demorecording || !cls.demofile )
		return;

	CL_WriteDemoCmdHeader( dem_usercmd, cls.demofile );

	FS_Write( cls.demofile, &cls.netchan.outgoing_sequence, sizeof( int ));
	FS_Write( cls.demofile, &cmdnumber, sizeof( int ));

	// write usercmd_t
	BF_Init( &buf, "UserCmd", data, sizeof( data ));
	CL_WriteUsercmd( &buf, -1, cmdnumber );	// always no delta

	bytes = BF_GetNumBytesWritten( &buf );

	FS_Write( cls.demofile, &bytes, sizeof( word ));
	FS_Write( cls.demofile, data, bytes );
}
Пример #9
0
/*
==============================
Netchan_CheckForCompletion

==============================
*/
void Netchan_CheckForCompletion( netchan_t *chan, int stream, int intotalbuffers )
{
	int	c, id;
	int	size;
	fragbuf_t	*p;

	size = 0;
	c = 0;

	p = chan->incomingbufs[stream];
	if( !p ) return;

	while( p )
	{
		size += BF_GetNumBytesWritten( &p->frag_message );
		c++;

		id = FRAG_GETID( p->bufferid );
		if( id != c )
		{
			if( chan->sock == NS_CLIENT )
			{
				MsgDev( D_ERROR, "Lost/dropped fragment would cause stall, retrying connection\n" );
				Cbuf_AddText( "reconnect\n" );
			}
		}
		p = p->next;
	}

	// received final message
	if( c == intotalbuffers )
	{
		chan->incomingready[stream] = true;
		MsgDev( D_NOTE, "\nincoming is complete %i bytes waiting\n", size );
	}
}
Пример #10
0
/*
===================
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 );
}
Пример #11
0
/*
==============================
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;
}
Пример #12
0
/*
==============================
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;
}
Пример #13
0
/*
==============================
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;
	}
}
Пример #14
0
/*
===============
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];
				}
			}
		}
	}