コード例 #1
0
ファイル: cl_parse.c プロジェクト: Turupawn/DogeWarsow
/*
* CL_CheckOrDownloadFile
* 
* Returns true if the file exists or couldn't send download request
* Files with .pk3 or .pak extension have to have gamedir attached
* Other files must not have gamedir
*/
qboolean CL_CheckOrDownloadFile( const char *filename )
{
	const char *ext;

	if( !cl_downloads->integer )
		return qtrue;

	if( !COM_ValidateRelativeFilename( filename ) )
		return qtrue;

	ext = COM_FileExtension( filename );
	if( !ext || !*ext )
		return qtrue;

	if( FS_CheckPakExtension( filename ) )
	{
		if( FS_FOpenBaseFile( filename, NULL, FS_READ ) != -1 )
			return qtrue;
	}
	else
	{
		if( FS_FOpenFile( filename, NULL, FS_READ ) != -1 )
			return qtrue;
	}

	if( !CL_DownloadRequest( filename, qtrue ) )
		return qtrue;

	cls.download.requestnext = qtrue; // call CL_RequestNextDownload when done

	return qfalse;
}
コード例 #2
0
ファイル: cl_parse.c プロジェクト: Turupawn/DogeWarsow
/*
* CL_DownloadRequest
* 
* Request file download
* return qfalse if couldn't request it for some reason
* Files with .pk3 or .pak extension have to have gamedir attached
* Other files must not have gamedir
*/
qboolean CL_DownloadRequest( const char *filename, qboolean requestpak )
{
	if( cls.download.requestname )
	{
		Com_Printf( "Can't download: %s. Download already in progress.\n", filename );
		return qfalse;
	}

	if( !COM_ValidateRelativeFilename( filename ) )
	{
		Com_Printf( "Can't download: %s. Invalid filename.\n", filename );
		return qfalse;
	}

	if( FS_CheckPakExtension( filename ) )
	{
		if( FS_FOpenBaseFile( filename, NULL, FS_READ ) != -1 )
		{
			Com_Printf( "Can't download: %s. File already exists.\n", filename );
			return qfalse;
		}

		if( !Q_strnicmp( COM_FileBase( filename ), "modules", strlen( "modules" ) ) )
		{
			if( !CL_CanDownloadModules() )
				return qfalse;
		}
	}
	else
	{
		if( FS_FOpenFile( filename, NULL, FS_READ ) != -1 )
		{
			Com_Printf( "Can't download: %s. File already exists.\n", filename );
			return qfalse;
		}
	}

	if( cls.socket->type == SOCKET_LOOPBACK )
	{
		Com_DPrintf( "Can't download: %s. Loopback server.\n", filename );
		return qfalse;
	}

	Com_Printf( "Asking to download: %s\n", filename );

	cls.download.requestpak = requestpak;
	cls.download.requestname = Mem_ZoneMalloc( sizeof( char ) * ( strlen( filename ) + 1 ) );
	Q_strncpyz( cls.download.requestname, filename, sizeof( char ) * ( strlen( filename ) + 1 ) );
	cls.download.timeout = Sys_Milliseconds() + 5000;
	CL_AddReliableCommand( va( "download %i \"%s\"", requestpak, filename ) );

	return qtrue;
}
コード例 #3
0
ファイル: sv_client.c プロジェクト: codetwister/qfusion
static qboolean SV_FilenameForDownloadRequest( const char *requestname, qboolean requestpak,
	const char **uploadname, const char **errormsg )
{
	if( FS_CheckPakExtension( requestname ) )
	{
		if( !requestpak )
		{
			*errormsg = "Pak file requested as a non pak file";
			return qfalse;
		}
		if( FS_FOpenBaseFile( requestname, NULL, FS_READ ) == -1 )
		{
			*errormsg = "File not found";
			return qfalse;
		}

		*uploadname = requestname;
	}
	else
	{
		if( FS_FOpenFile( requestname, NULL, FS_READ ) == -1 )
		{
			*errormsg = "File not found";
			return qfalse;
		}

		// check if file is inside a PAK
		if( requestpak )
		{
			*uploadname = FS_PakNameForFile( requestname );
			if( !*uploadname )
			{
				*errormsg = "File not available in pack";
				return qfalse;
			}
		}
		else
		{
			*uploadname = FS_BaseNameForFile( requestname );
			if( !*uploadname )
			{
				*errormsg = "File only available in pack";
				return qfalse;
			}
		}
	}
	return qtrue;
}
コード例 #4
0
ファイル: sv_ccmds.c プロジェクト: Racenet/racesow
/*
* SV_AutoUpdateFromWeb
*/
void SV_AutoUpdateFromWeb( qboolean checkOnly )
{
	static const char *autoUpdateBaseUrl = APP_UPDATE_URL APP_SERVER_UPDATE_DIRECTORY;
	char checksumString1[32], checksumString2[32];
	unsigned int checksum;
	qboolean success;
	int length, filenum;
	qbyte *data;
	const char *token, *ptr;
	char path[MAX_QPATH];
	int downloadCount = 0, downloadFailed = 0;
	char newVersionTag[MAX_QPATH];
	qboolean newVersion = qfalse;

	if( !dedicated->integer )
		return;

	assert( svs.mapcmd[0] );

	if( !checkOnly )
		SV_UpdateActivity();

	Com_Printf( "\n" );
	Com_Printf( "========== Starting Auto Update ===========\n" );

	Com_Printf( "Checking for updates\n" );

	// download the update file list
	success = SV_WebDownload( autoUpdateBaseUrl, APP_SERVER_UPDATE_FILE, qtrue, qtrue );

	// set as last updated today
	if( !checkOnly )
		Cvar_ForceSet( "sv_lastAutoUpdate", va( "%i", (int)Com_DaysSince1900() ) );

	if( !success ) // no update to do
		goto done;

	// read the file list
	if( ( length = FS_FOpenBaseFile( APP_SERVER_UPDATE_FILE, &filenum, FS_READ ) ) == -1 )
	{
		Com_Printf( "WARNING: Couldn't find %s\n", path );
		goto done;
	}

	if( !length )
	{
		FS_FCloseFile( filenum );
		goto done;
	}

	data = Mem_TempMalloc( length + 1 );
	FS_Read( data, length, filenum );
	FS_FCloseFile( filenum );
	FS_RemoveBaseFile( APP_SERVER_UPDATE_FILE );

	ptr = (const char *)data;

	// first token is always the current release version
	token = COM_ParseExt( &ptr, qtrue );
	if( !token[0] )
		goto cancel;

	// compare versions
	Q_strncpyz( newVersionTag, token, sizeof( newVersionTag ) );
	if( atof( newVersionTag ) > atof( va( "%4.3f", APP_VERSION ) ) )
		newVersion = qtrue;

	while( ptr )
	{
		// we got what should be a checksum
		token = COM_ParseExt( &ptr, qtrue );
		if( !token[0] )
			goto cancel;

		// copy checksum reported by server
		Q_strncpyz( checksumString1, token, sizeof( checksumString1 ) );

		// get filename
		token = COM_ParseExt( &ptr, qtrue );
		if( !token[0] )
			goto cancel;

		// filename should never begin with a slash
		if( token[0] == '/' )
			token++;

		Q_strncpyz( path, token, sizeof( path ) );

		// we got what should be a file path
		if( !COM_ValidateRelativeFilename( path ) )
		{
			Com_Printf( "WARNING: Invalid filename %s\n", path );
			continue;
		}

		checksum = FS_ChecksumBaseFile( path );
		Q_snprintfz( checksumString2, sizeof( checksumString2 ), "%u", checksum );

		// if same checksum no need to update
		if( !strcmp( checksumString1, checksumString2 ) )
			continue;

		// if it's a pack file and the file exists it can't be replaced, so skip
		if( FS_CheckPakExtension( path ) && checksum )
		{
			Com_Printf( "WARNING: Purity check failed for: %s\n", path );
			Com_Printf( "WARNING: This file has been locally modified. It is highly \n" );
			Com_Printf( "WARNING: recommended to restore the original file.\n" );
			Com_Printf( "WARNING: Reinstalling \""APPLICATION"\" might be convenient.\n" );
			continue;	
		}

		if( checkOnly )
		{
			Com_Printf( "File update available : %s\n", path );
			continue;
		}
		
		if( developer->integer )
			Com_Printf( "Downloading update of %s (checksum %s local checksum %s)\n", path, checksumString1, checksumString2 );
		else
			Com_Printf( "Updating %s\n", path );

		if( !SV_WebDownload( autoUpdateBaseUrl, path, qtrue, qtrue ) )
		{
			Com_Printf( "Failed to update %s\n", path );
			downloadFailed++;
		}

		downloadCount++;
	}

cancel:
	Mem_TempFree( data );
done:
	if( newVersion )
	{
		if( downloadCount )
		{
			if( downloadFailed )
				Com_Printf( "This version of "APPLICATION" was updated incompletely\n" );
			else
				Com_Printf( "This version of "APPLICATION" was updated successfully\n\n" );
		}

		Com_Printf( "****** Version %s of "APPLICATION" is available. ******\n", newVersionTag );
		Com_Printf( "****** Please download the new version at "APP_URL" ******\n" );
	}
	else if( downloadCount )
	{
		if( downloadFailed )
			Com_Printf( APPLICATION" was updated incompletely\n" );
		else
			Com_Printf( APPLICATION" was updated successfully\n" );
	}
	else if( !checkOnly )
	{
		if( downloadFailed )
			Com_Printf( "At least one file failed to update\n" );
		else
			Com_Printf( APPLICATION" is up to date\n" );
	}

	Com_Printf( "========== Auto Update Finished ===========\n" );
	Com_Printf( "\n" );

	// update the map list, which also does a filesystem rescan
	ML_Update();

	// if there are any new filesystem entries, restart
	if( FS_GetNotifications() & FS_NOTIFT_NEWPAKS )
	{
		if( sv.state != ss_dead )
		{
			// restart the current map, SV_Map also rescans the filesystem
			Com_Printf( "The server will now restart...\n\n" );

			// start the default map if current map isn't available
			Cbuf_ExecuteText( EXEC_APPEND, va( "map %s\n", svs.mapcmd[0] ? svs.mapcmd : sv_defaultmap->string ) );
		}
	}
}
コード例 #5
0
ファイル: sv_ccmds.c プロジェクト: Racenet/racesow
/*
* SV_WebDownload
*/
static qboolean SV_WebDownload( const char *baseUrl, const char *filepath, qboolean overwrite, qboolean silent )
{
	qboolean success;
	int alloc_size;
	char *temppath, *writepath, *url;

	if( developer->integer )
		silent = qfalse;

	if( !baseUrl || !baseUrl[0] || !filepath )
		return qfalse;

	if( !strrchr( baseUrl, '/' ) )
	{
		if( !silent )
			Com_Printf( "SV_WebDownload: Invalid URL\n" );
		return qfalse;
	}

	if( filepath[0] == '/' ) // filepath should never begin with a slash
		filepath++;

	if( !COM_ValidateRelativeFilename( filepath ) )
	{
		if( !silent )
			Com_Printf( "SV_WebDownload: Invalid filename\n" );
		return qfalse;
	}

	if( !COM_FileExtension( filepath ) )
	{
		if( !silent )
			Com_Printf( "SV_WebDownload: no file extension\n" );
		return qfalse;
	}

	// full url (baseurl + path)
	alloc_size = strlen( baseUrl ) + 1 + strlen( filepath ) + 1;
	url = Mem_TempMalloc( alloc_size );
	if( baseUrl[ strlen( baseUrl ) - 1 ] == '/' ) // url includes last slash
		Q_snprintfz( url, alloc_size, "%s%s", baseUrl, filepath );
	else
		Q_snprintfz( url, alloc_size, "%s/%s", baseUrl, filepath );

	// add .tmp (relative + .tmp)
	alloc_size = strlen( filepath ) + strlen( ".tmp" ) + 1;
	temppath = Mem_TempMalloc( alloc_size );
	Q_snprintfz( temppath, alloc_size, "%s.tmp", filepath );

	// full write path for curl
	alloc_size = strlen( FS_WriteDirectory() ) + 1 + strlen( temppath ) + 1;
	writepath = Mem_TempMalloc( alloc_size );
	Q_snprintfz( writepath, alloc_size, "%s/%s", FS_WriteDirectory(), temppath );

	webDownloadPercentPrint = 0;
	webDownloadPercentStarted = qfalse;

	success = Web_Get( url, NULL, writepath, qtrue, 60 * 30, 60, SV_WebDownloadProgress, qfalse );

	if( webDownloadPercentStarted )
		Com_Printf( "\n" );

	if( !success )
	{
		if( !silent )
			Com_Printf( "Failed to download remote file.\n" );
		goto failed;
	}

	// rename the downloaded file
	if( !FS_MoveBaseFile( temppath, filepath ) )
	{
		if( !overwrite )
		{
			if( !silent )
				Com_Printf( "Failed to rename temporary file.\n" );
			goto failed;
		}

		// check if it failed because there already exists a file with the same name
		// and in this case remove this file
		if( FS_FOpenBaseFile( filepath, NULL, FS_READ ) != -1 )
		{
			char *backfile;

			alloc_size = strlen( filepath ) + strlen( ".bak" ) + 1;
			backfile = Mem_TempMalloc( alloc_size );
			Q_snprintfz( backfile, alloc_size, "%s.bak", filepath );

			// if there is already a .bak file, destroy it
			if( FS_FOpenBaseFile( backfile, NULL, FS_READ ) != -1 )
				FS_RemoveBaseFile( backfile );

			// move the current file into .bak file
			if( !FS_MoveBaseFile( filepath, backfile ) )
			{
				Mem_TempFree( backfile );
				if( !silent )
					Com_Printf( "Failed to backup destination file.\n" );
				goto failed;
			}

			// now try renaming the downloaded file again
			if( !FS_MoveBaseFile( temppath, filepath ) )
			{
				// didn't work, so restore the backup file
				if( FS_MoveBaseFile( backfile, filepath ) )
				{
					if( !silent )
						Com_Printf( "Failed to rename temporary file, restoring from backup.\n" );
				}
				else
				{
					if( !silent )
						Com_Printf( "Failed to rename temporary file and restore from backup.\n" );
				}

				Mem_TempFree( backfile );
				goto failed;
			}

			Mem_TempFree( backfile );
		}
	}

	Mem_TempFree( temppath );
	Mem_TempFree( writepath );
	Mem_TempFree( url );

	return qtrue;

failed:
	if( !silent )
		Com_Printf( "Removing temporary file: %s\n", writepath );
	FS_RemoveAbsoluteFile( writepath );
	Mem_TempFree( temppath );
	Mem_TempFree( writepath );
	Mem_TempFree( url );

	return qfalse;
}
コード例 #6
0
ファイル: cl_parse.c プロジェクト: Turupawn/DogeWarsow
/*
* CL_InitDownload_f
* 
* Hanldles server's initdownload message, starts web or server download if possible
*/
 static void CL_InitDownload_f( void )
{
	const char *filename;
	const char *url;
	int size, alloc_size;
	unsigned checksum;
	qboolean allow_localhttpdownload;
	download_list_t	*dl;
	// ignore download commands coming from demo files
	if( cls.demo.playing )		
		return;

	// read the data
	filename = Cmd_Argv( 1 );
	size = atoi( Cmd_Argv( 2 ) );
	checksum = strtoul( Cmd_Argv( 3 ), NULL, 10 );
	allow_localhttpdownload = ( atoi( Cmd_Argv( 4 ) ) != 0 ) && cls.httpbaseurl != NULL;
	url = Cmd_Argv( 5 );

	if( !cls.download.requestname )
	{
		Com_Printf( "Got init download message without request\n" );
		return;
	}

	if( cls.download.filenum || cls.download.web )
	{
		Com_Printf( "Got init download message while already downloading\n" );
		return;
	}

	if( size == -1 )
	{
		// means that download was refused
		Com_Printf( "Server refused download request: %s\n", url ); // if it's refused, url field holds the reason
		CL_DownloadDone();
		return;
	}

	if( size <= 0 )
	{
		Com_Printf( "Server gave invalid size, not downloading\n" );
		CL_DownloadDone();
		return;
	}

	if( checksum == 0 )
	{
		Com_Printf( "Server didn't provide checksum, not downloading\n" );
		CL_DownloadDone();
		return;
	}

	if( !COM_ValidateRelativeFilename( filename ) )
	{
		Com_Printf( "Not downloading, invalid filename: %s\n", filename );
		CL_DownloadDone();
		return;
	}

	if( FS_CheckPakExtension( filename ) && !cls.download.requestpak )
	{
		Com_Printf( "Got a pak file when requesting normal one, not downloading\n" );
		CL_DownloadDone();
		return;
	}

	if( !FS_CheckPakExtension( filename ) && cls.download.requestpak )
	{
		Com_Printf( "Got a non pak file when requesting pak, not downloading\n" );
		CL_DownloadDone();
		return;
	}

	if( !strchr( filename, '/' ) )
	{
		Com_Printf( "Refusing to download file with no gamedir: %s\n", filename );
		CL_DownloadDone();
		return;
	}

	// check that it is in game or basegame dir
	if( strlen( filename ) < strlen( FS_GameDirectory() )+1 ||
		strncmp( filename, FS_GameDirectory(), strlen( FS_GameDirectory() ) ) ||
		filename[strlen( FS_GameDirectory() )] != '/' )
	{
		if( strlen( filename ) < strlen( FS_BaseGameDirectory() )+1 ||
			strncmp( filename, FS_BaseGameDirectory(), strlen( FS_BaseGameDirectory() ) ) ||
			filename[strlen( FS_BaseGameDirectory() )] != '/' )
		{
			Com_Printf( "Can't download, invalid game directory: %s\n", filename );
			CL_DownloadDone();
			return;
		}
	}

	if( FS_CheckPakExtension( filename ) )
	{
		if( strchr( strchr( filename, '/' ) + 1, '/' ) )
		{
			Com_Printf( "Refusing to download pack file to subdirectory: %s\n", filename );
			CL_DownloadDone();
			return;
		}

		if( !Q_strnicmp( COM_FileBase( filename ), "modules", strlen( "modules" ) ) )
		{
			if( !CL_CanDownloadModules() )
			{
				CL_DownloadDone();
				return;
			}
		}

		if( FS_FOpenBaseFile( filename, NULL, FS_READ ) != -1 )
		{
			Com_Printf( "Can't download, file already exists: %s\n", filename );
			CL_DownloadDone();
			return;
		}
	}
	else
	{
		if( strcmp( cls.download.requestname, strchr( filename, '/' ) + 1 ) )
		{
			Com_Printf( "Can't download, got different file than requested: %s\n", filename );
			CL_DownloadDone();
			return;
		}
	}

	if( cls.download.requestnext )
	{
		dl = cls.download.list;
		while( dl != NULL )
		{
			if( !Q_stricmp( dl->filename, filename ) )
			{
				Com_Printf( "Skipping, already tried downloading: %s\n", filename );
				CL_DownloadDone();
				return;
			}
			dl = dl->next;
		}
	}

	cls.download.name = ZoneCopyString( filename );

	alloc_size = strlen( filename ) + strlen( ".tmp" ) + 1;
	cls.download.tempname = Mem_ZoneMalloc( alloc_size );
	Q_snprintfz( cls.download.tempname, alloc_size, "%s.tmp", filename );

	cls.download.web = qfalse;
	cls.download.cancelled = qfalse;
	cls.download.disconnect = qfalse;
	cls.download.size = size;
	cls.download.checksum = checksum;
	cls.download.percent = 0;
	cls.download.timeout = 0;
	cls.download.retries = 0;
	cls.download.timestart = Sys_Milliseconds();
	cls.download.offset = 0;
	cls.download.baseoffset = 0;
	cls.download.pending_reconnect = qfalse;

	Cvar_ForceSet( "cl_download_name", COM_FileBase( cls.download.name ) );
	Cvar_ForceSet( "cl_download_percent", "0" );

	if( cls.download.requestnext )
	{
		dl = Mem_ZoneMalloc( sizeof( download_list_t ) );
		dl->filename = ZoneCopyString( cls.download.name );
		dl->next = cls.download.list;
		cls.download.list = dl;
	}

	if( cl_downloads_from_web->integer && allow_localhttpdownload && url && url[0] != 0 ) {
		cls.download.web = qtrue;
		Com_Printf( "Web download: %s from %s%s\n", cls.download.tempname, cls.httpbaseurl, url );
	}
	else if( cl_downloads_from_web->integer && url && url[0] != 0 ) {
		cls.download.web = qtrue;
		Com_Printf( "Web download: %s from %s\n", cls.download.tempname, url );
	}
	else {
		Com_Printf( "Server download: %s\n", cls.download.tempname );
	}

	cls.download.baseoffset = cls.download.offset = FS_FOpenBaseFile( cls.download.tempname, &cls.download.filenum, FS_APPEND );
	if( !cls.download.filenum )
	{
		Com_Printf( "Can't download, couldn't open %s for writing\n", cls.download.tempname );

		Mem_ZoneFree( cls.download.name );
		cls.download.name = NULL;
		Mem_ZoneFree( cls.download.tempname );
		cls.download.tempname = NULL;

		cls.download.filenum = 0;
		cls.download.offset = 0;
		cls.download.size = 0;
		CL_DownloadDone();
		return;
	}

	if( cls.download.web ) {
		char *referer, *fullurl;
		const char *headers[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };

		alloc_size = strlen( APP_URI_SCHEME ) + strlen( NET_AddressToString( &cls.serveraddress ) ) + 1;
		referer = Mem_ZoneMalloc( alloc_size );
		Q_snprintfz( referer, alloc_size, APP_URI_SCHEME "%s", NET_AddressToString( &cls.serveraddress ) );
		Q_strlwr( referer );

		if( allow_localhttpdownload ) {
			alloc_size = strlen( cls.httpbaseurl ) + 1 + strlen( url ) + 1;
			fullurl = Mem_ZoneMalloc( alloc_size );
			Q_snprintfz( fullurl, alloc_size, "%s/%s", cls.httpbaseurl, url );
		}
		else {
			size_t url_len = strlen( url );
			alloc_size = url_len + 1 + strlen( filename ) * 3 + 1;
			fullurl = Mem_ZoneMalloc( alloc_size );
			Q_snprintfz( fullurl, alloc_size, "%s/", url );
			Q_urlencode_unsafechars( filename, fullurl + url_len + 1, alloc_size - url_len - 1 );
		}

		headers[0] = "Referer";
		headers[1] = referer;

		CL_AddSessionHttpRequestHeaders( fullurl, &headers[2] );

		CL_AsyncStreamRequest( fullurl, headers, cl_downloads_from_web_timeout->integer / 100, cls.download.offset, 
			CL_WebDownloadReadCb, CL_WebDownloadDoneCb, NULL, NULL, qfalse );

		Mem_ZoneFree( fullurl );
		Mem_ZoneFree( referer );
		return;
	}

	// have to use Sys_Milliseconds because cls.realtime might be old from Web_Get
	cls.download.timeout = Sys_Milliseconds() + 3000;
	cls.download.retries = 0;

	CL_AddReliableCommand( va( "nextdl \"%s\" %i", cls.download.name, cls.download.offset ) );
}
コード例 #7
0
ファイル: sv_client.c プロジェクト: MGXRace/racesow
/*
* SV_NextDownload_f
* 
* Responds to reliable nextdl packet with unreliable download packet
* If nextdl packet's offet information is negative, download will be stopped
*/
static void SV_NextDownload_f( client_t *client )
{
	int blocksize;
	int offset;
	uint8_t data[FRAGMENT_SIZE*2];

	if( !client->download.name )
	{
		Com_Printf( "nextdl message for client with no download active, from: %s\n", client->name );
		return;
	}

	if( Q_stricmp( client->download.name, Cmd_Argv( 1 ) ) )
	{
		Com_Printf( "nextdl message for wrong filename, from: %s\n", client->name );
		return;
	}

	offset = atoi( Cmd_Argv( 2 ) );

	if( offset > client->download.size )
	{
		Com_Printf( "nextdl message with too big offset, from: %s\n", client->name );
		return;
	}

	if( offset == -1 )
	{
		Com_Printf( "Upload of %s to %s%s completed\n", client->download.name, client->name, S_COLOR_WHITE );
		SV_ClientCloseDownload( client );
		return;
	}

	if( offset < 0 )
	{
		Com_Printf( "Upload of %s to %s%s failed\n", client->download.name, client->name, S_COLOR_WHITE );
		SV_ClientCloseDownload( client );
		return;
	}

	if( !client->download.file )
	{
		Com_Printf( "Starting server upload of %s to %s\n", client->download.name, client->name );

		client->download.size = FS_FOpenBaseFile( client->download.name, &client->download.file, FS_READ );
		if( !client->download.file || client->download.size < 0 )
		{
			Com_Printf( "Error opening %s for uploading\n", client->download.name );
			SV_ClientCloseDownload( client );
			return;
		}
	}

	SV_InitClientMessage( client, &tmpMessage, NULL, 0 );
	SV_AddReliableCommandsToMessage( client, &tmpMessage );

	blocksize = client->download.size - offset;
	// jalfixme: adapt download to user rate setting and sv_maxrate setting.
	if( blocksize > sizeof( data ) )
		blocksize = sizeof( data );
	if( offset + blocksize > client->download.size )
		blocksize = client->download.size - offset;
	if( blocksize < 0 )
		blocksize = 0;

	if( blocksize > 0 )
	{
		FS_Seek( client->download.file, offset, FS_SEEK_SET );
		blocksize = FS_Read( data, blocksize, client->download.file );
	}

	MSG_WriteByte( &tmpMessage, svc_download );
	MSG_WriteString( &tmpMessage, client->download.name );
	MSG_WriteLong( &tmpMessage, offset );
	MSG_WriteLong( &tmpMessage, blocksize );
	if( blocksize > 0 )
		MSG_CopyData( &tmpMessage, data, blocksize );
	SV_SendMessageToClient( client, &tmpMessage );

	client->download.timeout = svs.realtime + 10000;
}