static void G_Client_AssignTeamSkin( edict_t *ent, char *userinfo ) { char skin[MAX_QPATH], model[MAX_QPATH]; const char *userskin, *usermodel; // index skin file userskin = GS_TeamSkinName( ent->s.team ); // is it a team skin? if( !userskin ) // NULL indicates *user defined* { userskin = Info_ValueForKey( userinfo, "skin" ); if( !userskin || !userskin[0] || !COM_ValidateRelativeFilename( userskin ) || strchr( userskin, '/' ) || strstr( userskin, "invisibility" ) ) userskin = NULL; } // index player model usermodel = Info_ValueForKey( userinfo, "model" ); if( !usermodel || !usermodel[0] || !COM_ValidateRelativeFilename( usermodel ) || strchr( usermodel, '/' ) ) usermodel = NULL; if( userskin && usermodel ) { Q_snprintfz( model, sizeof( model ), "$models/players/%s", usermodel ); Q_snprintfz( skin, sizeof( skin ), "models/players/%s/%s", usermodel, userskin ); } else { Q_snprintfz( model, sizeof( model ), "$models/players/%s", DEFAULT_PLAYERMODEL ); Q_snprintfz( skin, sizeof( skin ), "models/players/%s/%s", DEFAULT_PLAYERMODEL, DEFAULT_PLAYERSKIN ); } if( !ent->deadflag ) ent->s.modelindex = trap_ModelIndex( model ); ent->s.skinnum = trap_SkinIndex( skin ); }
/* * TV_ReleaseModule */ void TV_ReleaseModule( const char *game ) { tv_module_t *iter; assert( game && strlen( game ) < MAX_CONFIGSTRING_CHARS ); assert( COM_ValidateRelativeFilename( game ) && !strchr( game, '/' ) ); // see if it's already loaded iter = modules; if( !iter ) Com_Error( ERR_FATAL, "Attempting to release non-existing module" ); assert( iter->count > 0 ); iter->count--; if( !iter->count ) { iter->export->Shutdown(); #ifndef TV_MODULE_HARD_LINKED Com_UnloadGameLibrary( &iter->handle ); #endif Mem_Free( iter ); modules = NULL; }
/* * 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; }
/* * ML_ValidateFilename * Checks that the filename provided is valid */ bool ML_ValidateFilename( const char *filename ) { const char *extension; if( !filename || !*filename ) return false; extension = COM_FileExtension( filename ); if( !extension ) { if( strlen( "maps/" ) + strlen( filename ) + strlen( ".bsp" ) >= MAX_CONFIGSTRING_CHARS ) return false; } else { if( Q_stricmp( extension, ".bsp" ) ) return false; if( strlen( "maps/" ) + strlen( filename ) >= MAX_CONFIGSTRING_CHARS ) return false; } if( !COM_ValidateRelativeFilename( filename ) || strchr( filename, '/' ) ) return false; return true; }
/* * CG_SC_DemoGet */ static void CG_SC_DemoGet( void ) { if( cgs.demoPlaying ) { // ignore download commands coming from demo files return; } if( !demo_requested ) { CG_Printf( "Warning: demoget when not requested, ignored\n" ); return; } demo_requested = qfalse; if( trap_Cmd_Argc() < 2 ) { CG_Printf( "No such demo found\n" ); return; } if( !COM_ValidateRelativeFilename( trap_Cmd_Argv( 1 ) ) ) { CG_Printf( "Warning: demoget: Invalid filename, ignored\n" ); return; } trap_DownloadRequest( trap_Cmd_Argv( 1 ), qfalse ); }
/* * Con_Dump_f * * Save the console contents out to a file */ static void Con_Dump_f( void ) { int file; size_t buffer_size; char *buffer; size_t name_size; char *name; const char *newline = "\r\n"; if( !con_initialized ) return; if( Cmd_Argc() != 2 ) { Com_Printf( "usage: condump <filename>\n" ); return; } name_size = sizeof( char ) * ( strlen( Cmd_Argv( 1 ) ) + strlen( ".txt" ) + 1 ); name = Mem_TempMalloc( name_size ); Q_strncpyz( name, Cmd_Argv( 1 ), name_size ); COM_DefaultExtension( name, ".txt", name_size ); COM_SanitizeFilePath( name ); if( !COM_ValidateRelativeFilename( name ) ) { Com_Printf( "Invalid filename.\n" ); Mem_TempFree( name ); return; } if( FS_FOpenFile( name, &file, FS_WRITE ) == -1 ) { Com_Printf( "Couldn't open: %s\n", name ); Mem_TempFree( name ); return; } buffer_size = Con_BufferText( NULL, newline ) + 1; buffer = Mem_TempMalloc( buffer_size ); Con_BufferText( buffer, newline ); FS_Write( buffer, buffer_size - 1, file ); FS_FCloseFile( file ); Mem_TempFree( buffer ); Com_Printf( "Dumped console text: %s\n", name ); Mem_TempFree( name ); }
/* * 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; }
/* * R_ScreenShot_f */ void R_ScreenShot_f( void ) { int i; const char *name; const char *mediadir; size_t path_size; char *path; char timestamp_str[MAX_QPATH]; struct tm newtime; R_Localtime( time( NULL ), &newtime ); name = ri.Cmd_Argv( 1 ); mediadir = ri.FS_MediaDirectory( FS_MEDIA_IMAGES ); if( mediadir ) { path_size = strlen( mediadir ) + 1 /* '/' */ + strlen( glConfig.applicationName ) + 1 /* '/' */ + 1; path = alloca( path_size ); Q_snprintfz( path, path_size, "%s/%s/", mediadir, glConfig.applicationName ); } else { path_size = strlen( ri.FS_WriteDirectory() ) + 1 /* '/' */ + strlen( ri.FS_GameDirectory() ) + strlen( "/screenshots/" ) + 1; path = alloca( path_size ); Q_snprintfz( path, path_size, "%s/%s/screenshots/", ri.FS_WriteDirectory(), ri.FS_GameDirectory() ); } // validate timestamp string for( i = 0; i < 2; i++ ) { strftime( timestamp_str, sizeof( timestamp_str ), r_screenshot_fmtstr->string, &newtime ); if( !COM_ValidateRelativeFilename( timestamp_str ) ) { ri.Cvar_ForceSet( r_screenshot_fmtstr->name, r_screenshot_fmtstr->dvalue ); } else { break; } } // hm... shouldn't really happen, but check anyway if( i == 2 ) { ri.Cvar_ForceSet( r_screenshot_fmtstr->name, glConfig.screenshotPrefix ); } RF_ScreenShot( path, name, r_screenshot_fmtstr->string, ri.Cmd_Argc() >= 3 && !Q_stricmp( ri.Cmd_Argv( 2 ), "silent" ) ? true : false ); }
/* * TV_Upstream_StartDemo */ void TV_Upstream_StartDemo( upstream_t *upstream, const char *demoname, qboolean randomize ) { char *name, *filepath; int tempdemofilehandle, tempdemofilelen; name = filepath = NULL; tempdemofilehandle = 0; tempdemofilelen = -1; TV_Upstream_NextDemo( demoname, upstream->demo.filename, randomize, &name, &filepath ); if( filepath ) { if( COM_ValidateRelativeFilename( filepath ) ) tempdemofilelen = FS_FOpenFile( filepath, &tempdemofilehandle, FS_READ|SNAP_DEMO_GZ ); } TV_Upstream_StopDemo( upstream ); if( name ) Com_Printf( "Starting demo from %s\n", filepath ); upstream->demo.playing = qtrue; upstream->demo.filename = name ? TV_Upstream_CopyString( upstream, name ) : NULL; upstream->demo.filehandle = tempdemofilehandle; upstream->demo.filelen = tempdemofilelen; upstream->demo.random = randomize; upstream->state = CA_HANDSHAKE; upstream->reliable = qfalse; upstream->servername = TV_Upstream_CopyString( upstream, demoname ); // can be demo filename/pattern or demolist filename upstream->rejected = qfalse; upstream->lastPacketReceivedTime = tvs.realtime; // reset the timeout limit upstream->multiview = qfalse; upstream->precacheDone = qfalse; if( name ) Mem_TempFree( name ); if( filepath ) Mem_TempFree( filepath ); }
/* * ML_ValidateFilename * Checks that the filename provided is valid */ qboolean ML_ValidateFilename( const char *filename ) { if( !filename || !*filename ) return qfalse; if( !COM_FileExtension( filename ) ) { if( strlen( "maps/" ) + strlen( filename ) + strlen( ".bsp" ) >= MAX_CONFIGSTRING_CHARS ) return qfalse; } else { if( Q_stricmp( COM_FileExtension( filename ), ".bsp" ) ) return qfalse; if( strlen( "maps/" ) + strlen( filename ) >= MAX_CONFIGSTRING_CHARS ) return qfalse; } if( !COM_ValidateRelativeFilename( filename ) || strchr( filename, '/' ) ) return qfalse; return qtrue; }
/* * TV_Upstream_StartDemoRecord */ void TV_Upstream_StartDemoRecord( upstream_t *upstream, const char *demoname, qboolean silent ) { char *servername, *temp; size_t name_size; assert( upstream ); assert( demoname ); if( upstream->demo.playing ) { if( !silent ) Com_Printf( "You can't record from another demo.\n" ); return; } if( upstream->demo.recording ) { if( !silent ) Com_Printf( "Already recording.\n" ); return; } // strip the port number from servername servername = TempCopyString( upstream->servername ); temp = strstr( servername, ":" ); if( temp ) *temp = '\0'; // store the name name_size = sizeof( char ) * ( strlen( "demos/tvserver" ) + 1 + strlen( servername ) + 1 + strlen( demoname ) + strlen( APP_DEMO_EXTENSION_STR ) + 1 ); upstream->demo.filename = Mem_ZoneMalloc( name_size ); Q_snprintfz( upstream->demo.filename, name_size, "demos/tvserver/%s/%s", servername, demoname ); COM_SanitizeFilePath( upstream->demo.filename ); COM_DefaultExtension( upstream->demo.filename, APP_DEMO_EXTENSION_STR, name_size ); Mem_TempFree( servername ); if( !COM_ValidateRelativeFilename( upstream->demo.filename ) ) { if( !silent ) Com_Printf( "Invalid filename.\n" ); Mem_ZoneFree( upstream->demo.filename ); return; } // temp name name_size = sizeof( char ) * ( strlen( upstream->demo.filename ) + strlen( ".rec" ) + 1 ); upstream->demo.tempname = Mem_ZoneMalloc( name_size ); Q_snprintfz( upstream->demo.tempname, name_size, "%s.rec", upstream->demo.filename ); // open the demo file if( FS_FOpenFile( upstream->demo.tempname, &upstream->demo.filehandle, FS_WRITE|SNAP_DEMO_GZ ) == -1 ) { Com_Printf( "Error: Couldn't create the demo file.\n" ); Mem_ZoneFree( upstream->demo.tempname ); upstream->demo.tempname = NULL; Mem_ZoneFree( upstream->demo.filename ); upstream->demo.filename = NULL; return; } if( !silent ) Com_Printf( "Recording demo: %s\n", upstream->demo.filename ); upstream->demo.recording = qtrue; upstream->demo.localtime = 0; upstream->demo.basetime = upstream->demo.duration = 0; // don't start saving messages until a non-delta compressed message is received TV_Upstream_AddReliableCommand( upstream, "nodelta" ); // request non delta compressed frame from server upstream->demo.waiting = qtrue; // the rest of the demo file will be individual frames }
/* * R_ScreenShot_f */ void R_ScreenShot_f( void ) { const char *name; const char *extension; const char *mediadir; size_t path_size; char *path; char *checkname = NULL; size_t checkname_size = 0; int quality; if( !R_ScreenEnabled() ) return; name = ri.Cmd_Argv( 1 ); if( r_screenshot_jpeg->integer ) { extension = ".jpg"; quality = r_screenshot_jpeg_quality->integer; } else { extension = ".tga"; quality = 100; } mediadir = ri.FS_MediaDirectory( FS_MEDIA_IMAGES ); if( mediadir ) { path_size = strlen( mediadir ) + 1 + strlen( glConfig.applicationName ) + sizeof( " Screenshots/" ); path = alloca( path_size ); Q_snprintfz( path, path_size, "%s/%s Screenshots/", mediadir, glConfig.applicationName ); } else { path_size = strlen( ri.FS_WriteDirectory() ) + 1 + strlen( ri.FS_GameDirectory() ) + sizeof( "/screenshots/" ); path = alloca( path_size ); Q_snprintfz( path, path_size, "%s/%s/screenshots/", ri.FS_WriteDirectory(), ri.FS_GameDirectory() ); } if( name && name[0] && Q_stricmp(name, "*") ) { if( !COM_ValidateRelativeFilename( name ) ) { Com_Printf( "Invalid filename\n" ); return; } checkname_size = ( path_size - 1 ) + strlen( name ) + strlen( extension ) + 1; checkname = alloca( checkname_size ); Q_snprintfz( checkname, checkname_size, "%s%s", path, name ); COM_DefaultExtension( checkname, extension, checkname_size ); } // // find a file name to save it to // if( !checkname ) { int i; const int maxFiles = 100000; static int lastIndex = 0; bool addIndex = false; time_t timestamp; char timestamp_str[MAX_QPATH]; struct tm *timestampptr; timestamp = time( NULL ); timestampptr = localtime( ×tamp ); // validate timestamp string for( i = 0; i < 2; i++ ) { strftime( timestamp_str, sizeof( timestamp_str ), r_screenshot_fmtstr->string, timestampptr ); if( !COM_ValidateRelativeFilename( timestamp_str ) ) ri.Cvar_ForceSet( r_screenshot_fmtstr->name, r_screenshot_fmtstr->dvalue ); else break; } // hm... shouldn't really happen, but check anyway if( i == 2 ) { Q_strncpyz( timestamp_str, glConfig.screenshotPrefix, sizeof( timestamp_str ) ); ri.Cvar_ForceSet( r_screenshot_fmtstr->name, glConfig.screenshotPrefix ); } checkname_size = ( path_size - 1 ) + strlen( timestamp_str ) + 5 + 1 + strlen( extension ); checkname = alloca( checkname_size ); // if the string format is a constant or file already exists then iterate if( !*timestamp_str || !strcmp( timestamp_str, r_screenshot_fmtstr->string ) ) { addIndex = true; // force a rescan in case some vars have changed.. if( r_screenshot_fmtstr->modified ) { lastIndex = 0; r_screenshot_fmtstr->modified = true; } if( r_screenshot_jpeg->modified ) { lastIndex = 0; r_screenshot_jpeg->modified = false; } } else { Q_snprintfz( checkname, checkname_size, "%s%s%s", path, timestamp_str, extension ); if( ri.FS_FOpenAbsoluteFile( checkname, NULL, FS_READ ) != -1 ) { lastIndex = 0; addIndex = true; } } for( ; addIndex && lastIndex < maxFiles; lastIndex++ ) { Q_snprintfz( checkname, checkname_size, "%s%s%05i%s", path, timestamp_str, lastIndex, extension ); if( ri.FS_FOpenAbsoluteFile( checkname, NULL, FS_READ ) == -1 ) break; // file doesn't exist } if( lastIndex == maxFiles ) { Com_Printf( "Couldn't create a file\n" ); return; } lastIndex++; } R_ScreenShot( checkname, 0, 0, glConfig.width, glConfig.height, quality, false, false, false, ri.Cmd_Argc() >= 3 && !Q_stricmp( ri.Cmd_Argv( 2 ), "silent" ) ? true : false ); ri.FS_AddFileToMedia( checkname ); }
/* * 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_ParseServerData */ static void CL_ParseServerData( msg_t *msg ) { const char *str, *gamedir; int i, sv_bitflags, numpure; int http_portnum; Com_DPrintf( "Serverdata packet received.\n" ); // wipe the client_state_t struct CL_ClearState(); CL_SetClientState( CA_CONNECTED ); // parse protocol version number i = MSG_ReadLong( msg ); if( i != APP_PROTOCOL_VERSION ) Com_Error( ERR_DROP, "Server returned version %i, not %i", i, APP_PROTOCOL_VERSION ); cl.servercount = MSG_ReadLong( msg ); cl.snapFrameTime = (unsigned int)MSG_ReadShort( msg ); // set extrapolation time to half snapshot time Cvar_ForceSet( "cl_extrapolationTime", va( "%i", (unsigned int)( cl.snapFrameTime * 0.5 ) ) ); cl_extrapolationTime->modified = qfalse; // base game directory str = MSG_ReadString( msg ); if( !str || !str[0] ) Com_Error( ERR_DROP, "Server sent an empty base game directory" ); if( !COM_ValidateRelativeFilename( str ) || strchr( str, '/' ) ) Com_Error( ERR_DROP, "Server sent an invalid base game directory: %s", str ); if( strcmp( FS_BaseGameDirectory(), str ) ) { Com_Error( ERR_DROP, "Server has different base game directory (%s) than the client (%s)", str, FS_BaseGameDirectory() ); } // game directory str = MSG_ReadString( msg ); if( !str || !str[0] ) Com_Error( ERR_DROP, "Server sent an empty game directory" ); if( !COM_ValidateRelativeFilename( str ) || strchr( str, '/' ) ) Com_Error( ERR_DROP, "Server sent an invalid game directory: %s", str ); gamedir = FS_GameDirectory(); if( strcmp( str, gamedir ) ) { // shutdown the cgame module first in case it is running for whatever reason // (happens on wswtv in lobby), otherwise precaches that are going to follow // will probably f**k up (like models trying to load before the world model) CL_GameModule_Shutdown(); if( !FS_SetGameDirectory( str, qtrue ) ) Com_Error( ERR_DROP, "Failed to load game directory set by server: %s", str ); ML_Restart( qtrue ); } // parse player entity number cl.playernum = MSG_ReadShort( msg ); // get the full level name Q_strncpyz( cl.servermessage, MSG_ReadString( msg ), sizeof( cl.servermessage ) ); sv_bitflags = MSG_ReadByte( msg ); if( cls.demo.playing ) { cls.reliable = ( sv_bitflags & SV_BITFLAGS_RELIABLE ); } else { if( cls.reliable != ( ( sv_bitflags & SV_BITFLAGS_RELIABLE ) != 0 ) ) Com_Error( ERR_DROP, "Server and client disagree about connection reliability" ); } // builting HTTP server port if( cls.httpbaseurl ) { Mem_Free( cls.httpbaseurl ); cls.httpbaseurl = NULL; } if( ( sv_bitflags & SV_BITFLAGS_HTTP ) != 0 ) { if( ( sv_bitflags & SV_BITFLAGS_HTTP_BASEURL ) != 0 ) { // read base upstream url cls.httpbaseurl = ZoneCopyString( MSG_ReadString( msg ) ); } else { http_portnum = MSG_ReadShort( msg ) & 0xffff; cls.httpaddress = cls.serveraddress; if( cls.httpaddress.type == NA_IP6 ) { cls.httpaddress.address.ipv6.port = BigShort( http_portnum ); } else { cls.httpaddress.address.ipv4.port = BigShort( http_portnum ); } if( http_portnum ) { if( cls.httpaddress.type == NA_LOOPBACK ) { cls.httpbaseurl = ZoneCopyString( va( "http://localhost:%hu/", http_portnum ) ); } else { cls.httpbaseurl = ZoneCopyString( va( "http://%s/", NET_AddressToString( &cls.httpaddress ) ) ); } } } } // pure list // clean old, if necessary Com_FreePureList( &cls.purelist ); // add new numpure = MSG_ReadShort( msg ); while( numpure > 0 ) { const char *pakname = MSG_ReadString( msg ); const unsigned checksum = MSG_ReadLong( msg ); Com_AddPakToPureList( &cls.purelist, pakname, checksum, NULL ); numpure--; } //assert( numpure == 0 ); // get the configstrings request CL_AddReliableCommand( va( "configstrings %i 0", cl.servercount ) ); cls.sv_pure = ( sv_bitflags & SV_BITFLAGS_PURE ) != 0; cls.sv_tv = ( sv_bitflags & SV_BITFLAGS_TVSERVER ) != 0; #ifdef PURE_CHEAT cls.sv_pure = qfalse; #endif // separate the printfs so the server message can have a color Com_Printf( S_COLOR_WHITE "\n" "=====================================\n" ); Com_Printf( S_COLOR_WHITE "%s\n\n", cl.servermessage ); }
/* * 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; }
/* * 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; }
/* * R_TakeScreenShot */ void R_TakeScreenShot( const char *path, const char *name, const char *fmtString, int x, int y, int w, int h, bool silent, bool media ) { const char *extension; size_t path_size = strlen( path ) + 1; char *checkname = NULL; size_t checkname_size = 0; int quality; if( !R_IsRenderingToScreen() ) { return; } if( r_screenshot_jpeg->integer ) { extension = ".jpg"; quality = r_screenshot_jpeg_quality->integer; } else { extension = ".tga"; quality = 100; } if( name && name[0] && Q_stricmp( name, "*" ) ) { if( !COM_ValidateRelativeFilename( name ) ) { Com_Printf( "Invalid filename\n" ); return; } checkname_size = ( path_size - 1 ) + strlen( name ) + strlen( extension ) + 1; checkname = alloca( checkname_size ); Q_snprintfz( checkname, checkname_size, "%s%s", path, name ); COM_DefaultExtension( checkname, extension, checkname_size ); } // // find a file name to save it to // if( !checkname ) { const int maxFiles = 100000; static int lastIndex = 0; bool addIndex = false; char timestampString[MAX_QPATH]; static char lastFmtString[MAX_QPATH]; struct tm newtime; R_Localtime( time( NULL ), &newtime ); strftime( timestampString, sizeof( timestampString ), fmtString, &newtime ); checkname_size = ( path_size - 1 ) + strlen( timestampString ) + 5 + 1 + strlen( extension ); checkname = alloca( checkname_size ); // if the string format is a constant or file already exists then iterate if( !*fmtString || !strcmp( timestampString, fmtString ) ) { addIndex = true; // force a rescan in case some vars have changed.. if( strcmp( lastFmtString, fmtString ) ) { lastIndex = 0; Q_strncpyz( lastFmtString, fmtString, sizeof( lastFmtString ) ); r_screenshot_fmtstr->modified = false; } if( r_screenshot_jpeg->modified ) { lastIndex = 0; r_screenshot_jpeg->modified = false; } } else { Q_snprintfz( checkname, checkname_size, "%s%s%s", path, timestampString, extension ); if( ri.FS_FOpenAbsoluteFile( checkname, NULL, FS_READ ) != -1 ) { lastIndex = 0; addIndex = true; } } for( ; addIndex && lastIndex < maxFiles; lastIndex++ ) { Q_snprintfz( checkname, checkname_size, "%s%s%05i%s", path, timestampString, lastIndex, extension ); if( ri.FS_FOpenAbsoluteFile( checkname, NULL, FS_READ ) == -1 ) { break; // file doesn't exist } } if( lastIndex == maxFiles ) { Com_Printf( "Couldn't create a file\n" ); return; } lastIndex++; } R_ScreenShot( checkname, x, y, w, h, quality, false, false, false, silent ); if( media ) { ri.FS_AddFileToMedia( checkname ); } }
/* * SV_Demo_Start_f * * Begins server demo recording. */ void SV_Demo_Start_f( void ) { int demofilename_size, i; if( Cmd_Argc() < 2 ) { Com_Printf( "Usage: serverrecord <demoname>\n" ); return; } if( svs.demo.file ) { Com_Printf( "Already recording\n" ); return; } if( sv.state != ss_game ) { Com_Printf( "Must be in a level to record\n" ); return; } for( i = 0; i < sv_maxclients->integer; i++ ) { if( svs.clients[i].state >= CS_SPAWNED && svs.clients[i].edict && !( svs.clients[i].edict->r.svflags & SVF_NOCLIENT ) ) break; } if( i == sv_maxclients->integer ) { Com_Printf( "No players in game, can't record a demo\n" ); return; } // // open the demo file // // real name demofilename_size = sizeof( char ) * ( strlen( SV_DEMO_DIR ) + 1 + strlen( Cmd_Args() ) + strlen( APP_DEMO_EXTENSION_STR ) + 1 ); svs.demo.filename = Mem_ZoneMalloc( demofilename_size ); Q_snprintfz( svs.demo.filename, demofilename_size, "%s/%s", SV_DEMO_DIR, Cmd_Args() ); COM_SanitizeFilePath( svs.demo.filename ); if( !COM_ValidateRelativeFilename( svs.demo.filename ) ) { Mem_ZoneFree( svs.demo.filename ); svs.demo.filename = NULL; Com_Printf( "Invalid filename.\n" ); return; } COM_DefaultExtension( svs.demo.filename, APP_DEMO_EXTENSION_STR, demofilename_size ); // temp name demofilename_size = sizeof( char ) * ( strlen( svs.demo.filename ) + strlen( ".rec" ) + 1 ); svs.demo.tempname = Mem_ZoneMalloc( demofilename_size ); Q_snprintfz( svs.demo.tempname, demofilename_size, "%s.rec", svs.demo.filename ); // open it if( FS_FOpenFile( svs.demo.tempname, &svs.demo.file, FS_WRITE|SNAP_DEMO_GZ ) == -1 ) { Com_Printf( "Error: Couldn't open file: %s\n", svs.demo.tempname ); Mem_ZoneFree( svs.demo.filename ); svs.demo.filename = NULL; Mem_ZoneFree( svs.demo.tempname ); svs.demo.tempname = NULL; return; } Com_Printf( "Recording server demo: %s\n", svs.demo.filename ); SV_Demo_InitClient(); // write serverdata, configstrings and baselines svs.demo.duration = 0; svs.demo.basetime = svs.gametime; svs.demo.localtime = time( NULL ); SV_Demo_WriteStartMessages(); // write one nodelta frame svs.demo.client.nodelta = true; SV_Demo_WriteSnap(); svs.demo.client.nodelta = false; }
/* * 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 ) ); } } }
/* * 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 ) ); }