/* * CL_DownloadComplete * * Checks downloaded file's checksum, renames it and adds to the filesystem. */ static void CL_DownloadComplete( void ) { unsigned checksum = 0; int length; qboolean updateMapList = qfalse; FS_FCloseFile( cls.download.filenum ); cls.download.filenum = 0; // verify checksum if( FS_CheckPakExtension( cls.download.name ) ) { if( !FS_IsPakValid( cls.download.tempname, &checksum ) ) { Com_Printf( "Downloaded file is not a valid pack file. Removing\n" ); FS_RemoveBaseFile( cls.download.tempname ); return; } } else { length = FS_LoadBaseFile( cls.download.tempname, NULL, NULL, 0 ); if( length < 0 ) { Com_Printf( "Error: Couldn't load downloaded file\n" ); return; } checksum = FS_ChecksumBaseFile( cls.download.tempname ); } if( cls.download.checksum != checksum ) { Com_Printf( "Downloaded file has wrong checksum. Removing: %u %u %s\n", cls.download.checksum, checksum, cls.download.tempname ); FS_RemoveBaseFile( cls.download.tempname ); return; } if( !FS_MoveBaseFile( cls.download.tempname, cls.download.name ) ) { Com_Printf( "Failed to rename the downloaded file\n" ); return; } if( FS_CheckPakExtension( cls.download.name ) ) updateMapList = qtrue; else if( !Q_stricmp( COM_FileExtension( cls.download.name ), ".bsp" ) ) updateMapList = qtrue; // Maplist hook so we also know when a new map is added if( updateMapList ) ML_Update (); cls.download.successCount++; cls.download.timeout = 0; }
/* * SV_Download_f * Download command issued from server */ static void SV_Download_f( void ) { qboolean success; char *s; char url[MAX_STRING_CHARS], filepath[MAX_QPATH], writepath[MAX_QPATH]; if( Cmd_Argc() != 2 ) { Com_Printf( "Usage: %s <url>\n", Cmd_Argv( 0 ) ); Com_Printf( "Downloads .pk3 or .pak from URL to gamedir and adds it to the server\n" ); Com_Printf( "Note, server will not function properly while downloading\n" ); return; } s = Cmd_Argv( 1 ); if( !Com_GlobMatch( "*://*", s, qfalse ) ) Q_strncpyz( url, "http://", sizeof( url ) ); else url[0] = 0; Q_strncatz( url, s, sizeof( url ) ); s = strrchr( url, '/' ); if( !s ) { Com_Printf( "%s: invalid URL\n", Cmd_Argv( 0 ) ); return; } Q_strncpyz( filepath, va( "%s/%s", FS_GameDirectory(), s + 1 ), sizeof( filepath ) ); Q_strncpyz( writepath, va( "%s.tmp", filepath ), sizeof( writepath ) ); if( !FS_CheckPakExtension( writepath ) ) { Com_Printf( "Missing or invalid archive extension. Only download of pack files is supported\n" ); return; } Com_Printf( "download url: %s\n", url ); webDownloadPercentPrint = 0; success = Web_Get( url, NULL, writepath, qtrue, 60 * 30, 60, SV_WebDownloadProgress, qfalse ); if( !success ) { Com_Printf( "Server web download failed\n" ); return; } if( !FS_MoveBaseFile( writepath, filepath ) ) { Com_Printf( "Couldn't rename the downloaded file. Download failed\n" ); return; } Com_Printf( "Download successful\n" ); // update the map list, which also does a filesystem rescan ML_Update(); }
/* * 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; }
/* * 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; }
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; }
/* * 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 ) ); } } }
/* * SV_BeginDownload_f * Responds to reliable download packet with reliable initdownload packet */ static void SV_BeginDownload_f( client_t *client ) { const char *requestname; const char *uploadname; size_t alloc_size; unsigned checksum; char *url; const char *errormsg = NULL; qboolean allow, requestpak; qboolean local_http = SV_Web_Running() && sv_uploads_http->integer != 0; requestpak = ( atoi( Cmd_Argv( 1 ) ) == 1 ); requestname = Cmd_Argv( 2 ); if( !requestname[0] || !COM_ValidateRelativeFilename( requestname ) ) { SV_DenyDownload( client, "Invalid filename" ); return; } if( !SV_FilenameForDownloadRequest( requestname, requestpak, &uploadname, &errormsg ) ) { assert( errormsg != NULL ); SV_DenyDownload( client, errormsg ); return; } if( FS_CheckPakExtension( uploadname ) ) { allow = qfalse; // allow downloading paks from the pure list, if not spawned if( client->state < CS_SPAWNED ) { purelist_t *purefile; purefile = svs.purelist; while( purefile ) { if( !strcmp( uploadname, purefile->filename ) ) { allow = qtrue; break; } purefile = purefile->next; } } // game module has a change to allow extra downloads if( !allow && !SV_GameAllowDownload( client, requestname, uploadname ) ) { SV_DenyDownload( client, "Downloading of this file is not allowed" ); return; } } else { if( !SV_GameAllowDownload( client, requestname, uploadname ) ) { SV_DenyDownload( client, "Downloading of this file is not allowed" ); return; } } // we will just overwrite old download, if any if( client->download.name ) { if( client->download.data ) { FS_FreeBaseFile( client->download.data ); client->download.data = NULL; } Mem_ZoneFree( client->download.name ); client->download.name = NULL; client->download.size = 0; client->download.timeout = 0; } client->download.size = FS_LoadBaseFile( uploadname, NULL, NULL, 0 ); if( client->download.size == -1 ) { Com_Printf( "Error getting size of %s for uploading\n", uploadname ); client->download.size = 0; SV_DenyDownload( client, "Error getting file size" ); return; } checksum = FS_ChecksumBaseFile( uploadname ); client->download.timeout = svs.realtime + 1000 * 60 * 60; // this is web download timeout alloc_size = sizeof( char ) * ( strlen( uploadname ) + 1 ); client->download.name = Mem_ZoneMalloc( alloc_size ); Q_strncpyz( client->download.name, uploadname, alloc_size ); Com_Printf( "Offering %s to %s\n", client->download.name, client->name ); if( FS_CheckPakExtension( uploadname ) && ( local_http || sv_uploads_baseurl->string[0] != 0 ) ) { // .pk3 and .pak download from the web if( local_http ) { url = TempCopyString( va( "files/%s", uploadname ) ); } else { alloc_size = sizeof( char ) * ( strlen( sv_uploads_baseurl->string ) + 1 ); url = Mem_TempMalloc( alloc_size ); Q_snprintfz( url, alloc_size, "%s/", sv_uploads_baseurl->string ); } } else if( SV_IsDemoDownloadRequest( requestname ) && ( local_http || sv_uploads_demos_baseurl->string[0] != 0 ) ) { // demo file download from the web if( local_http ) { url = TempCopyString( va( "files/%s", uploadname ) ); } else { alloc_size = sizeof( char ) * ( strlen( sv_uploads_demos_baseurl->string ) + 1 ); url = Mem_TempMalloc( alloc_size ); Q_snprintfz( url, alloc_size, "%s/", sv_uploads_demos_baseurl->string ); } } else { url = NULL; } // start the download SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); SV_SendServerCommand( client, "initdownload \"%s\" %i %u %i \"%s\"", client->download.name, client->download.size, checksum, local_http ? 1 : 0, ( url ? url : "" ) ); SV_AddReliableCommandsToMessage( client, &tmpMessage ); SV_SendMessageToClient( client, &tmpMessage ); if( url ) { Mem_TempFree( url ); url = NULL; } }
/* * 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 ) ); }
/* * CL_DownloadRequest * * Request file download * return false 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 */ bool CL_DownloadRequest( const char *filename, bool requestpak ) { if( cls.download.requestname ) { Com_Printf( "Can't download: %s. Download already in progress.\n", filename ); return false; } if( !COM_ValidateRelativeFilename( filename ) ) { Com_Printf( "Can't download: %s. Invalid filename.\n", filename ); return false; } if( FS_CheckPakExtension( filename ) ) { if( FS_PakFileExists( filename ) ) { Com_Printf( "Can't download: %s. File already exists.\n", filename ); return false; } if( !Q_strnicmp( COM_FileBase( filename ), "modules", strlen( "modules" ) ) ) { if( !CL_CanDownloadModules() ) return false; } } else { if( FS_FOpenFile( filename, NULL, FS_READ ) != -1 ) { Com_Printf( "Can't download: %s. File already exists.\n", filename ); return false; } if( !requestpak ) { const char *extension; // only allow demo downloads extension = COM_FileExtension( filename ); if( !extension || Q_stricmp( extension, APP_DEMO_EXTENSION_STR ) ) { Com_Printf( "Can't download, got arbitrary file type: %s\n", filename ); return false; } } } if( cls.socket->type == SOCKET_LOOPBACK ) { Com_DPrintf( "Can't download: %s. Loopback server.\n", filename ); return false; } 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 true; }