/* * 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(); }
/* * Sys_Library_GetGameLibPath */ const char *Sys_Library_GetGameLibPath( const char *name, int64_t time, int randomizer ) { // no randomizer because only one instance can run at once static char tempname[PATH_MAX]; Q_snprintfz( tempname, sizeof( tempname ), "/data/data/%s/cache/%d.%d/%s/tempmodules" #ifdef DEDICATED_ONLY "_server" #endif "/%s", sys_android_packageName, APP_VERSION_MAJOR, APP_VERSION_MINOR, FS_GameDirectory(), name ); return tempname; }
/* * CL_CanDownloadModules * * The user has to give permission for modules to be downloaded */ qboolean CL_CanDownloadModules( void ) { #if 0 if( !Q_stricmp( FS_GameDirectory(), FS_BaseGameDirectory() ) ) { Com_Error( ERR_DROP, "Can not download modules to the base directory" ); return qfalse; } #endif if( !cl_download_allow_modules->integer ) { Com_Error( ERR_DROP, "Downloading of modules disabled." ); return qfalse; } return qtrue; }
static char *SV_ShortInfoString( void ) { static char string[MAX_STRING_SVCINFOSTRING]; char hostname[64]; char entry[20]; size_t len; int i, count, bots; const char *password; bots = 0; count = 0; for( i = 0; i < sv_maxclients->integer; i++ ) { if( svs.clients[i].state >= CS_CONNECTED ) { if( svs.clients[i].edict->r.svflags & SVF_FAKECLIENT || svs.clients[i].tvclient ) bots++; count++; } } //format: //" \377\377\377\377info\\n\\server_name\\m\\map name\\u\\clients/maxclients\\g\\gametype\\s\\skill\\EOT " Q_strncpyz( hostname, sv_hostname->string, sizeof( hostname ) ); Q_snprintfz( string, sizeof( string ), "\\\\n\\\\%s\\\\m\\\\%8s\\\\u\\\\%2i/%2i\\\\", hostname, sv.mapname, count > 99 ? 99 : count, sv_maxclients->integer > 99 ? 99 : sv_maxclients->integer ); len = strlen( string ); Q_snprintfz( entry, sizeof( entry ), "g\\\\%6s\\\\", Cvar_String( "g_gametype" ) ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } if( Q_stricmp( FS_GameDirectory(), FS_BaseGameDirectory() ) ) { Q_snprintfz( entry, sizeof( entry ), "mo\\\\%8s\\\\", FS_GameDirectory() ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } } if( Cvar_Value( "g_instagib" ) ) { Q_snprintfz( entry, sizeof( entry ), "ig\\\\1\\\\" ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } } Q_snprintfz( entry, sizeof( entry ), "s\\\\%1d\\\\", sv_skilllevel->integer ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } password = Cvar_String( "password" ); if( password[0] != '\0' ) { Q_snprintfz( entry, sizeof( entry ), "p\\\\1\\\\" ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } } if( bots ) { Q_snprintfz( entry, sizeof( entry ), "b\\\\%2i\\\\", bots > 99 ? 99 : bots ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } } if( SV_MM_Initialized() ) { Q_snprintfz( entry, sizeof( entry ), "mm\\\\1\\\\" ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } } if( Cvar_Value( "g_race_gametype" ) ) { Q_snprintfz( entry, sizeof( entry ), "r\\\\1\\\\" ); if( MAX_SVCINFOSTRING_LEN - len > strlen( entry ) ) { Q_strncatz( string, entry, sizeof( string ) ); len = strlen( string ); } } // finish it Q_strncatz( string, "EOT", sizeof( string ) ); return string; }
/* * SV_New_f * * Sends the first message from the server to a connected client. * This will be sent on the initial connection and upon each server load. */ static void SV_New_f( client_t *client ) { int playernum; unsigned int numpure; purelist_t *purefile; edict_t *ent; int sv_bitflags = 0; Com_DPrintf( "New() from %s\n", client->name ); // if in CS_AWAITING we have sent the response packet the new once already, // but client might have not got it so we send it again if( client->state >= CS_SPAWNED ) { Com_Printf( "New not valid -- already spawned\n" ); return; } // // serverdata needs to go over for all types of servers // to make sure the protocol is right, and to set the gamedir // SV_InitClientMessage( client, &tmpMessage, NULL, 0 ); // send the serverdata MSG_WriteByte( &tmpMessage, svc_serverdata ); MSG_WriteLong( &tmpMessage, APP_PROTOCOL_VERSION ); MSG_WriteLong( &tmpMessage, svs.spawncount ); MSG_WriteShort( &tmpMessage, (unsigned short)svc.snapFrameTime ); MSG_WriteString( &tmpMessage, FS_BaseGameDirectory() ); MSG_WriteString( &tmpMessage, FS_GameDirectory() ); playernum = client - svs.clients; MSG_WriteShort( &tmpMessage, playernum ); // send full levelname MSG_WriteString( &tmpMessage, sv.mapname ); // // game server // if( sv.state == ss_game ) { // set up the entity for the client ent = EDICT_NUM( playernum+1 ); ent->s.number = playernum+1; client->edict = ent; if( sv_pure->integer ) sv_bitflags |= SV_BITFLAGS_PURE; if( client->reliable ) sv_bitflags |= SV_BITFLAGS_RELIABLE; if( SV_Web_Running() ) { const char *baseurl = SV_Web_UpstreamBaseUrl(); sv_bitflags |= SV_BITFLAGS_HTTP; if( baseurl[0] ) sv_bitflags |= SV_BITFLAGS_HTTP_BASEURL; } MSG_WriteByte( &tmpMessage, sv_bitflags ); } if( sv_bitflags & SV_BITFLAGS_HTTP ) { if( sv_bitflags & SV_BITFLAGS_HTTP_BASEURL ) MSG_WriteString( &tmpMessage, sv_http_upstream_baseurl->string ); else MSG_WriteShort( &tmpMessage, sv_http_port->integer ); // HTTP port number } // always write purelist numpure = Com_CountPureListFiles( svs.purelist ); if( numpure > (short)0x7fff ) Com_Error( ERR_DROP, "Error: Too many pure files." ); MSG_WriteShort( &tmpMessage, numpure ); purefile = svs.purelist; while( purefile ) { MSG_WriteString( &tmpMessage, purefile->filename ); MSG_WriteLong( &tmpMessage, purefile->checksum ); purefile = purefile->next; } SV_ClientResetCommandBuffers( client ); SV_SendMessageToClient( client, &tmpMessage ); Netchan_PushAllFragments( &client->netchan ); // don't let it send reliable commands until we get the first configstring request client->state = CS_CONNECTING; }
/** * Responds to a Steam server query. * * @param s query string * @param socket response socket * @param address response address * @param inmsg message for arguments * @return whether the request was handled as a Steam query */ bool SV_SteamServerQuery( const char *s, const socket_t *socket, const netadr_t *address, msg_t *inmsg ) { #if APP_STEAMID if( sv.state < ss_loading || sv.state > ss_game ) return false; // server not running if( ( !sv_public->integer && !NET_IsLANAddress( address ) ) || ( sv_maxclients->integer == 1 ) ) return false; if( !strcmp( s, "i" ) ) { // ping const char pingResponse[] = "j00000000000000"; Netchan_OutOfBand( socket, address, sizeof( pingResponse ), ( const uint8_t * )pingResponse ); return true; } if( !strcmp( s, "W" ) || !strcmp( s, "U\xFF\xFF\xFF\xFF" ) ) { // challenge - security feature, but since we don't send multiple packets always return 0 const uint8_t challengeResponse[] = { 'A', 0, 0, 0, 0 }; Netchan_OutOfBand( socket, address, sizeof( challengeResponse ), ( const uint8_t * )challengeResponse ); return true; } if( !strcmp( s, "TSource Engine Query" ) ) { // server info char hostname[MAX_INFO_VALUE]; char gamedir[MAX_QPATH]; char gamename[128]; char version[32]; char tags[MAX_STEAMQUERY_TAG_STRING]; int i, players = 0, bots = 0, maxclients = 0; int flags = 0x80 | 0x01; // game port | game ID containing app ID client_t *cl; msg_t msg; uint8_t msgbuf[MAX_STEAMQUERY_PACKETLEN - sizeof( int32_t )]; if( sv_showInfoQueries->integer ) Com_Printf( "Steam Info Packet %s\n", NET_AddressToString( address ) ); Q_strncpyz( hostname, COM_RemoveColorTokens( sv_hostname->string ), sizeof( hostname ) ); if( !hostname[0] ) Q_strncpyz( hostname, sv_hostname->dvalue, sizeof( hostname ) ); Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) ); Q_strncpyz( gamename, APPLICATION, sizeof( gamename ) ); if( Cvar_Value( "g_instagib" ) ) Q_strncatz( gamename, " IG", sizeof( gamename ) ); if( sv.configstrings[CS_GAMETYPETITLE][0] || sv.configstrings[CS_GAMETYPENAME][0] ) { Q_strncatz( gamename, " ", sizeof( gamename ) ); Q_strncatz( gamename, sv.configstrings[sv.configstrings[CS_GAMETYPETITLE][0] ? CS_GAMETYPETITLE : CS_GAMETYPENAME], sizeof( gamename ) ); } for( i = 0; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[i]; if( cl->state >= CS_CONNECTED ) { if( cl->tvclient ) // exclude TV from the max players count continue; if( cl->edict->r.svflags & SVF_FAKECLIENT ) bots++; players++; } maxclients++; } Q_snprintfz( version, sizeof( version ), "%i.%i.0.0", APP_VERSION_MAJOR, APP_VERSION_MINOR ); SV_GetSteamTags( tags ); if( tags[0] ) flags |= 0x20; MSG_Init( &msg, msgbuf, sizeof( msgbuf ) ); MSG_WriteByte( &msg, 'I' ); MSG_WriteByte( &msg, APP_PROTOCOL_VERSION ); MSG_WriteString( &msg, hostname ); MSG_WriteString( &msg, sv.mapname ); MSG_WriteString( &msg, gamedir ); MSG_WriteString( &msg, gamename ); MSG_WriteShort( &msg, 0 ); // app ID specified later MSG_WriteByte( &msg, min( players, 99 ) ); MSG_WriteByte( &msg, min( maxclients, 99 ) ); MSG_WriteByte( &msg, min( bots, 99 ) ); MSG_WriteByte( &msg, ( dedicated && dedicated->integer ) ? 'd' : 'l' ); MSG_WriteByte( &msg, STEAMQUERY_OS ); MSG_WriteByte( &msg, Cvar_String( "password" )[0] ? 1 : 0 ); MSG_WriteByte( &msg, 0 ); // VAC insecure MSG_WriteString( &msg, version ); MSG_WriteByte( &msg, flags ); // port MSG_WriteShort( &msg, sv_port->integer ); // tags if( flags & 0x20 ) MSG_WriteString( &msg, tags ); // 64-bit game ID - needed to specify app ID MSG_WriteLong( &msg, APP_STEAMID & 0xffffff ); MSG_WriteLong( &msg, 0 ); Netchan_OutOfBand( socket, address, msg.cursize, msg.data ); return true; } if( s[0] == 'U' ) { // players msg_t msg; uint8_t msgbuf[MAX_STEAMQUERY_PACKETLEN - sizeof( int32_t )]; int i, players = 0; client_t *cl; char name[MAX_NAME_BYTES]; unsigned int time = Sys_Milliseconds(); if( sv_showInfoQueries->integer ) Com_Printf( "Steam Players Packet %s\n", NET_AddressToString( address ) ); MSG_Init( &msg, msgbuf, sizeof( msgbuf ) ); MSG_WriteByte( &msg, 'D' ); MSG_WriteByte( &msg, 0 ); for( i = 0; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[i]; if( ( cl->state < CS_CONNECTED ) || cl->tvclient ) continue; Q_strncpyz( name, COM_RemoveColorTokens( cl->name ), sizeof( name ) ); if( ( msg.cursize + 10 + strlen( name ) ) > sizeof( msgbuf ) ) break; MSG_WriteByte( &msg, i ); MSG_WriteString( &msg, name ); MSG_WriteLong( &msg, cl->edict->r.client->r.frags ); MSG_WriteFloat( &msg, ( float )( time - cl->lastconnect ) * 0.001f ); players++; if( players == 99 ) break; } msgbuf[1] = players; Netchan_OutOfBand( socket, address, msg.cursize, msg.data ); return true; } if( !strcmp( s, "s" ) ) { // master server query, terminated by \n, followed by the challenge int i; bool fromMaster = false; int challenge; char gamedir[MAX_QPATH], basedir[MAX_QPATH], tags[MAX_STEAMQUERY_TAG_STRING]; int players = 0, bots = 0, maxclients = 0; client_t *cl; char msg[MAX_STEAMQUERY_PACKETLEN]; for( i = 0; i < MAX_MASTERS; i++ ) { if( sv_masters[i].steam && NET_CompareAddress( address, &sv_masters[i].address ) ) { fromMaster = true; break; } } if( !fromMaster ) return true; if( sv_showInfoQueries->integer ) Com_Printf( "Steam Master Server Info Packet %s\n", NET_AddressToString( address ) ); challenge = MSG_ReadLong( inmsg ); Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) ); Q_strncpyz( basedir, FS_BaseGameDirectory(), sizeof( basedir ) ); SV_GetSteamTags( tags ); for( i = 0; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[i]; if( cl->state >= CS_CONNECTED ) { if( cl->tvclient ) // exclude TV from the max players count continue; if( cl->edict->r.svflags & SVF_FAKECLIENT ) bots++; players++; } maxclients++; } Q_snprintfz( msg, sizeof( msg ), "0\n\\protocol\\7\\challenge\\%i" // protocol must be 7 to match Source "\\players\\%i\\max\\%i\\bots\\%i" "\\gamedir\\%s\\map\\%s" "\\password\\%i\\os\\%c" "\\lan\\%i\\region\\255" "%s%s" "\\type\\%c\\secure\\0" "\\version\\%i.%i.0.0" "\\product\\%s\n", challenge, min( players, 99 ), min( maxclients, 99 ), min( bots, 99 ), gamedir, sv.mapname, Cvar_String( "password" )[0] ? 1 : 0, STEAMQUERY_OS, sv_public->integer ? 0 : 1, tags[0] ? "\\gametype\\" /* legacy - "gametype", not "tags" */ : "", tags, ( dedicated && dedicated->integer ) ? 'd' : 'l', APP_VERSION_MAJOR, APP_VERSION_MINOR, basedir ); NET_SendPacket( socket, ( const uint8_t * )msg, strlen( msg ), address ); return true; } if( s[0] == 'O' ) { // out of date message static bool printed = false; if( !printed ) { int i; for( i = 0; i < MAX_MASTERS; i++ ) { if( sv_masters[i].steam && NET_CompareAddress( address, &sv_masters[i].address ) ) { Com_Printf( "Server is out of date and cannot be added to the Steam master servers.\n" ); printed = true; return true; } } } return true; } #endif return false; }
/* * 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 ); }
/* * 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 ) ); }
/* * Sys_Library_GetGameLibPath */ const char *Sys_Library_GetGameLibPath( const char *name, int64_t time, int randomizer ) { static char tempname[1024 * 10]; Q_snprintfz( tempname, sizeof( tempname ), "%s/%s/tempmodules_%lld_%d_%d/%s", FS_RuntimeDirectory(), FS_GameDirectory(), time, Sys_GetCurrentProcessId(), randomizer, name ); return tempname; }
/** * Responds to a Steam server query. * * @param s query string * @param socket response socket * @param address response address * @param inmsg message for arguments * @return whether the request was handled as a Steam query */ bool TV_Downstream_SteamServerQuery( const char *s, const socket_t *socket, const netadr_t *address, msg_t *inmsg ) { #if APP_STEAMID if( ( !tv_public->integer && !NET_IsLANAddress( address ) ) || ( tv_maxclients->integer == 1 ) ) return false; if( !strcmp( s, "i" ) ) { // ping const char pingResponse[] = "j00000000000000"; Netchan_OutOfBand( socket, address, sizeof( pingResponse ), ( const uint8_t * )pingResponse ); return true; } if( !strcmp( s, "TSource Engine Query" ) ) { // server info char hostname[MAX_INFO_VALUE]; char gamedir[MAX_QPATH]; char version[32]; int i, count = 0; msg_t msg; uint8_t msgbuf[MAX_STEAMQUERY_PACKETLEN - sizeof( int32_t )]; Q_strncpyz( hostname, COM_RemoveColorTokens( tv_name->string ), sizeof( hostname ) ); if( !hostname[0] ) Q_strncpyz( hostname, tv_name->dvalue, sizeof( hostname ) ); Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) ); for( i = 0; i < tv_maxclients->integer; i++ ) { if( tvs.clients[i].state >= CS_CONNECTED ) { count++; if( count == 99 ) break; } } Q_snprintfz( version, sizeof( version ), "%i.%i.0.0", APP_VERSION_MAJOR, APP_VERSION_MINOR ); MSG_Init( &msg, msgbuf, sizeof( msgbuf ) ); MSG_WriteByte( &msg, 'I' ); MSG_WriteByte( &msg, APP_PROTOCOL_VERSION ); MSG_WriteString( &msg, hostname ); MSG_WriteString( &msg, "" ); // no map MSG_WriteString( &msg, gamedir ); MSG_WriteString( &msg, APPLICATION " TV" ); MSG_WriteShort( &msg, 0 ); // app ID specified later MSG_WriteByte( &msg, count ); MSG_WriteByte( &msg, min( tv_maxclients->integer, 99 ) ); MSG_WriteByte( &msg, 0 ); // no bots MSG_WriteByte( &msg, 'p' ); MSG_WriteByte( &msg, STEAMQUERY_OS ); MSG_WriteByte( &msg, tv_password->string[0] ? 1 : 0 ); MSG_WriteByte( &msg, 0 ); // VAC insecure MSG_WriteString( &msg, version ); MSG_WriteByte( &msg, 0x40 | 0x1 ); // spectator data | game ID containing app ID // spectator data MSG_WriteShort( &msg, tv_port->integer ); MSG_WriteString( &msg, hostname ); // 64-bit game ID - needed to specify app ID MSG_WriteLong( &msg, APP_STEAMID & 0xffffff ); MSG_WriteLong( &msg, 0 ); Netchan_OutOfBand( socket, address, msg.cursize, msg.data ); return true; } if( !strcmp( s, "s" ) ) { // master server query, terminated by \n, followed by the challenge bool isSteamMaster = false; int challenge; char gamedir[MAX_QPATH], basedir[MAX_QPATH]; int i, count = 0; char msg[MAX_STEAMQUERY_PACKETLEN]; if( !TV_Downstream_IsMaster( address, &isSteamMaster ) || !isSteamMaster ) return true; challenge = MSG_ReadLong( inmsg ); Q_strncpyz( gamedir, FS_GameDirectory(), sizeof( gamedir ) ); Q_strncpyz( basedir, FS_BaseGameDirectory(), sizeof( basedir ) ); for( i = 0; i < tv_maxclients->integer; i++ ) { if( tvs.clients[i].state >= CS_CONNECTED ) { count++; if( count == 99 ) break; } } Q_snprintfz( msg, sizeof( msg ), "0\n\\protocol\\7\\challenge\\%i" // protocol must be 7 to match Source "\\players\\%i\\max\\%i\\bots\\0" "\\gamedir\\%s" "\\password\\%i\\os\\%c" "\\lan\\%i\\region\\255" "\\type\\p\\secure\\0" "\\version\\%i.%i.0.0" "\\product\\%s\n", challenge, count, min( tv_maxclients->integer, 99 ), gamedir, tv_password->string[0] ? 1 : 0, STEAMQUERY_OS, tv_public->integer ? 0 : 1, APP_VERSION_MAJOR, APP_VERSION_MINOR, basedir ); NET_SendPacket( socket, ( const uint8_t * )msg, strlen( msg ), address ); return true; } if( s[0] == 'O' ) { // out of date message static bool printed = false; if( !printed ) { bool isSteamMaster = false; if( TV_Downstream_IsMaster( address, &isSteamMaster ) && isSteamMaster ) { Com_Printf( "Server is out of date and cannot be added to the Steam master servers.\n" ); printed = true; } } return true; } #endif return false; }
/* * SNAP_BeginDemoRecording */ void SNAP_BeginDemoRecording( int demofile, unsigned int spawncount, unsigned int snapFrameTime, const char *sv_name, unsigned int sv_bitflags, purelist_t *purelist, char *configstrings, entity_state_t *baselines ) { unsigned int i; msg_t msg; uint8_t msg_buffer[MAX_MSGLEN]; purelist_t *purefile; entity_state_t nullstate; entity_state_t *base; MSG_Init( &msg, msg_buffer, sizeof( msg_buffer ) ); SNAP_DemoMetaDataMessage( &msg, "", 0 ); SNAP_RecordDemoMetaDataMessage( demofile, &msg ); // serverdata message MSG_WriteByte( &msg, svc_serverdata ); MSG_WriteLong( &msg, APP_DEMO_PROTOCOL_VERSION ); MSG_WriteLong( &msg, spawncount ); MSG_WriteShort( &msg, (unsigned short)snapFrameTime ); MSG_WriteString( &msg, FS_BaseGameDirectory() ); MSG_WriteString( &msg, FS_GameDirectory() ); MSG_WriteShort( &msg, -1 ); // playernum MSG_WriteString( &msg, sv_name ); // level name MSG_WriteByte( &msg, sv_bitflags & ~SV_BITFLAGS_HTTP ); // sv_bitflags // pure files i = Com_CountPureListFiles( purelist ); if( i > (short)0x7fff ) Com_Error( ERR_DROP, "Error: Too many pure files." ); MSG_WriteShort( &msg, i ); purefile = purelist; while( purefile ) { MSG_WriteString( &msg, purefile->filename ); MSG_WriteLong( &msg, purefile->checksum ); purefile = purefile->next; DEMO_SAFEWRITE( demofile, &msg, false ); } // config strings for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { const char *configstring = configstrings + i * MAX_CONFIGSTRING_CHARS; if( configstring[0] ) { MSG_WriteByte( &msg, svc_servercs ); MSG_WriteString( &msg, va( "cs %i \"%s\"", i, configstring ) ); DEMO_SAFEWRITE( demofile, &msg, false ); } } // baselines memset( &nullstate, 0, sizeof( nullstate ) ); for( i = 0; i < MAX_EDICTS; i++ ) { base = &baselines[i]; if( base->modelindex || base->sound || base->effects ) { MSG_WriteByte( &msg, svc_spawnbaseline ); MSG_WriteDeltaEntity( &nullstate, base, &msg, true, true ); DEMO_SAFEWRITE( demofile, &msg, false ); } } // client expects the server data to be in a separate packet DEMO_SAFEWRITE( demofile, &msg, true ); MSG_WriteByte( &msg, svc_servercs ); MSG_WriteString( &msg, "precache" ); DEMO_SAFEWRITE( demofile, &msg, true ); }
/* * TV_Downstream_New_f * * Sends the first message from the server to a connected client. * This will be sent on the initial upstream and upon each server load. */ void TV_Downstream_New_f( client_t *client ) { int playernum, numpure; int tv_bitflags; purelist_t *iter; msg_t message; uint8_t messageData[MAX_MSGLEN]; // if in CS_AWAITING we have sended the response packet the new once already, // but client might have not got it so we send it again if( client->state >= CS_SPAWNED ) { Com_DPrintf( "New not valid -- already spawned\n" ); return; } // relay is not ready yet if( client->relay && client->relay->state < CA_ACTIVE ) { TV_Relay_DelayNew( client ); return; } // // serverdata needs to go over for all types of servers // to make sure the protocol is right, and to set the gamedir // TV_Downstream_InitClientMessage( client, &message, messageData, sizeof( messageData ) ); // send the serverdata MSG_WriteByte( &message, svc_serverdata ); MSG_WriteLong( &message, APP_PROTOCOL_VERSION ); if( !client->relay ) { MSG_WriteLong( &message, tvs.lobby.spawncount ); MSG_WriteShort( &message, tvs.lobby.snapFrameTime ); MSG_WriteString( &message, FS_BaseGameDirectory() ); MSG_WriteString( &message, FS_GameDirectory() ); } else { MSG_WriteLong( &message, client->relay->servercount ); MSG_WriteShort( &message, client->relay->snapFrameTime ); MSG_WriteString( &message, client->relay->basegame ); MSG_WriteString( &message, client->relay->game ); } if( client->relay ) { // we use our own playernum on the relay server MSG_WriteShort( &message, client->relay->playernum ); } else { playernum = client - tvs.clients; MSG_WriteShort( &message, playernum ); } // send full levelname if( !client->relay ) MSG_WriteString( &message, tv_name->string ); else MSG_WriteString( &message, client->relay->levelname ); memset( &client->lastcmd, 0, sizeof( client->lastcmd ) ); tv_bitflags = SV_BITFLAGS_TVSERVER; if( client->reliable ) tv_bitflags |= SV_BITFLAGS_RELIABLE; MSG_WriteByte( &message, tv_bitflags ); // sv_bitflags // purelist if( !client->relay ) { MSG_WriteShort( &message, 0 ); } else { numpure = Com_CountPureListFiles( client->relay->purelist ); MSG_WriteShort( &message, numpure ); iter = client->relay->purelist; while( iter ) { MSG_WriteString( &message, iter->filename ); MSG_WriteLong( &message, iter->checksum ); iter = iter->next; } } TV_Downstream_ClientResetCommandBuffers( client, true ); TV_Downstream_SendMessageToClient( client, &message ); Netchan_PushAllFragments( &client->netchan ); // don't let it send reliable commands until we get the first configstring request client->state = CS_CONNECTING; }