/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cl_parse.c -- parse a message received from the server #include "client.h" #include "../qcommon/webdownload.h" char *svc_strings[256] = { "svc_bad", "svc_nop", "svc_servercmd", "svc_sound", "svc_serverdata", "svc_spawnbaseline", "svc_download", "svc_playerinfo", "svc_packetentities", "svc_match", "svc_clcack", "svc_servercs", //tmp jalfixme: sends reliable command as unreliable "svc_frame" #ifdef BATTLEYE , "svc_battleye" #endif }; //============================================================================= /* =============== CL_CheckOrDownloadFile Returns true if the file exists, otherwise it attempts to start a download from the server. =============== */ qboolean CL_CheckOrDownloadFile( char *filename ) { if( !cl_downloads->integer ) return qtrue; if( strstr (filename, "..") || *filename == '.' || *filename == '/' || strchr (filename, '\"') ) { Com_Printf( "Not downloading, invalid filename: %s\n", filename ); return qtrue; } // it exists, no need to download if( FS_FOpenFile ( filename, NULL, FS_READ ) != -1 ) { return qtrue; } Com_Printf( "Asking to download %s\n", filename ); CL_AddReliableCommand( va( "download \"%s\"", filename ) ); return qfalse; } /* =============== CL_Download_f =============== */ void CL_Download_f( void ) { char filename[MAX_OSPATH]; if( Cmd_Argc() != 2 ) { Com_Printf( "Usage: download \n" ); return; } Q_snprintfz( filename, sizeof(filename), "%s", Cmd_Argv(1) ); if( strstr (filename, "..") || *filename == '.' || *filename == '/' || strchr (filename, '\"') ) { Com_Printf( "Not downloading, invalid filename.\n" ); return; } if ( FS_FOpenFile (filename, NULL, FS_READ) != -1 ) { Com_Printf( "File already exists.\n" ); return; } Q_strncpyz( cls.download.name, filename, sizeof(cls.download.name) ); Com_Printf( "Asking to download %s\n", cls.download.name ); CL_AddReliableCommand( va("download \"%s\"", cls.download.name) ); } /* ===================== CL_WebDownloadProgress Callback function for webdownloads. Since Web_Get only returns once it's done, we have to do various things here: Update download percent, handle input, redraw UI and send net packets. ===================== */ int CL_WebDownloadProgress( double percent ) { cls.download.percent = percent; IN_Frame(); CL_SendCommand(); CL_UIModule_DrawConnectScreen(qtrue); SCR_UpdateScreen(); // get new key events Sys_SendKeyEvents(); // allow mice or other external controllers to add commands IN_Commands(); // process console commands Cbuf_Execute(); return cls.download.disconnect; } /* ===================== CL_DownloadComplete Checks downloaded file's checksum, renames it and adds to the filesystem. ===================== */ void CL_DownloadComplete( void ) { char oldn[MAX_OSPATH], newn[MAX_OSPATH]; unsigned checksum; qbyte *buffer; int length; Q_snprintfz( oldn, sizeof(oldn), "%s/%s", FS_Gamedir(), cls.download.tempname ); Q_snprintfz( newn, sizeof(newn), "%s/%s", FS_Gamedir(), cls.download.name ); // verify checksum length = FS_LoadAbsoluteFile( oldn, (void **)&buffer, NULL, 0); if( !buffer ) { Com_Printf("Error loading the downloaded file.\n"); return; } checksum = Com_BlockChecksum( buffer, length ); FS_FreeFile( buffer ); if( cls.download.checksum != checksum ) { Com_Printf("Downloaded file has wrong checksum. Removing.\n"); FS_RemoveFile( oldn ); return; } if( FS_RenameFile( oldn, newn ) ) { Com_Printf( "Failed to rename the downloaded file.\n" ); return; } FS_AddPakFile( newn ); // wsw : jal : update filesys } /* ===================== CL_FreeDownloadList ===================== */ void CL_FreeDownloadList( void ) { download_list_t *prev, *dl; dl = cls.download.list; while( dl != NULL ) { prev = dl; dl = prev->next; Mem_TempFree( prev ); } cls.download.list = NULL; } /* ===================== CL_InitDownload_f Hanldles server's initdownload message, starts web or server download if possible ===================== */ void CL_InitDownload_f( void ) { char filename[MAX_OSPATH]; char url[MAX_STRING_CHARS]; int size; unsigned checksum; qboolean allow_serverdownload; download_list_t *new; download_list_t *dl; // read the data Q_strncpyz( filename, Cmd_Argv(1), sizeof(filename) ); size = atoi(Cmd_Argv(2)); checksum = strtoul(Cmd_Argv(3), NULL, 10); allow_serverdownload = (atoi(Cmd_Argv(4)) != 0); Q_strncpyz(url, Cmd_Argv(5), sizeof(url)); 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( "Download request refused: %s\n", url ); // if it's refused, url field holds the reason CL_RequestNextDownload(); return; } if( allow_serverdownload == qfalse && strlen(url) == 0 ) { Com_Printf( "Server doesn't allow downloading of %s\n", filename ); CL_RequestNextDownload(); return; } if( size <= 0 ) { Com_Printf( "Server gave invalid size. Not downloading.\n" ); CL_RequestNextDownload(); return; } if( checksum == 0 ) { Com_Printf( "Server didn't provide checksum. Not downloading.\n" ); CL_RequestNextDownload(); return; } if( strstr (filename, "..") || *filename == '.' || *filename == '/' || *filename == '$' ) { // deny player models by now Com_Printf( "Not downloading, invalid filename: %s\n", filename ); CL_RequestNextDownload(); return; } if( strchr (filename, '\\') || strchr (filename, '/') ) { Com_Printf( "Refusing to download to subdirectory: %s\n", filename ); CL_RequestNextDownload(); return; } if( Q_stricmp(COM_FileExtension(filename), ".pk3")) { Com_Printf( "Refusing to download non .pk3 file: %s\n", filename ); CL_RequestNextDownload(); return; } if( FS_FOpenFile (filename, NULL, FS_READ) != -1 ) { // it exists, no need to download Com_Printf( "Can't download %s. File already exists.\n", filename ); CL_RequestNextDownload(); return; } if( !cl_downloads->integer ) { // shouldn't happen CL_RequestNextDownload(); return; } if( !cl_downloads_from_web->integer && !allow_serverdownload ) { Com_Printf( "Not downloading. Server only provided web download.\n" ); CL_RequestNextDownload(); return; } dl = cls.download.list; while( dl != NULL ) { if( !Q_stricmp(dl->pakname, filename) ) { Com_Printf( "Allready tried downloading %s. Skipping.\n", filename ); CL_RequestNextDownload(); return; } dl = dl->next; } Q_strncpyz( cls.download.name, filename, sizeof(cls.download.name) ); Q_snprintfz( cls.download.tempname, sizeof(cls.download.tempname), "%s.tmp", filename ); cls.download.size = size; cls.download.checksum = checksum; cls.download.percent = 0; new = Mem_TempMalloc(sizeof(download_list_t)); Q_strncpyz( new->pakname, cls.download.name, sizeof(new->pakname) ); new->next = NULL; if( cls.download.list == NULL ) { cls.download.list = new; } else { dl = cls.download.list; while( dl->next != NULL ) dl = dl->next; dl->next = new; } if( cl_downloads_from_web->integer && strlen(url) > 0 ) { char fulltemp[MAX_OSPATH], referer[MAX_STRING_CHARS]; qboolean success; Com_Printf( "Web download: %s from %s\n", cls.download.tempname, url ); Q_snprintfz( fulltemp, sizeof(fulltemp), "%s/%s", FS_Gamedir(), cls.download.tempname ); Q_snprintfz( referer, sizeof(referer), "wsw://%s", NET_AdrToString(&cls.serveraddress) ); cls.download.web = qtrue; cls.download.disconnect = qfalse; success = Web_Get( url, referer, fulltemp, qtrue, 30, CL_WebDownloadProgress ); cls.download.web = qfalse; if( success ) { Com_Printf("Web download of %s was succesfull\n", cls.download.tempname); CL_DownloadComplete(); cls.download.name[0] = 0; cls.download.tempname[0] = 0; cls.download.size = 0; cls.download.percent = 0.0; } else { Com_Printf("Web download of %s failed\n", cls.download.tempname); } // check if user pressed escape to stop the download if( cls.download.disconnect ) { cls.download.disconnect = qfalse; CL_Disconnect_f(); return; } if( success ) { CL_RequestNextDownload(); return; } } if( !allow_serverdownload ) { CL_RequestNextDownload(); return; } cls.download.offset = FS_FOpenFile(cls.download.tempname, &cls.download.filenum, FS_APPEND); if( cls.download.offset < 0 ) { Com_Printf( "Can't download. Couldn't open %s for writing.\n", cls.download.tempname ); cls.download.filenum = 0; cls.download.offset = 0; cls.download.size = 0; CL_RequestNextDownload(); return; } Com_Printf( "Server download: %s\n", cls.download.tempname ); // have to use Sys_Milliseconds because cls.realtime might be old from Web_Get cls.download.timeout = Sys_Milliseconds() + 2000; cls.download.retries = 0; CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, cls.download.offset) ); } /* ===================== CL_StopServerDownload ===================== */ void CL_StopServerDownload( void ) { FS_FCloseFile( cls.download.filenum ); cls.download.filenum = 0; cls.download.name[0] = 0; cls.download.tempname[0] = 0; cls.download.offset = 0; cls.download.size = 0; cls.download.percent = 0.0; cls.download.timeout = 0; cls.download.retries = 0; } /* ===================== CL_RetryDownload Resends download request Also aborts download if we have retried too many times ===================== */ static void CL_RetryDownload( void ) { if( ++cls.download.retries > 5 ) { Com_Printf( "Download timed out: %s\n", cls.download.name ); // let the server know we're done CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, -2) ); CL_StopServerDownload(); CL_RequestNextDownload(); } else { cls.download.timeout = cls.realtime + 2000; CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, cls.download.offset) ); } } /* ===================== CL_CheckDownloadTimeout Retry downloading if too much time has passed since last download packet was received ===================== */ void CL_CheckDownloadTimeout( void ) { if( !cls.download.filenum || !cls.download.timeout || cls.download.timeout > cls.realtime ) return; CL_RetryDownload(); } /* ===================== CL_ParseDownload Handles download message from the server. Writes data to the file and requests next download block. ===================== */ void CL_ParseDownload( msg_t *msg ) { int size, offset; char *svFilename; // read the data svFilename = MSG_ReadString( msg ); offset = MSG_ReadLong( msg ); size = MSG_ReadLong( msg ); if( size <= 0 ) { Com_Printf( "invalid size on download message\n" ); CL_RetryDownload(); return; } if( !cls.download.filenum ) { Com_Printf( "download message while not dowloading\n" ); msg->readcount += size; return; } if( Q_stricmp(cls.download.name, svFilename) ) { Com_Printf( "download message for wrong file\n" ); msg->readcount += size; return; } if( offset < 0 || offset+size > cls.download.size ) { Com_Printf( "invalid download message\n" ); msg->readcount += size; CL_RetryDownload(); return; } if( cls.download.offset != offset ) { Com_Printf( "download message for wrong position\n" ); msg->readcount += size; CL_RetryDownload(); return; } FS_Write( msg->data + msg->readcount, size, cls.download.filenum ); msg->readcount += size; cls.download.offset += size; cls.download.percent = cls.download.offset*1.0 / cls.download.size; if( cls.download.offset < cls.download.size ) { cls.download.timeout = cls.realtime+2000; cls.download.retries = 0; CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, cls.download.offset) ); } else { Com_Printf( "Download complete: %s\n", cls.download.name ); FS_FCloseFile( cls.download.filenum ); CL_DownloadComplete(); // let the server know we're done CL_AddReliableCommand( va("nextdl %s %i", cls.download.name, -1) ); cls.download.filenum = 0; cls.download.name[0] = 0; cls.download.tempname[0] = 0; cls.download.offset = 0; cls.download.size = 0; cls.download.percent = 0.0; cls.download.timeout = 0; cls.download.retries = 0; CL_RequestNextDownload(); } } /* ===================================================================== SERVER CONNECTING MESSAGES ===================================================================== */ void CL_ParseConfigstringCommand( void ); /* ================== CL_ParseServerData ================== */ void CL_ParseServerData( msg_t *msg ) { extern cvar_t *fs_gamedirvar; char *str; int i, start, sv_bitflags; 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 != PROTOCOL_VERSION ) Com_Error( ERR_DROP, "Server returned version %i, not %i", i, PROTOCOL_VERSION ); cl.servercount = MSG_ReadLong( msg ); // game directory str = MSG_ReadString( msg ); Q_strncpyz( cl.gamedir, str, sizeof(cl.gamedir) ); // set gamedir if( ( *str && ( !fs_gamedirvar->string || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str) ) ) || ( !*str && (fs_gamedirvar->string && *fs_gamedirvar->string) ) ) { Cvar_Set( "fs_game", str ); } // 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 1 // get the configstrings request start = MSG_ReadShort( msg ); if( start >= 0 ) CL_AddReliableCommand( va("configstrings %i %i", cl.servercount, start) ); #else // read the configstrings we could get inside this packet { int csNum; char *s; start = MSG_ReadShort( msg ); for( i = 0; i < start; i++ ) { csNum = MSG_ReadShort( msg ); s = MSG_ReadString( msg ); //add the configstring Cmd_TokenizeString( va("cs %i %s", i, s ), qfalse ); CL_ParseConfigstringCommand(); } start = MSG_ReadShort( msg ); if( start >= 0 ) { if( start < MAX_CONFIGSTRINGS ) CL_AddReliableCommand( va("configstrings %i %i", cl.servercount, start) ); else CL_AddReliableCommand( va("baselines %i 0", cl.servercount, start) ); } } #endif CL_RestartMedia(); #ifdef BATTLEYE if( sv_bitflags & 128 ) { // don't start BE again if it's already running (e.g. when changing map) if( cl_battleye->integer && !cls.runBattlEye ) CL_BE_Start(); } #endif // separate the printfs so the server message can have a color Com_Printf( "\n%s\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n", S_COLOR_RED ); Com_Printf( "%s%s\n\n", S_COLOR_WHITE, cl.servermessage ); } /* ================== CL_ParseBaseline ================== */ void CL_ParseBaseline( msg_t *msg ) { entity_state_t *es; unsigned bits; int newnum; entity_state_t nullstate; memset (&nullstate, 0, sizeof(nullstate)); newnum = CL_ParseEntityBits( msg, &bits ); es = &cl_baselines[newnum]; CL_ParseDelta( msg, &nullstate, es, newnum, bits ); } //========= StringCommands================ void CL_Reconnect_f( void ); void CL_Changing_f( void ); void CL_Precache_f( void ); void CL_ForwardToServer_f( void ); void CL_ServerDisconnect_f( void ); /* ================== CL_ValidateConfigstring ================== */ qboolean CL_ValidateConfigstring( char *string ) { char *p; qboolean opened = qfalse; int parity = 0; if( !string || !string[0] ) return qfalse; p = string; while( *p ) { if( *p == '\"' ) { if( opened ) { parity--; opened = qfalse; } else { parity++; opened = qtrue; } } p++; } if( parity != 0 ) return qfalse; return qtrue; } /* ================== CL_ParseConfigstringCommand ================== */ void CL_ParseConfigstringCommand( void ) { int i; char *s; if( Cmd_Argc() < 2 ) return; i = atoi( Cmd_Argv(1) ); s = Cmd_Argv(2); if( !s || !s[0] ) return; if( cl_debug_serverCmd->integer && (cls.state >= CA_ACTIVE || cls.demoplaying) ) Com_Printf( "CL_ParseConfigstringCommand(%i): \"%s\"\n", i, s ); if( i < 0 || i >= MAX_CONFIGSTRINGS ) Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); // wsw : jal : warn if configstring overflow if( strlen(s) >= MAX_CONFIGSTRING_CHARS ) { Com_Printf( "%sWARNING:%s Configstring %i overflowed\n", S_COLOR_YELLOW, S_COLOR_WHITE, i ); Com_Printf( "%s%s\n", S_COLOR_WHITE, s ); } if( !CL_ValidateConfigstring(s) ) { Com_Printf( "%sWARNING:%s Invalid Configstring (%i): %s\n", S_COLOR_YELLOW, S_COLOR_WHITE, i, s ); return; } Q_strncpyz( cl.configstrings[i], s, sizeof(cl.configstrings[i]) ); // allow cgame to update it too CL_GameModule_ServerCommand(); } void CL_CmdStuffText_f( void ) { char *s = Cmd_Argv(1); Com_DPrintf( "stufftext: %s\n", s ); Cbuf_AddText( s ); } typedef struct { char *name; void (*func) (void); } svcmd_t; svcmd_t svcmds[] = { {"reconnect", CL_Reconnect_f}, {"changing", CL_Changing_f}, {"precache", CL_Precache_f}, {"cmd", CL_ForwardToServer_f}, {"stufftext", CL_CmdStuffText_f}, {"cs", CL_ParseConfigstringCommand}, {"disconnect", CL_ServerDisconnect_f}, {"initdownload", CL_InitDownload_f}, {NULL, NULL} }; /* ================== CL_ParseServerCommand ================== */ void CL_ParseServerCommand( msg_t *msg ) { char *s, *text; svcmd_t *cmd; text = MSG_ReadString( msg ); Cmd_TokenizeString( text, qfalse ); s = Cmd_Argv(0); if( cl_debug_serverCmd->integer && (cls.state < CA_ACTIVE || cls.demoplaying) ) Com_Printf( "CL_ParseServerCommand: \"%s\"\n", text ); // filter out these server commands to be called from the client for( cmd = svcmds; cmd->name; cmd++ ) { if( !strcmp(s, cmd->name) ) { cmd->func(); return; } } CL_GameModule_ServerCommand(); } /* ===================================================================== ACTION MESSAGES ===================================================================== */ /* ================== CL_ParseStartSoundPacket ================== */ void CL_ParseStartSoundPacket( msg_t *msg ) { vec3_t pos; int channel, ent; int sound_num; float volume; float attenuation; int flags; flags = MSG_ReadByte( msg ); sound_num = MSG_ReadByte( msg ); // entity relative channel = MSG_ReadShort( msg ); ent = channel>>3; if (ent > MAX_EDICTS) Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent); channel &= 7; // positioned in space if ((flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_8) pos[0] = MSG_ReadChar( msg ); else if ((flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_16) pos[0] = MSG_ReadShort( msg ); else pos[0] = MSG_ReadInt3( msg ); if ((flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_8) pos[1] = MSG_ReadChar( msg ); else if ((flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_16) pos[1] = MSG_ReadShort( msg ); else pos[1] = MSG_ReadInt3( msg ); if ((flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_8) pos[2] = MSG_ReadChar( msg ); else if ((flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_16) pos[2] = MSG_ReadShort( msg ); else pos[2] = MSG_ReadInt3( msg ); if (flags & SND_VOLUME) volume = MSG_ReadByte( msg ) / 255.0; else volume = DEFAULT_SOUND_PACKET_VOLUME; if (flags & SND_ATTENUATION) attenuation = MSG_ReadByte( msg ) / 64.0; else attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; CL_GameModule_GlobalSound( pos, ent, channel, sound_num, volume, attenuation ); } #ifdef BATTLEYE void CL_ParseSVCBattleye( msg_t *msg ) { static qbyte tempdata[BE_MAX_PACKET_SIZE]; short len; unsigned int id; cls.BE.acknowledgedPacket = MSG_ReadLong( msg ); len = MSG_ReadShort( msg ); if( len > 0 && len <= BE_MAX_PACKET_SIZE ) { MSG_ReadData( msg, tempdata, len ); id = MSG_ReadLong( msg ); if( id > cls.BE.commandReceived) { cls.BE.commandReceived = id; // pass this packet to BE Client if( !cls.demoplaying ) CL_BE_NewIncomingPacket( tempdata, len ); } } else if( len != 0 ) Com_Error( ERR_DROP, "Invalid BattlEye packet size from server" ); } #endif void SHOWNET( msg_t *msg, char *s ) { if (cl_shownet->integer>=2) Com_Printf ("%3i:%s\n", msg->readcount-1, s); } /* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; if( cl_shownet->integer == 1 ) { Com_Printf( "%i ", msg->cursize ); } else if( cl_shownet->integer >= 2 ) { Com_Printf( "------------------\n" ); } // parse the message // while( 1 ) { if( msg->readcount > msg->cursize ) { Com_Error( ERR_DROP,"CL_ParseServerMessage: Bad server message" ); break; } cmd = MSG_ReadByte( msg ); if( cl_debug_serverCmd->integer & 4 ) { if( cmd == -1 ) Com_Printf( "%3i:CMD %i %s\n", msg->readcount-1, cmd, "EOF" ); else Com_Printf( "%3i:CMD %i %s\n", msg->readcount-1, cmd, !svc_strings[cmd] ? "bad" : svc_strings[cmd] ); } if( cmd == -1 ) { SHOWNET( msg, "END OF MESSAGE" ); break; } if( cl_shownet->integer>=2 ) { if( !svc_strings[cmd] ) Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); else SHOWNET( msg, svc_strings[cmd] ); } // other commands switch( cmd ) { default: Com_Error( ERR_DROP,"CL_ParseServerMessage: Illegible server message" ); break; case svc_nop: // Com_Printf( "svc_nop\n" ); break; case svc_servercmd: { int cmdNum; char *s; cmdNum = MSG_ReadLong( msg ); if( cmdNum < 0 ) Com_Error( ERR_DISCONNECT, "CL_ParseServerMessage: Invalid cmdNum value received: %i\n", cmdNum ); if( cmdNum > cls.lastExecutedServerCommand ) { cls.lastExecutedServerCommand = cmdNum; CL_ParseServerCommand( msg ); } else { // read but ignore s = MSG_ReadString( msg ); } } break; case svc_serverdata: { Cbuf_Execute(); // make sure any stuffed commands are done CL_ParseServerData( msg ); } break; case svc_sound: CL_ParseStartSoundPacket( msg ); break; case svc_spawnbaseline: CL_ParseBaseline( msg ); break; case svc_download: CL_ParseDownload( msg ); break; case svc_servercs: // configstrings from demo files. they don't have acknowledge CL_ParseServerCommand( msg ); break; case svc_clcack: { cls.reliableAcknowledge = MSG_ReadLong( msg ); if( cls.reliableAcknowledge < 0 ) Com_Error( ERR_DISCONNECT, "CL_ParseServerMessage: Invalid cls.reliableAcknowledge value received: %i\n", cls.reliableAcknowledge ); if( cl_debug_serverCmd->integer & 4 ) Com_Printf( "svc_clcack:%i\n", cls.reliableAcknowledge ); } break; case svc_frame: CL_ParseFrame( msg ); break; #ifdef BATTLEYE case svc_battleye: CL_ParseSVCBattleye( msg ); break; #endif case svc_playerinfo: case svc_packetentities: case svc_match: Com_Error( ERR_DROP, "Out of place frame data" ); break; } } CL_AddNetgraph(); // // if recording demos, copy the message out // // // we don't know if it is ok to save a demo message until // after we have parsed the frame // if( cls.demorecording && !cls.demowaiting ) CL_WriteDemoMessage( msg ); }