diff options
-rw-r--r-- | build/server.mk | 13 | ||||
-rwxr-xr-x | configure | 18 | ||||
-rw-r--r-- | source/cl_console.c | 1 | ||||
-rw-r--r-- | source/cl_demo.c | 12 | ||||
-rw-r--r-- | source/common.c | 24 | ||||
-rw-r--r-- | source/files.c | 24 | ||||
-rw-r--r-- | source/gtv.h | 44 | ||||
-rw-r--r-- | source/mvd_client.c | 1847 | ||||
-rw-r--r-- | source/mvd_game.c | 155 | ||||
-rw-r--r-- | source/mvd_local.h | 88 | ||||
-rw-r--r-- | source/mvd_parse.c | 242 | ||||
-rw-r--r-- | source/net_common.c | 101 | ||||
-rw-r--r-- | source/net_stream.h | 3 | ||||
-rw-r--r-- | source/q_fifo.h | 14 | ||||
-rw-r--r-- | source/q_shared.c | 197 | ||||
-rw-r--r-- | source/q_shared.h | 4 | ||||
-rw-r--r-- | source/sv_ac.c | 58 | ||||
-rw-r--r-- | source/sv_ccmds.c | 78 | ||||
-rw-r--r-- | source/sv_game.c | 15 | ||||
-rw-r--r-- | source/sv_http.c | 820 | ||||
-rw-r--r-- | source/sv_init.c | 25 | ||||
-rw-r--r-- | source/sv_local.h | 90 | ||||
-rw-r--r-- | source/sv_main.c | 113 | ||||
-rw-r--r-- | source/sv_mvd.c | 1296 | ||||
-rw-r--r-- | source/sv_null.c | 9 | ||||
-rw-r--r-- | source/sv_public.h | 6 | ||||
-rw-r--r-- | source/sv_send.c | 17 | ||||
-rw-r--r-- | source/sv_user.c | 5 |
28 files changed, 2586 insertions, 2733 deletions
diff --git a/build/server.mk b/build/server.mk index 6abb1f1..163a904 100644 --- a/build/server.mk +++ b/build/server.mk @@ -24,12 +24,17 @@ SRCFILES+=sv_ccmds.c \ sv_main.c \ sv_send.c \ sv_user.c \ - sv_world.c \ - sv_mvd.c \ - sv_http.c \ - mvd_client.c \ + sv_world.c + +ifdef USE_MVD_SERVER +SRCFILES+=sv_mvd.c +endif + +ifdef USE_MVD_CLIENT +SRCFILES+=mvd_client.c \ mvd_parse.c \ mvd_game.c +endif ifdef USE_ANTICHEAT SRCFILES+=sv_ac.c @@ -91,6 +91,8 @@ use_ref="gl" use_ui="yes" use_server="no" use_anticheat="no" +use_mvd_server="yes" +use_mvd_client="yes" use_asm="???" use_openffa="no" @@ -154,6 +156,10 @@ for opt do ;; --enable-anticheat) use_anticheat="yes" ;; + --disable-mvd-server) use_mvd_server="no" + ;; + --disable-mvd-client) use_mvd_client="no" + ;; --single-user) singleuser="yes" ;; --datadir=*) datadir=`echo $opt | cut -d '=' -f 2` @@ -193,6 +199,8 @@ echo " --enable-jpg enable JPG images support" echo " --disable-ui disable menu user interface" echo " --disable-asm disable i386 assembly optimizations" echo " --enable-anticheat enable r1ch.net anticheat server interface" +echo " --disable-mvd-server disable MVD/GTV server" +echo " --disable-mvd-client disable MVD/GTV client" echo " --single-user assume to be installed in home dir" echo " --prefix=PREFIX install in PREFIX [$prefix]" echo " --datadir=DIR path to game data tree [$datadir]" @@ -513,6 +521,16 @@ if [ "$use_anticheat" = "yes" ]; then echo "#define USE_ANTICHEAT 2" >> $config_h fi +if [ "$use_mvd_server" = "yes" ]; then + echo "USE_MVD_SERVER=yes" >> $config_mk + echo "#define USE_MVD_SERVER 1" >> $config_h +fi + +if [ "$use_mvd_client" = "yes" ]; then + echo "USE_MVD_CLIENT=yes" >> $config_mk + echo "#define USE_MVD_CLIENT 1" >> $config_h +fi + echo "#define USE_MAPCHECKSUM 1" >> $config_h echo "#define USE_AUTOREPLY 1" >> $config_h diff --git a/source/cl_console.c b/source/cl_console.c index af89fc5..0e5127d 100644 --- a/source/cl_console.c +++ b/source/cl_console.c @@ -671,6 +671,7 @@ void Con_DrawNotify( void ) { R_DrawString( CHAR_WIDTH, v, 0, MAX_STRING_CHARS, text, con.charsetImage ); + con.chatPrompt.inputLine.visibleChars = con.linewidth - skip + 1; IF_Draw( &con.chatPrompt.inputLine, skip * CHAR_WIDTH, v, UI_DRAWCURSOR, con.charsetImage ); } diff --git a/source/cl_demo.c b/source/cl_demo.c index 8812870..bc39672 100644 --- a/source/cl_demo.c +++ b/source/cl_demo.c @@ -245,7 +245,11 @@ void CL_Stop_f( void ) { Com_Printf( "Stopped demo (%u bytes written).\n", msglen ); } -extern const cmd_option_t o_mvdrecord[]; +static const cmd_option_t o_record[] = { + { "h", "help", "display this message" }, + { "z", "gzip", "compress file with gzip" }, + { NULL } +}; /* ==================== @@ -275,12 +279,12 @@ static void CL_Record_f( void ) { return; } - while( ( c = Cmd_ParseOptions( o_mvdrecord ) ) != -1 ) { + while( ( c = Cmd_ParseOptions( o_record ) ) != -1 ) { switch( c ) { case 'h': - Cmd_PrintUsage( o_mvdrecord, "[/]<filename>" ); + Cmd_PrintUsage( o_record, "[/]<filename>" ); Com_Printf( "Begin client demo recording.\n" ); - Cmd_PrintHelp( o_mvdrecord ); + Cmd_PrintHelp( o_record ); return; case 'z': gzip = qtrue; diff --git a/source/common.c b/source/common.c index e5ec0c3..e3bf26d 100644 --- a/source/common.c +++ b/source/common.c @@ -294,7 +294,7 @@ void Com_Printf( const char *fmt, ... ) { Sys_ConsoleOutput( msg ); // remote console - SV_ConsoleOutput( msg ); + //SV_ConsoleOutput( msg ); // logfile if( com_logFile ) { @@ -976,6 +976,28 @@ size_t FIFO_Write( fifo_t *fifo, const void *buffer, size_t len ) { return tail + wrapped; } +qboolean FIFO_ReadMessage( fifo_t *fifo, size_t msglen ) { + size_t len; + byte *data; + + data = FIFO_Peek( fifo, &len ); + if( len < msglen ) { + // read in two chunks into message buffer + if( !FIFO_TryRead( fifo, msg_read_buffer, msglen ) ) { + return qfalse; // not yet available + } + SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) ); + } else { + // read in a single block without copying any memory + SZ_Init( &msg_read, data, msglen ); + FIFO_Decommit( fifo, msglen ); + } + + msg_read.cursize = msglen; + return qtrue; +} + + /* ============================================================================== diff --git a/source/files.c b/source/files.c index c297c9f..6289d52 100644 --- a/source/files.c +++ b/source/files.c @@ -474,15 +474,7 @@ void FS_FCloseFile( fileHandle_t f ) { break; } - // don't clear name and mode, in case - // this handle will be reopened later - file->type = FS_FREE; - file->fp = NULL; -#if USE_ZLIB - file->zfp = NULL; -#endif - file->pak = NULL; - file->unique = qfalse; + memset( file, 0, sizeof( *file ) ); } /* @@ -2516,12 +2508,16 @@ void FS_Restart( void ) { temp = com_logFile; com_logFile = 0; - // make sure no files are opened for reading + // make sure no files from paks are opened for( i = 0, file = fs_files; i < MAX_FILE_HANDLES; i++, file++ ) { - if( file->type == FS_FREE ) { - continue; - } - if( file->mode == FS_MODE_READ ) { + switch( file->type ) { + case FS_FREE: + case FS_REAL: +#if USE_ZLIB + case FS_GZIP: +#endif + break; + default: Com_Error( ERR_FATAL, "%s: closing handle %d", __func__, i + 1 ); } } diff --git a/source/gtv.h b/source/gtv.h new file mode 100644 index 0000000..6486408 --- /dev/null +++ b/source/gtv.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2003-2008 Andrey Nazarov + +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. + +*/ + +#define GTV_PROTOCOL_VERSION 0xED01 + +#define GTF_DEFLATE 1 + +typedef enum { + GTS_HELLO, + GTS_PONG, + GTS_STREAM_START, + GTS_STREAM_STOP, + GTS_STREAM_DATA, + GTS_ERROR, + GTS_BADREQUEST, + GTS_NOACCESS, + GTS_DISCONNECT, + GTS_RECONNECT +} gtv_serverop_t; + +typedef enum { + GTC_HELLO, + GTC_PING, + GTC_STREAM_START, + GTC_STREAM_STOP +} gtv_clientop_t; + diff --git a/source/mvd_client.c b/source/mvd_client.c index bd3c794..a4e3370 100644 --- a/source/mvd_client.c +++ b/source/mvd_client.c @@ -1,5 +1,5 @@ /* -Copyright (C) 2003-2006 Andrey Nazarov +Copyright (C) 2003-2008 Andrey Nazarov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,15 +19,61 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // -// mvd_client.c +// mvd_client.c -- MVD/GTV client // #include "sv_local.h" #include "mvd_local.h" +#include "gtv.h" #include <setjmp.h> +#define GTV_DEFAULT_BACKOFF (5*1000) +#define GTV_MAXIMUM_BACKOFF (5*3600*1000) + +typedef enum { + GTV_DISCONNECTED, // disconnected + GTV_CONNECTING, // connect() in progress + GTV_PREPARING, // waiting for server hello + GTV_CONNECTED, // keeping connection alive + GTV_ACTIVE, // actively running MVD stream + GTV_OVERFLOWED // recovering from delay buffer overflow +} gtv_state_t; + +typedef struct gtv_s { + list_t entry; + + int id; + char name[MAX_MVD_NAME]; + gtv_state_t state; + mvd_t *mvd; + + // connection variables + netstream_t stream; + char address[MAX_QPATH]; + byte *data; + size_t msglen; +#if USE_ZLIB + qboolean z_act; // true when actively inflating + z_stream z_str; + fifo_t z_buf; +#endif + unsigned last_rcvd; + unsigned last_sent; + void (*drop)( struct gtv_s * ); + void (*destroy)( struct gtv_s * ); + void (*run)( struct gtv_s * ); + unsigned retry_time; + unsigned retry_backoff; + + // demo related variables + fileHandle_t demoplayback; + int demoloop; + string_entry_t *demohead, *demoentry; +} gtv_t; + +static LIST_DECL( mvd_gtvs ); + LIST_DECL( mvd_channels ); -LIST_DECL( mvd_ready ); LIST_DECL( mvd_active ); mvd_t mvd_waitingRoom; @@ -36,52 +82,31 @@ int mvd_chanid; jmp_buf mvd_jmpbuf; -cvar_t *mvd_running; -cvar_t *mvd_shownet; -cvar_t *mvd_debug; -cvar_t *mvd_timeout; -cvar_t *mvd_wait_delay; -cvar_t *mvd_wait_percent; -cvar_t *mvd_chase_msgs; +cvar_t *mvd_shownet; +cvar_t *mvd_timeout; +cvar_t *mvd_wait_delay; +cvar_t *mvd_wait_percent; +cvar_t *mvd_chase_msgs; // ==================================================================== -void MVD_Disconnect( mvd_t *mvd ) { - if( mvd->demoplayback ) { - FS_FCloseFile( mvd->demoplayback ); - mvd->demoplayback = 0; - } else { - if( Com_IsDedicated() ) { - MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0, - "[MVD] Disconnected from the game server!\n" ); - } - NET_Close( &mvd->stream ); - } - - mvd->state = MVD_DISCONNECTED; -} - -static void MVD_FreePlayer( mvd_player_t *player ) { - mvd_cs_t *cs, *next; - - for( cs = player->configstrings; cs; cs = next ) { - next = cs->next; - Z_Free( cs ); - } -} - void MVD_Free( mvd_t *mvd ) { int i; -#if USE_ZLIB - if( mvd->z.state ) { - inflateEnd( &mvd->z ); + // destroy any existing connection + if( mvd->gtv ) { + mvd->gtv->destroy( mvd->gtv ); } - if( mvd->zbuf.data ) { - Z_Free( mvd->zbuf.data ); + + // stop demo recording + if( mvd->demorecording ) { + uint16_t msglen = 0; + FS_Write( &msglen, 2, mvd->demorecording ); + FS_FCloseFile( mvd->demorecording ); + mvd->demorecording = 0; } -#endif + for( i = 0; i < mvd->maxclients; i++ ) { MVD_FreePlayer( &mvd->players[i] ); } @@ -89,16 +114,15 @@ void MVD_Free( mvd_t *mvd ) { CM_FreeMap( &mvd->cm ); + Z_Free( mvd->delay.data ); + List_Remove( &mvd->active ); - List_Remove( &mvd->ready ); List_Remove( &mvd->entry ); Z_Free( mvd ); } void MVD_Destroy( mvd_t *mvd ) { - udpClient_t *u, *unext; - tcpClient_t *t; - uint16_t length; + mvd_client_t *client, *next; // update channel menus if( !LIST_EMPTY( &mvd->active ) ) { @@ -106,42 +130,14 @@ void MVD_Destroy( mvd_t *mvd ) { } // cause UDP clients to reconnect - LIST_FOR_EACH_SAFE( udpClient_t, u, unext, &mvd->udpClients, entry ) { - MVD_SwitchChannel( u, &mvd_waitingRoom ); - } - - // send EOF to TCP clients and kick them - length = 0; - LIST_FOR_EACH( tcpClient_t, t, &mvd->tcpClients, mvdEntry ) { - SV_HttpWrite( t, &length, 2 ); - SV_HttpFinish( t ); - SV_HttpDrop( t, "channel destroyed" ); - NET_Run( &t->stream ); - } - - // stop demo recording - if( mvd->demorecording ) { - uint16_t msglen = 0; - FS_Write( &msglen, 2, mvd->demorecording ); - FS_FCloseFile( mvd->demorecording ); - mvd->demorecording = 0; + LIST_FOR_EACH_SAFE( mvd_client_t, client, next, &mvd->clients, entry ) { + MVD_SwitchChannel( client, &mvd_waitingRoom ); } - // disconnect if still connected - MVD_Disconnect( mvd ); - // free all channel data MVD_Free( mvd ); } -void MVD_Drop( mvd_t *mvd ) { - if( mvd->state < MVD_WAITING ) { - MVD_Destroy( mvd ); - } else { - MVD_Disconnect( mvd ); - } -} - void MVD_Destroyf( mvd_t *mvd, const char *fmt, ... ) { va_list argptr; char text[MAXPRINTMSG]; @@ -157,7 +153,54 @@ void MVD_Destroyf( mvd_t *mvd, const char *fmt, ... ) { longjmp( mvd_jmpbuf, -1 ); } -void MVD_Dropf( mvd_t *mvd, const char *fmt, ... ) { +mvd_t *MVD_SetChannel( int arg ) { + char *s = Cmd_Argv( arg ); + mvd_t *mvd; + int id; + + if( LIST_EMPTY( &mvd_channels ) ) { + Com_Printf( "No active channels.\n" ); + return NULL; + } + + if( !*s ) { + if( List_Count( &mvd_channels ) == 1 ) { + return LIST_FIRST( mvd_t, &mvd_channels, entry ); + } + Com_Printf( "Please specify an exact channel ID.\n" ); + return NULL; + } + + if( COM_IsUint( s ) ) { + id = atoi( s ); + LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { + if( mvd->id == id ) { + return mvd; + } + } + } else { + LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { + if( !strcmp( mvd->name, s ) ) { + return mvd; + } + } + } + + Com_Printf( "No such channel ID: %s\n", s ); + return NULL; +} + + +/* +==================================================================== + +COMMON GTV STUFF + +==================================================================== +*/ + +static void q_noreturn q_printf( 2, 3 ) +gtv_dropf( gtv_t *gtv, const char *fmt, ... ) { va_list argptr; char text[MAXPRINTMSG]; @@ -165,660 +208,908 @@ void MVD_Dropf( mvd_t *mvd, const char *fmt, ... ) { Q_vsnprintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); - Com_Printf( "[%s] %s\n", mvd->name, text ); + Com_Printf( "[%s] %s\n", gtv->name, text ); - MVD_Drop( mvd ); + gtv->drop( gtv ); longjmp( mvd_jmpbuf, -1 ); } -void MVD_DPrintf( const char *fmt, ... ) { +static void q_noreturn q_printf( 2, 3 ) +gtv_destroyf( gtv_t *gtv, const char *fmt, ... ) { va_list argptr; char text[MAXPRINTMSG]; - if( !mvd_debug->integer ) { - return; - } - va_start( argptr, fmt ); Q_vsnprintf( text, sizeof( text ), fmt, argptr ); va_end( argptr ); - Com_Printf( S_COLOR_BLUE "%s", text ); + Com_Printf( "[%s] %s\n", gtv->name, text ); + + gtv->destroy( gtv ); + + longjmp( mvd_jmpbuf, -1 ); } -static void MVD_HttpPrintf( mvd_t *mvd, const char *fmt, ... ) { - char buffer[MAX_STRING_CHARS]; - va_list argptr; - size_t len; +static mvd_t *create_channel( gtv_t *gtv ) { + mvd_t *mvd; - va_start( argptr, fmt ); - len = Q_vsnprintf( buffer, sizeof( buffer ), fmt, argptr ); - va_end( argptr ); + mvd = MVD_Mallocz( sizeof( *mvd ) ); + mvd->gtv = gtv; + mvd->id = gtv->id; + Q_strlcpy( mvd->name, gtv->name, sizeof( mvd->name ) ); + mvd->pool.edicts = mvd->edicts; + mvd->pool.edict_size = sizeof( edict_t ); + mvd->pool.max_edicts = MAX_EDICTS; + mvd->pm_type = PM_SPECTATOR; + mvd->min_packets = mvd_wait_delay->value * 10; + List_Init( &mvd->clients ); + List_Init( &mvd->entry ); + List_Init( &mvd->active ); + + return mvd; +} - if( len >= sizeof( buffer ) || FIFO_Write( &mvd->stream.send, buffer, len ) != len ) { - MVD_Dropf( mvd, "%s: overflow", __func__ ); +static gtv_t *gtv_set_conn( int arg ) { + char *s = Cmd_Argv( arg ); + gtv_t *gtv; + int id; + + if( LIST_EMPTY( &mvd_gtvs ) ) { + Com_Printf( "No GTV connections.\n" ); + return NULL; } + + if( !*s ) { + if( List_Count( &mvd_gtvs ) == 1 ) { + return LIST_FIRST( gtv_t, &mvd_gtvs, entry ); + } + Com_Printf( "Please specify an exact connection ID.\n" ); + return NULL; + } + + if( COM_IsUint( s ) ) { + id = atoi( s ); + LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) { + if( gtv->id == id ) { + return gtv; + } + } + } else { + LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) { + if( !strcmp( gtv->name, s ) ) { + return gtv; + } + } + } + + Com_Printf( "No such connection ID: %s\n", s ); + return NULL; } -void MVD_ClearState( mvd_t *mvd ) { - mvd_player_t *player; - int i; +/* +============== +MVD_Frame - // clear all entities, don't trust num_edicts as it is possible - // to miscount removed entities - memset( mvd->edicts, 0, sizeof( mvd->edicts ) ); - mvd->pool.num_edicts = 0; +Called from main server loop. +============== +*/ +int MVD_Frame( void ) { + gtv_t *gtv, *next; + int connections = 0; - // clear all players - for( i = 0; i < mvd->maxclients; i++ ) { - player = &mvd->players[i]; - MVD_FreePlayer( player ); - memset( player, 0, sizeof( *player ) ); + // run all GTV connections (but not demos) + LIST_FOR_EACH_SAFE( gtv_t, gtv, next, &mvd_gtvs, entry ) { + if( setjmp( mvd_jmpbuf ) ) { + continue; + } + + gtv->run( gtv ); + + connections++; } - mvd->numplayers = 0; - // free current map - CM_FreeMap( &mvd->cm ); + return connections; +} + + +/* +==================================================================== + +DEMO PLAYER + +==================================================================== +*/ + +static void demo_play_next( gtv_t *gtv, string_entry_t *entry ); - if( mvd->intermission ) { - // save oldscores - //strcpy( mvd->oldscores, mvd->layout ); +static qboolean demo_read_message( fileHandle_t f ) { + size_t ret; + uint16_t msglen; + + ret = FS_Read( &msglen, 2, f ); + if( ret != 2 ) { + return qfalse; + } + if( !msglen ) { + return qfalse; + } + msglen = LittleShort( msglen ); + if( msglen > MAX_MSGLEN ) { + return qfalse; + } + ret = FS_Read( msg_read_buffer, msglen, f ); + if( ret != msglen ) { + return qfalse; } - memset( mvd->configstrings, 0, sizeof( mvd->configstrings ) ); - mvd->layout[0] = 0; + SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) ); + msg_read.cursize = msglen; - mvd->framenum = 0; - // intermission flag will be cleared in MVD_ChangeLevel + return qtrue; } -void MVD_BeginWaiting( mvd_t *mvd ) { - int maxDelay = mvd_wait_delay->value * 1000; +static qboolean demo_read_frame( mvd_t *mvd ) { + gtv_t *gtv = mvd->gtv; - mvd->state = MVD_WAITING; - mvd->waitTime = svs.realtime; - mvd->waitDelay = 5000 + 1000 * mvd->waitCount; - if( mvd->waitDelay > maxDelay ) { - mvd->waitDelay = maxDelay; + if( mvd->state == MVD_WAITING ) { + return qfalse; // paused by user + } + if( !gtv ) { + MVD_Destroyf( mvd, "End of MVD stream reached" ); } - mvd->waitCount++; -} -static void MVD_EmitGamestate( mvd_t *mvd ) { - char *string; - int i, j; - entity_state_t *es; - player_state_t *ps; - size_t length; - uint8_t *patch; - int flags, extra, portalbytes; - byte portalbits[MAX_MAP_AREAS/8]; + if( !demo_read_message( gtv->demoplayback ) ) { + demo_play_next( gtv, gtv->demoentry->next ); + return qtrue; + } - patch = SZ_GetSpace( &msg_write, 2 ); + MVD_ParseMessage( mvd ); + return qtrue; +} - // send the serverdata - MSG_WriteByte( mvd_serverdata ); - MSG_WriteLong( PROTOCOL_VERSION_MVD ); - MSG_WriteShort( PROTOCOL_VERSION_MVD_CURRENT ); - MSG_WriteLong( mvd->servercount ); - MSG_WriteString( mvd->gamedir ); - MSG_WriteShort( mvd->clientNum ); +static void demo_play_next( gtv_t *gtv, string_entry_t *entry ) { + uint32_t magic = 0; - // send configstrings - for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { - string = mvd->configstrings[i]; - if( !string[0] ) { - continue; - } - length = strlen( string ); - if( length > MAX_QPATH ) { - length = MAX_QPATH; + if( !entry ) { + if( gtv->demoloop ) { + if( --gtv->demoloop == 0 ) { + gtv_destroyf( gtv, "End of play list reached" ); + } } + entry = gtv->demohead; + } - MSG_WriteShort( i ); - MSG_WriteData( string, length ); - MSG_WriteByte( 0 ); + // close previous file + if( gtv->demoplayback ) { + FS_FCloseFile( gtv->demoplayback ); + gtv->demoplayback = 0; } - MSG_WriteShort( MAX_CONFIGSTRINGS ); - // send baseline frame - portalbytes = CM_WritePortalBits( &sv.cm, portalbits ); - MSG_WriteByte( portalbytes ); - MSG_WriteData( portalbits, portalbytes ); - - // send base player states - flags = 0; - if( sv_mvd_noblend->integer ) { - flags |= MSG_PS_IGNORE_BLEND; + // open new file + FS_FOpenFile( entry->string, >v->demoplayback, FS_MODE_READ ); + if( !gtv->demoplayback ) { + gtv_destroyf( gtv, "Couldn't reopen %s", entry->string ); } - if( sv_mvd_nogun->integer ) { - flags |= MSG_PS_IGNORE_GUNINDEX|MSG_PS_IGNORE_GUNFRAMES; + + // figure out if file is compressed and check magic + if( FS_Read( &magic, 4, gtv->demoplayback ) != 4 ) { + gtv_destroyf( gtv, "Couldn't read magic from %s", entry->string ); } - for( i = 0; i < mvd->maxclients; i++ ) { - ps = &mvd->players[i].ps; - extra = 0; - if( !PPS_INUSE( ps ) ) { - extra |= MSG_PS_REMOVE; + if( ( ( LittleLong( magic ) & 0xe0ffffff ) == 0x00088b1f ) ) { + if( !FS_FilterFile( gtv->demoplayback ) ) { + gtv_destroyf( gtv, "Couldn't install gzip filter on %s", entry->string ); + } + if( FS_Read( &magic, 4, gtv->demoplayback ) != 4 ) { + gtv_destroyf( gtv, "Couldn't read magic from %s", entry->string ); } - MSG_WriteDeltaPlayerstate_Packet( NULL, ps, i, flags | extra ); } - MSG_WriteByte( CLIENTNUM_NONE ); + if( magic != MVD_MAGIC ) { + gtv_destroyf( gtv, "%s is not a MVD2 file", entry->string ); + } - // send base entity states - for( i = 1; i < mvd->pool.num_edicts; i++ ) { - es = &mvd->edicts[i].s; - flags = 0; - if( i <= mvd->maxclients ) { - ps = &mvd->players[ i - 1 ].ps; - if( PPS_INUSE( ps ) && ps->pmove.pm_type == PM_NORMAL ) { - flags |= MSG_ES_FIRSTPERSON; - } - } - if( ( j = es->number ) == 0 ) { - flags |= MSG_ES_REMOVE; - } - es->number = i; - MSG_WriteDeltaEntity( NULL, es, flags ); - es->number = j; + // read the first message + if( !demo_read_message( gtv->demoplayback ) ) { + gtv_destroyf( gtv, "Couldn't read first message from %s", entry->string ); } - MSG_WriteShort( 0 ); - // TODO: write private layouts/configstrings + // create MVD channel + if( !gtv->mvd ) { + gtv->mvd = create_channel( gtv ); + gtv->mvd->read_frame = demo_read_frame; + } - length = msg_write.cursize - 2; - patch[0] = length & 255; - patch[1] = ( length >> 8 ) & 255; -} + // parse gamestate + MVD_ParseMessage( gtv->mvd ); + if( !gtv->mvd->state ) { + gtv_destroyf( gtv, "First message of %s does not contain gamestate", entry->string ); + } -void MVD_SendGamestate( tcpClient_t *client ) { - MVD_EmitGamestate( client->mvd ); + gtv->mvd->state = MVD_READING; - Com_DPrintf( "Sent gamestate to MVD client %s\n", - NET_AdrToString( &client->stream.address ) ); - client->state = cs_spawned; + Com_Printf( "[%s] Reading from %s\n", gtv->name, entry->string ); - SV_HttpWrite( client, msg_write.data, msg_write.cursize ); - SZ_Clear( &msg_write ); + // reset state + gtv->demoentry = entry; + + // set channel address + Q_strlcpy( gtv->address, COM_SkipPath( entry->string ), sizeof( gtv->address ) ); } -void MVD_GetStatus( void ) { - char buffer[MAX_STRING_CHARS]; - mvd_t *mvd; - int count, len; +static void demo_destroy( gtv_t *gtv ) { + string_entry_t *entry, *next; + mvd_t *mvd = gtv->mvd; + + if( mvd ) { + mvd->gtv = NULL; + if( !mvd->state ) { + MVD_Free( mvd ); + } + } - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" ); + if( gtv->demoplayback ) { + FS_FCloseFile( gtv->demoplayback ); + gtv->demoplayback = 0; + } - if( http_client->method == HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "\r\n" ); - SV_HttpDrop( http_client, "200 OK" ); - return; + for( entry = gtv->demohead; entry; entry = next ) { + next = entry->next; + Z_Free( entry ); } - SV_HttpPrintf( - "Content-Type: text/html; charset=us-ascii\r\n" - "\r\n" ); + Z_Free( gtv ); +} + - count = SV_CountClients(); - len = Q_EscapeMarkup( buffer, sv_hostname->string, sizeof( buffer ) ); - Q_snprintf( buffer + len, sizeof( buffer ) - len, " - %d/%d", - count, sv_maxclients->integer - sv_reserved_slots->integer ); +/* +==================================================================== - SV_HttpHeader( buffer ); +GTV CONNECTIONS - buffer[len] = 0; - SV_HttpPrintf( "<h1>%s</h1><p>This server has ", buffer ); +==================================================================== +*/ - count = List_Count( &mvd_ready ); - if( count ) { - SV_HttpPrintf( "%d channel%s available, %d active. ", - count, count == 1 ? "" : "s", List_Count( &mvd_active ) ); - } else { - SV_HttpPrintf( "no channels available. " ); +static qboolean gtv_wait_stop( mvd_t *mvd ) { + int usage; + + // see how many frames are buffered + if( mvd->num_packets >= mvd->min_packets ) { + Com_Printf( "[%s] Waiting finished, reading...\n", mvd->name ); + mvd->state = MVD_READING; + return qtrue; } - count = List_Count( &mvd_waitingRoom.udpClients ); - if( count ) { - SV_HttpPrintf( "Waiting room has %d spectator%s.</p>", - count, count == 1 ? "" : "s" ); - } else { - SV_HttpPrintf( "Waiting room is empty.</p>" ); + // see how much data is buffered + usage = FIFO_Percent( &mvd->delay ); + if( usage >= mvd_wait_percent->value ) { + Com_Printf( "[%s] Buffering finished, reading...\n", mvd->name ); + mvd->state = MVD_READING; + return qtrue; } - - if( !LIST_EMPTY( &mvd_ready ) ) { - SV_HttpPrintf( - "<table border=\"1\"><tr>" - "<th>ID</th><th>Name</th><th>Map</th><th>Specs</th><th>Players</th></tr>" ); - - LIST_FOR_EACH( mvd_t, mvd, &mvd_ready, ready ) { - SV_HttpPrintf( "<tr><td>" ); - if( sv_mvd_enable->integer ) { - SV_HttpPrintf( "<a href=\"http://%s/mvdstream/%d\">%d</a>", - http_host, mvd->id, mvd->id ); - } else { - SV_HttpPrintf( "%d", mvd->id ); - } - SV_HttpPrintf( "</td><td>" ); - - Q_EscapeMarkup( buffer, mvd->name, sizeof( buffer ) ); - if( sv_mvd_enable->integer ) { - SV_HttpPrintf( "<a href=\"http://%s/mvdstream/%d\">%s</a>", - http_host, mvd->id, buffer ); - } else { - SV_HttpPrintf( "%s", buffer ); - } - Q_EscapeMarkup( buffer, mvd->mapname, sizeof( buffer ) ); - count = List_Count( &mvd->udpClients ); - SV_HttpPrintf( "</td><td>%s</td><td>%d</td><td>%d</td></tr>", - buffer, count, mvd->numplayers ); - } - SV_HttpPrintf( "</table>" ); + return qfalse; +} + +// ran out of buffers +static void gtv_wait_start( mvd_t *mvd ) { + int tr = mvd_wait_delay->value * 10; + + // if not connected, kill it + if( !mvd->gtv ) { + MVD_Destroyf( mvd, "End of MVD stream reached" ); } - SV_HttpPrintf( - "<p><a href=\"quake2://%s\">Join this server</a></p>", http_host ); + Com_Printf( "[%s] Buffering data...\n", mvd->name ); - SV_HttpFooter(); + // notify spectators + if( Com_IsDedicated() ) { + MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0, + "[MVD] Buffering data, please wait...\n" ); + } - SV_HttpDrop( http_client, "200 OK" ); + mvd->state = MVD_WAITING; + mvd->min_packets = 50 + 10 * mvd->underflows; + if( mvd->min_packets > tr ) { + mvd->min_packets = tr; + } + mvd->underflows++; } -static mvd_t *MVD_SetStream( const char *uri ) { - mvd_t *mvd; - int id; +static qboolean gtv_read_frame( mvd_t *mvd ) { + uint16_t msglen; - if( LIST_EMPTY( &mvd_ready ) ) { - SV_HttpReject( "503 Service Unavailable", - "No MVD streams are available on this server." ); - return NULL; + switch( mvd->state ) { + case MVD_WAITING: + if( !gtv_wait_stop( mvd ) ) { + return qfalse; + } + break; + case MVD_READING: + if( !mvd->num_packets ) { + gtv_wait_start( mvd ); + return qfalse; + } + break; + default: + MVD_Destroyf( mvd, "%s: bad mvd->state", __func__ ); } - if( *uri == '/' ) { - uri++; + // NOTE: if we got here, delay buffer MUST contain + // at least one complete, non-empty packet + + // parse msglen + if( FIFO_Read( &mvd->delay, &msglen, 2 ) != 2 ) { + MVD_Destroyf( mvd, "%s: partial data", __func__ ); } - if( *uri == 0 ) { - if( List_Count( &mvd_ready ) == 1 ) { - return LIST_FIRST( mvd_t, &mvd_ready, ready ); - } - strcpy( http_header, "Cache-Control: no-cache\r\n" ); - SV_HttpReject( "300 Multiple Choices", - "Please specify an exact stream ID." ); - return NULL; + msglen = LittleShort( msglen ); + if( msglen < 1 || msglen > MAX_MSGLEN ) { + MVD_Destroyf( mvd, "%s: invalid msglen", __func__ ); } - id = atoi( uri ); - LIST_FOR_EACH( mvd_t, mvd, &mvd_ready, ready ) { - if( mvd->id == id ) { - return mvd; - } + // read this message + if( !FIFO_ReadMessage( &mvd->delay, msglen ) ) { + MVD_Destroyf( mvd, "%s: partial data", __func__ ); } - SV_HttpReject( "404 Not Found", - "Requested MVD stream was not found on this server." ); - return NULL; -} + // decrement buffered packets counter + mvd->num_packets--; -void MVD_GetStream( const char *uri ) { - mvd_t *mvd; + // parse it + MVD_ParseMessage( mvd ); + return qtrue; +} - mvd = MVD_SetStream( uri ); - if( !mvd ) { - return; +static void write_stream( gtv_t *gtv, void *data, size_t len ) { + if( !FIFO_TryWrite( >v->stream.send, data, len ) ) { + gtv_destroyf( gtv, "Send buffer overflowed" ); } - if( http_client->method == HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n\r\n" ); - SV_HttpDrop( http_client, "200 OK " ); - return; - } + // don't timeout + gtv->last_sent = svs.realtime; +} - List_Append( &mvd->tcpClients, &http_client->mvdEntry ); - http_client->mvd = mvd; +static void write_message( gtv_t *gtv, gtv_clientop_t op ) { + byte header[3]; + size_t len = msg_write.cursize + 1; - SV_MvdInitStream(); + header[0] = len & 255; + header[1] = ( len >> 8 ) & 255; + header[2] = op; + write_stream( gtv, header, sizeof( header ) ); - MVD_SendGamestate( http_client ); + write_stream( gtv, msg_write.data, msg_write.cursize ); } -void MVD_ChangeLevel( mvd_t *mvd ) { - udpClient_t *client; +#if USE_ZLIB +static voidpf gtv_zalloc OF(( voidpf opaque, uInt items, uInt size )) { + return MVD_Malloc( items * size ); +} - if( sv.state != ss_broadcast ) { - MVD_Spawn_f(); // the game is just starting - return; +static void gtv_zfree OF(( voidpf opaque, voidpf address )) { + Z_Free( address ); +} +#endif + +static void parse_hello( gtv_t *gtv ) { + int flags; + + if( gtv->state >= GTV_CONNECTED ) { + gtv_destroyf( gtv, "Duplicated server hello" ); } - // cause all UDP clients to reconnect - MSG_WriteByte( svc_stufftext ); - MSG_WriteString( va( "changing map=%s; reconnect\n", mvd->mapname ) ); + flags = MSG_ReadLong(); - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { - if( mvd->intermission && client->cl->state == cs_spawned ) { - // make them switch to previous target instead of MVD dummy - client->target = client->oldtarget; - client->oldtarget = NULL; +#if USE_ZLIB + if( flags & GTF_DEFLATE ) { + if( !gtv->z_str.state ) { + gtv->z_str.zalloc = gtv_zalloc; + gtv->z_str.zfree = gtv_zfree; + if( inflateInit( >v->z_str ) != Z_OK ) { + gtv_destroyf( gtv, "inflateInit() failed: %s", + gtv->z_str.msg ); + } + } + if( !gtv->z_buf.data ) { + gtv->z_buf.data = MVD_Malloc( MAX_MSGLEN ); + gtv->z_buf.size = MAX_MSGLEN; } - SV_ClientReset( client->cl ); - client->cl->spawncount = mvd->servercount; - SV_ClientAddMessage( client->cl, MSG_RELIABLE ); + gtv->z_act = qtrue; // remaining data is deflated } +#endif - SZ_Clear( &msg_write ); + gtv->state = GTV_CONNECTED; - mvd->intermission = qfalse; + Com_Printf( "[%s] Sending stream request...\n", gtv->name ); - SV_SendAsyncPackets(); + // send stream request + write_message( gtv, GTC_STREAM_START ); } -static void MVD_PlayNext( mvd_t *mvd, string_entry_t *entry ) { - uint32_t magic = 0; +static void parse_stream_start( gtv_t *gtv ) { + if( gtv->state != GTV_CONNECTED ) { + gtv_destroyf( gtv, "Unexpected stream start packet" ); + } - if( !entry ) { - if( mvd->demoloop ) { - if( --mvd->demoloop == 0 ) { - MVD_Destroyf( mvd, "End of play list reached" ); - return; - } + // create the channel + if( !gtv->mvd ) { + mvd_t *mvd = create_channel( gtv ); + + // allocate delay buffer + mvd->delay.data = MVD_Malloc( MAX_MSGLEN * 2 ); + mvd->delay.size = MAX_MSGLEN * 2; + mvd->read_frame = gtv_read_frame; + + gtv->mvd = mvd; + } else { + if( Com_IsDedicated() ) { + // notify spectators + MVD_BroadcastPrintf( gtv->mvd, PRINT_HIGH, 0, + "[MVD] Stream has been resumed.\n" ); } - entry = mvd->demohead; + // reset delay to default + gtv->mvd->min_packets = mvd_wait_delay->value * 10; + gtv->mvd->underflows = 0; } - if( mvd->demoplayback ) { - FS_FCloseFile( mvd->demoplayback ); - mvd->demoplayback = 0; - } + Com_Printf( "[%s] Stream start marker received.\n", gtv->name ); + + gtv->state = GTV_ACTIVE; +} - FS_FOpenFile( entry->string, &mvd->demoplayback, FS_MODE_READ ); - if( !mvd->demoplayback ) { - MVD_Destroyf( mvd, "Couldn't reopen %s", entry->string ); +static void parse_stream_stop( gtv_t *gtv ) { + if( gtv->state < GTV_ACTIVE ) { + gtv_destroyf( gtv, "Unexpected stream stop packet" ); } - if( FS_Read( &magic, 4, mvd->demoplayback ) != 4 ) { - MVD_Destroyf( mvd, "Couldn't read magic out of %s", entry->string ); + if( gtv->mvd && Com_IsDedicated() && gtv->state == GTV_ACTIVE ) { + // notify spectators + MVD_BroadcastPrintf( gtv->mvd, PRINT_HIGH, 0, + "[MVD] Stream has been suspended.\n" ); } - if( ( ( LittleLong( magic ) & 0xe0ffffff ) == 0x00088b1f ) ) { - if( !FS_FilterFile( mvd->demoplayback ) ) { - MVD_Destroyf( mvd, "Couldn't install gzip filter on %s", entry->string ); - } - if( FS_Read( &magic, 4, mvd->demoplayback ) != 4 ) { - MVD_Destroyf( mvd, "Couldn't read magic out of %s", entry->string ); - } + + Com_Printf( "[%s] Stream stop marker received.\n", gtv->name ); + + gtv->state = GTV_CONNECTED; +} + +static void parse_stream_data( gtv_t *gtv ) { + mvd_t *mvd = gtv->mvd; + + if( gtv->state < GTV_ACTIVE ) { + gtv_destroyf( gtv, "Unexpected stream data packet" ); } - if( magic != MVD_MAGIC ) { - MVD_Destroyf( mvd, "%s is not a MVD2 file", entry->string ); + + // ignore if still recovering from overflow + if( gtv->state == GTV_OVERFLOWED ) { + msg_read.readcount = msg_read.cursize; + return; } - Com_Printf( "[%s] Reading from %s\n", mvd->name, entry->string ); + if( !mvd->state ) { + // parse it in place + MVD_ParseMessage( mvd ); + } else { + byte *data = msg_read.data + 1; + size_t len = msg_read.cursize - 1; + uint16_t msglen; + + // see if this packet fits + if( FIFO_Write( &mvd->delay, NULL, len + 2 ) != len + 2 ) { + if( mvd->state == MVD_WAITING ) { + // if delay buffer overflowed in waiting state, + // something is seriously wrong, disconnect for safety + gtv_destroyf( gtv, "Delay buffer overflowed in waiting state" ); + } + + // oops, overflowed + Com_Printf( "[%s] Delay buffer overflowed!\n", gtv->name ); - // reset state - mvd->demoentry = entry; + if( Com_IsDedicated() ) { + // notify spectators + MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0, + "[MVD] Delay buffer overflowed!\n" ); + } - // set channel address - Q_strlcpy( mvd->address, COM_SkipPath( entry->string ), sizeof( mvd->address ) ); + // clear entire delay buffer + FIFO_Clear( &mvd->delay ); + mvd->state = MVD_WAITING; + mvd->min_packets = 50; + mvd->overflows++; + + // request restart of the stream + write_message( gtv, GTC_STREAM_STOP ); + write_message( gtv, GTC_STREAM_START ); + + // ignore any pending data + gtv->state = GTV_OVERFLOWED; + return; + } + + // write it into delay buffer + msglen = LittleShort( len ); + FIFO_Write( &mvd->delay, &msglen, 2 ); + FIFO_Write( &mvd->delay, data, len ); + + // increment buffered packets counter + mvd->num_packets++; + + msg_read.readcount = msg_read.cursize; + } } -void MVD_Finish( mvd_t *mvd, const char *reason ) { - Com_Printf( "[%s] %s\n", mvd->name, reason ); +static void send_hello( gtv_t *gtv ) { + int flags, maxbuf; - if( mvd->demoentry ) { - MVD_PlayNext( mvd, mvd->demoentry->next ); - mvd->state = MVD_PREPARING; - List_Delete( &mvd_ready ); + flags = 0; +#if USE_ZLIB + flags |= GTF_DEFLATE; +#endif + + if( gtv->mvd ) { + maxbuf = gtv->mvd->min_packets - 10; } else { - MVD_Destroy( mvd ); + maxbuf = mvd_wait_delay->value * 10 - 10; + } + if( maxbuf < 0 ) { + maxbuf = 0; } - longjmp( mvd_jmpbuf, -1 ); + MSG_WriteShort( GTV_PROTOCOL_VERSION ); + MSG_WriteLong( flags ); + MSG_WriteShort( maxbuf ); + MSG_WriteLong( 0 ); + MSG_WriteString( NULL ); + MSG_WriteString( com_version->string ); + write_message( gtv, GTC_HELLO ); + SZ_Clear( &msg_write ); } -static void MVD_ReadDemo( mvd_t *mvd ) { - byte *data; - size_t length, read, total = 0; - do { - data = FIFO_Reserve( &mvd->stream.recv, &length ); - if( !length ) { - return; +static qboolean parse_message( gtv_t *gtv, fifo_t *fifo ) { + uint32_t magic; + uint16_t msglen; + int cmd; + + // check magic + if( gtv->state < GTV_PREPARING ) { + if( !FIFO_TryRead( fifo, &magic, 4 ) ) { + return qfalse; + } + if( magic != MVD_MAGIC ) { + gtv_destroyf( gtv, "Not a MVD/GTV stream" ); } - read = FS_Read( data, length, mvd->demoplayback ); - FIFO_Commit( &mvd->stream.recv, read ); - total += read; - } while( read ); + gtv->state = GTV_PREPARING; - if( !total ) { - MVD_Dropf( mvd, "End of MVD file reached" ); + // send client hello + send_hello( gtv ); + } + + // parse msglen + if( !gtv->msglen ) { + if( !FIFO_TryRead( fifo, &msglen, 2 ) ) { + return qfalse; + } + msglen = LittleShort( msglen ); + if( !msglen ) { + gtv_dropf( gtv, "End of MVD/GTV stream" ); + } + if( msglen > MAX_MSGLEN ) { + gtv_destroyf( gtv, "Oversize message" ); + } + gtv->msglen = msglen; } -} -static htcoding_t MVD_FindCoding( const char *name ) { - if( !Q_stricmp( name, "identity" ) ) { - return HTTP_CODING_NONE; + // read this message + if( !FIFO_ReadMessage( fifo, gtv->msglen ) ) { + return qfalse; } - if( !Q_stricmp( name, "gzip" ) ) { - return HTTP_CODING_GZIP; + + gtv->msglen = 0; + + cmd = MSG_ReadByte(); + + if( mvd_shownet->integer == -1 ) { + Com_Printf( "[%"PRIz"]%d ", msg_read.cursize, cmd ); } - if( !Q_stricmp( name, "x-gzip" ) ) { - return HTTP_CODING_GZIP; + + switch( cmd ) { + case GTS_HELLO: + parse_hello( gtv ); + break; + case GTS_PONG: + break; + case GTS_STREAM_START: + parse_stream_start( gtv ); + break; + case GTS_STREAM_STOP: + parse_stream_stop( gtv ); + break; + case GTS_STREAM_DATA: + parse_stream_data( gtv ); + break; + case GTS_ERROR: + gtv_destroyf( gtv, "Server side error occured." ); + break; + case GTS_BADREQUEST: + gtv_destroyf( gtv, "Server refused to process our request." ); + break; + case GTS_NOACCESS: + gtv_destroyf( gtv, + "You don't have permission to access " + "MVD/GTV stream on this server." ); + break; + case GTS_DISCONNECT: + gtv_destroyf( gtv, "Server has been shut down." ); + break; + case GTS_RECONNECT: + gtv_dropf( gtv, "Server has been restarted." ); + break; + default: + gtv_destroyf( gtv, "Unknown command byte" ); } - if( !Q_stricmp( name, "deflate" ) ) { - return HTTP_CODING_DEFLATE; + + if( msg_read.readcount > msg_read.cursize ) { + gtv_destroyf( gtv, "Read past end of message" ); } - return HTTP_CODING_UNKNOWN; + + gtv->last_rcvd = svs.realtime; // don't timeout + return qtrue; } -static qboolean MVD_ParseResponse( mvd_t *mvd ) { - char key[MAX_TOKEN_CHARS]; - char *p, *token; - const char *line; - byte *b, *data; - size_t length; +#if USE_ZLIB +static int inflate_stream( fifo_t *dst, fifo_t *src, z_streamp z ) { + byte *data; + size_t avail_in, avail_out; + int ret = Z_BUF_ERROR; - while( 1 ) { - data = FIFO_Peek( &mvd->stream.recv, &length ); - if( !length ) { + do { + data = FIFO_Peek( src, &avail_in ); + if( !avail_in ) { break; } - if( ( b = memchr( data, '\n', length ) ) != NULL ) { - length = b - data + 1; - } - if( mvd->responseLength + length > MAX_NET_STRING - 1 ) { - MVD_Dropf( mvd, "Response line exceeded maximum length" ); + z->next_in = data; + z->avail_in = ( uInt )avail_in; + + data = FIFO_Reserve( dst, &avail_out ); + if( !avail_out ) { + break; } + z->next_out = data; + z->avail_out = ( uInt )avail_out; - memcpy( mvd->response + mvd->responseLength, data, length ); - mvd->responseLength += length; - mvd->response[mvd->responseLength] = 0; + ret = inflate( z, Z_SYNC_FLUSH ); - FIFO_Decommit( &mvd->stream.recv, length ); + FIFO_Decommit( src, avail_in - z->avail_in ); + FIFO_Commit( dst, avail_out - z->avail_out ); + } while( ret == Z_OK ); - if( !b ) { - continue; - } + return ret; +} - line = mvd->response; - mvd->responseLength = 0; - - if( !mvd->statusCode ) { - // parse version - token = COM_SimpleParse( &line, NULL ); - if( !token[0] ) { - continue; // empty line? - } - if( strncmp( token, "HTTP/", 5 ) ) { - MVD_Dropf( mvd, "Malformed HTTP version" ); - } +static void inflate_more( gtv_t *gtv ) { + int ret = inflate_stream( >v->z_buf, >v->stream.recv, >v->z_str ); - // parse status code - token = COM_SimpleParse( &line, NULL ); - mvd->statusCode = atoi( token ); - if( !mvd->statusCode ) { - MVD_Dropf( mvd, "Malformed HTTP status code" ); - } + switch( ret ) { + case Z_BUF_ERROR: + case Z_OK: + break; + case Z_STREAM_END: + inflateReset( >v->z_str ); + gtv->z_act = qfalse; + break; + default: + gtv_destroyf( gtv, "inflate() failed: %s", gtv->z_str.msg ); + } +} +#endif - // parse reason phrase - if( line ) { - while( *line && *line <= ' ' ) { - line++; - } - Q_ClearStr( mvd->statusText, line, MAX_QPATH ); - } - } else { - // parse header fields - token = COM_SimpleParse( &line, NULL ); - if( !token[0] ) { - return qtrue; // end of header - } - strcpy( key, token ); - p = strchr( key, ':' ); - if( !p ) { - MVD_Dropf( mvd, "Malformed HTTP header field" ); - } - *p = 0; - Q_strlwr( key ); - - token = COM_SimpleParse( &line, NULL ); - if( !strcmp( key, "content-type" ) ) { - } else if( !strcmp( key, "content-encoding" ) ) { - mvd->contentCoding = MVD_FindCoding( token ); - } else if( !strcmp( key, "content-length" ) ) { - mvd->contentLength = atoi( token ); - } else if( !strcmp( key, "transfer-encoding" ) ) { - } else if( !strcmp( key, "location" ) ) { - } - } +static neterr_t run_connect( gtv_t *gtv ) { + neterr_t ret; + uint32_t magic; + + // run connection + if( ( ret = NET_RunConnect( >v->stream ) ) != NET_OK ) { + return ret; } - return qfalse; + Com_Printf( "[%s] Connected to the game server!\n", gtv->name ); + + // allocate buffers + if( !gtv->data ) { + gtv->data = MVD_Malloc( MAX_MSGLEN + 128 ); + } + gtv->stream.recv.data = gtv->data; + gtv->stream.recv.size = MAX_MSGLEN; + gtv->stream.send.data = gtv->data + MAX_MSGLEN; + gtv->stream.send.size = 128; + + // don't timeout + gtv->last_rcvd = svs.realtime; + + // send magic + magic = MVD_MAGIC; + write_stream( gtv, &magic, 4 ); + + return NET_OK; } -static inline float MVD_BufferPercent( mvd_t *mvd ) { - size_t usage = FIFO_Usage( &mvd->stream.recv ); - size_t size = mvd->stream.recv.size; +static neterr_t run_stream( gtv_t *gtv ) { + neterr_t ret; + + // run network stream + if( ( ret = NET_RunStream( >v->stream ) ) != NET_OK ) { + return ret; + } -#ifdef USE_ZLIB - usage += FIFO_Usage( &mvd->zbuf ); - size += mvd->zbuf.size; +#if USE_ZLIB + if( gtv->z_act ) { + do { + // decompress more data + if( gtv->z_act ) { + inflate_more( gtv ); + } + } while( parse_message( gtv, >v->z_buf ) ); + } else #endif + while( parse_message( gtv, >v->stream.recv ) ) + ; - if( !size ) { - return 0; + if( mvd_shownet->integer == -1 ) { + Com_Printf( "\n" ); } - return usage * 100.0f / size; + return NET_OK; } -int MVD_Frame( void ) { - mvd_t *mvd, *next; - neterr_t ret; - float usage; - int connections = 0; +static void check_timeouts( gtv_t *gtv ) { + unsigned timeout = mvd_timeout->value * 1000; - LIST_FOR_EACH_SAFE( mvd_t, mvd, next, &mvd_channels, entry ) { - if( mvd->state <= MVD_DEAD || mvd->state >= MVD_DISCONNECTED ) { - continue; - } + // drop if no data has been received for too long + if( svs.realtime - gtv->last_rcvd > timeout ) { + gtv_dropf( gtv, "Server connection timed out." ); + } - if( setjmp( mvd_jmpbuf ) ) { - continue; + // ping if no data has been sent for too long + if( gtv->state < GTV_CONNECTED ) { + return; + } + if( svs.realtime - gtv->last_sent > 60000 ) { + write_message( gtv, GTC_PING ); + } +} + +static qboolean check_reconnect( gtv_t *gtv ) { + netadr_t adr; + + if( svs.realtime - gtv->retry_time < gtv->retry_backoff ) { + return qfalse; + } + + Com_Printf( "[%s] Attempting to reconnect to %s...\n", + gtv->name, gtv->address ); + + gtv->state = GTV_CONNECTING; + + // don't timeout + gtv->last_sent = gtv->last_rcvd = svs.realtime; + + if( !NET_StringToAdr( gtv->address, &adr, PORT_SERVER ) ) { + gtv_dropf( gtv, "Unable to lookup %s\n", gtv->address ); + } + + if( NET_Connect( &adr, >v->stream ) == NET_ERROR ) { + gtv_dropf( gtv, "%s to %s\n", NET_ErrorString(), + NET_AdrToString( &adr ) ); + } + + return qtrue; +} + +static void gtv_run( gtv_t *gtv ) { + neterr_t ret = NET_AGAIN; + + // check if it is time to reconnect + if( !gtv->state ) { + if( !check_reconnect( gtv ) ) { + return; } + } - if( mvd->demoentry ) { - if( mvd->demoplayback ) { - MVD_ReadDemo( mvd ); - } - if( mvd->state == MVD_PREPARING ) { - MVD_Parse( mvd ); - } - continue; + // run network stream + switch( gtv->stream.state ) { + case NS_CONNECTING: + ret = run_connect( gtv ); + if( ret == NET_AGAIN ) { + return; + } + if( ret == NET_OK ) { + case NS_CONNECTED: + ret = run_stream( gtv ); } + break; + default: + return; + } - connections++; + switch( ret ) { + case NET_AGAIN: + case NET_OK: + check_timeouts( gtv ); + break; + case NET_ERROR: + gtv_dropf( gtv, "%s to %s", NET_ErrorString(), + NET_AdrToString( >v->stream.address ) ); + break; + case NET_CLOSED: + gtv_dropf( gtv, "Server has closed connection." ); + break; + } +} - // process network stream - ret = NET_Run( &mvd->stream ); - switch( ret ) { - case NET_AGAIN: - // check timeout - if( mvd->lastReceived > svs.realtime ) { - mvd->lastReceived = svs.realtime; - } - if( svs.realtime - mvd->lastReceived > mvd_timeout->value * 1000 ) { - MVD_Dropf( mvd, "Connection timed out" ); - } - continue; - case NET_ERROR: - MVD_Dropf( mvd, "%s to %s", NET_ErrorString(), - NET_AdrToString( &mvd->stream.address ) ); - case NET_CLOSED: - MVD_Dropf( mvd, "Connection closed" ); - case NET_OK: - break; +static void gtv_destroy( gtv_t *gtv ) { + mvd_t *mvd = gtv->mvd; + + // any associated MVD channel is orphaned + if( mvd ) { + mvd->gtv = NULL; + if( !mvd->state ) { + // free it here, since it is not yet + // added to global channel list + MVD_Free( mvd ); + } else if( Com_IsDedicated() ) { + // notify spectators + MVD_BroadcastPrintf( mvd, PRINT_HIGH, 0, + "[MVD] Disconnected from the game server!\n" ); } + } - // don't timeout - mvd->lastReceived = svs.realtime; + // make sure network connection is closed + NET_Close( >v->stream ); - // run MVD state machine - switch( mvd->state ) { - case MVD_CONNECTING: - Com_Printf( "[%s] Connected, awaiting response...\n", mvd->name ); - mvd->state = MVD_CONNECTED; - // fall through - case MVD_CONNECTED: - if( !MVD_ParseResponse( mvd ) ) { - continue; - } - if( mvd->statusCode != 200 ) { - MVD_Dropf( mvd, "HTTP request failed: %d %s", - mvd->statusCode, mvd->statusText ); - } - switch( mvd->contentCoding ) { - case HTTP_CODING_NONE: - break; + // unlink from the list of connections + List_Remove( >v->entry ); + + // free all memory buffers #if USE_ZLIB - case HTTP_CODING_GZIP: - case HTTP_CODING_DEFLATE: - if( inflateInit2( &mvd->z, 47 ) != Z_OK ) { - MVD_Dropf( mvd, "inflateInit2() failed: %s", mvd->z.msg ); - } - mvd->zbuf.data = MVD_Malloc( MAX_MSGLEN * 2 ); - mvd->zbuf.size = MAX_MSGLEN * 2; - break; + inflateEnd( >v->z_str ); + Z_Free( gtv->z_buf.data ); #endif - default: - MVD_Dropf( mvd, "Unsupported content encoding: %d", - mvd->contentCoding ); - break; - } - Com_Printf( "[%s] Got response, awaiting gamestate...\n", mvd->name ); - mvd->state = MVD_CHECKING; - // fall through - case MVD_CHECKING: - case MVD_PREPARING: - MVD_Parse( mvd ); - if( mvd->state <= MVD_PREPARING ) { - break; - } - // fall through - case MVD_WAITING: - if( svs.realtime - mvd->waitTime >= mvd->waitDelay ) { - Com_Printf( "[%s] Waiting finished, reading...\n", mvd->name ); - mvd->state = MVD_READING; - break; - } - usage = MVD_BufferPercent( mvd ); - if( usage >= mvd_wait_percent->value ) { - Com_Printf( "[%s] Buffering finished, reading...\n", mvd->name ); - mvd->state = MVD_READING; - } - break; - default: - break; + Z_Free( gtv->data ); + Z_Free( gtv ); +} + +static void gtv_drop( gtv_t *gtv ) { + if( gtv->stream.state < NS_CONNECTED ) { + gtv->retry_backoff += 15*1000; + } else { + // notify spectators + if( Com_IsDedicated() && gtv->mvd ) { + MVD_BroadcastPrintf( gtv->mvd, PRINT_HIGH, 0, + "[MVD] Lost connection to the game server!\n" ); + } + + if( gtv->state == GTV_CONNECTED ) { + gtv->retry_backoff = GTV_DEFAULT_BACKOFF; + } else { + gtv->retry_backoff += 30*1000; } } - return connections; + if( gtv->retry_backoff > GTV_MAXIMUM_BACKOFF ) { + gtv->retry_backoff = GTV_MAXIMUM_BACKOFF; + } + Com_Printf( "[%s] Reconnecting in %d seconds.\n", + gtv->name, gtv->retry_backoff / 1000 ); + + NET_Close( >v->stream ); +#if USE_ZLIB + inflateReset( >v->z_str ); + FIFO_Clear( >v->z_buf ); + gtv->z_act = qfalse; +#endif + gtv->msglen = 0; + gtv->state = GTV_DISCONNECTED; + gtv->retry_time = svs.realtime; } @@ -830,43 +1121,6 @@ OPERATOR COMMANDS ==================================================================== */ -mvd_t *MVD_SetChannel( int arg ) { - char *s = Cmd_Argv( arg ); - mvd_t *mvd; - int id; - - if( LIST_EMPTY( &mvd_channels ) ) { - Com_Printf( "No active channels.\n" ); - return NULL; - } - - if( !*s ) { - if( List_Count( &mvd_channels ) == 1 ) { - return LIST_FIRST( mvd_t, &mvd_channels, entry ); - } - Com_Printf( "Please specify an exact channel ID.\n" ); - return NULL; - } - - if( COM_IsUint( s ) ) { - id = atoi( s ); - LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { - if( mvd->id == id ) { - return mvd; - } - } - } else { - LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { - if( !strcmp( mvd->name, s ) ) { - return mvd; - } - } - } - - Com_Printf( "No such channel ID: %s\n", s ); - return NULL; -} - void MVD_Spawn_f( void ) { SV_InitGame( qtrue ); @@ -884,7 +1138,7 @@ void MVD_Spawn_f( void ) { sv.state = ss_broadcast; } -void MVD_ListChannels_f( void ) { +static void MVD_ListChannels_f( void ) { mvd_t *mvd; int usage; @@ -893,37 +1147,62 @@ void MVD_ListChannels_f( void ) { return; } - Com_Printf( "id name map spc plr stat buf address \n" - "-- ------------ -------- --- --- ---- --- --------------\n" ); + Com_Printf( "id name map spc plr stat buf pack address \n" + "-- ------------ -------- --- --- ---- --- ---- --------------\n" ); LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { Com_Printf( "%2d %-12.12s %-8.8s %3d %3d ", mvd->id, mvd->name, mvd->mapname, - List_Count( &mvd->udpClients ), mvd->numplayers ); + List_Count( &mvd->clients ), mvd->numplayers ); switch( mvd->state ) { case MVD_DEAD: Com_Printf( "DEAD" ); break; - case MVD_CONNECTING: - case MVD_CONNECTED: - Com_Printf( "CNCT" ); - break; - case MVD_CHECKING: - case MVD_PREPARING: - Com_Printf( "PREP" ); - break; case MVD_WAITING: Com_Printf( "WAIT" ); break; case MVD_READING: Com_Printf( "READ" ); break; - case MVD_DISCONNECTED: + } + usage = FIFO_Percent( &mvd->delay ); + Com_Printf( " %3d %4u %s\n", usage, mvd->num_packets, + mvd->gtv ? mvd->gtv->address : "" ); + } +} + +static void MVD_ListGtvs_f( void ) { + gtv_t *gtv; + + if( LIST_EMPTY( &mvd_gtvs ) ) { + Com_Printf( "No GTV connections.\n" ); + return; + } + + Com_Printf( "id name stat address \n" + "-- ------------ ---- --------------\n" ); + + LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) { + Com_Printf( "%2d %-12.12s ", gtv->id, gtv->name ); + switch( gtv->state ) { + case GTV_DISCONNECTED: Com_Printf( "DISC" ); break; + case GTV_CONNECTING: + case GTV_PREPARING: + Com_Printf( "CNCT" ); + break; + case GTV_CONNECTED: + Com_Printf( "IDLE" ); + break; + case GTV_ACTIVE: + Com_Printf( "STRM" ); + break; + case GTV_OVERFLOWED: + Com_Printf( "OVER" ); + break; } - usage = MVD_BufferPercent( mvd ); - Com_Printf( " %3d %s\n", usage, mvd->address ); + Com_Printf( " %s\n", NET_AdrToString( >v->stream.address ) ); } } @@ -951,6 +1230,76 @@ void MVD_StreamedStop_f( void ) { Com_Printf( "[%s] Stopped recording.\n", mvd->name ); } +static void MVD_EmitGamestate( mvd_t *mvd ) { + char *string; + int i; + edict_t *ent; + player_state_t *ps; + size_t length; + int flags, portalbytes; + byte portalbits[MAX_MAP_AREAS/8]; + + // send the serverdata + MSG_WriteByte( mvd_serverdata ); + MSG_WriteLong( PROTOCOL_VERSION_MVD ); + MSG_WriteShort( PROTOCOL_VERSION_MVD_CURRENT ); + MSG_WriteLong( mvd->servercount ); + MSG_WriteString( mvd->gamedir ); + MSG_WriteShort( mvd->clientNum ); + + // send configstrings + for( i = 0; i < MAX_CONFIGSTRINGS; i++ ) { + string = mvd->configstrings[i]; + if( !string[0] ) { + continue; + } + length = strlen( string ); + if( length > MAX_QPATH ) { + length = MAX_QPATH; + } + + MSG_WriteShort( i ); + MSG_WriteData( string, length ); + MSG_WriteByte( 0 ); + } + MSG_WriteShort( MAX_CONFIGSTRINGS ); + + // send baseline frame + portalbytes = CM_WritePortalBits( &sv.cm, portalbits ); + MSG_WriteByte( portalbytes ); + MSG_WriteData( portalbits, portalbytes ); + + // send base player states + for( i = 0; i < mvd->maxclients; i++ ) { + ps = &mvd->players[i].ps; + flags = 0; + if( !PPS_INUSE( ps ) ) { + flags |= MSG_PS_REMOVE; + } + MSG_WriteDeltaPlayerstate_Packet( NULL, ps, i, flags ); + } + MSG_WriteByte( CLIENTNUM_NONE ); + + // send base entity states + for( i = 1; i < mvd->pool.num_edicts; i++ ) { + ent = &mvd->edicts[i]; + flags = 0; + if( i <= mvd->maxclients ) { + ps = &mvd->players[ i - 1 ].ps; + if( PPS_INUSE( ps ) && ps->pmove.pm_type == PM_NORMAL ) { + flags |= MSG_ES_FIRSTPERSON; + } + } + if( !ent->inuse ) { + flags |= MSG_ES_REMOVE; + } + MSG_WriteDeltaEntity( NULL, &ent->s, flags ); + } + MSG_WriteShort( 0 ); + + // TODO: write private layouts/configstrings +} + extern const cmd_option_t o_mvdrecord[]; void MVD_StreamedRecord_f( void ) { @@ -958,6 +1307,7 @@ void MVD_StreamedRecord_f( void ) { fileHandle_t f; mvd_t *mvd; uint32_t magic; + uint16_t msglen; qboolean gzip = qfalse; int c; size_t len; @@ -972,6 +1322,8 @@ void MVD_StreamedRecord_f( void ) { case 'z': gzip = qtrue; break; + default: + return; } } @@ -991,11 +1343,6 @@ void MVD_StreamedRecord_f( void ) { return; } - if( mvd->state < MVD_WAITING ) { - Com_Printf( "[%s] Channel not ready.\n", mvd->name ); - return; - } - // // open the demo file // @@ -1012,7 +1359,7 @@ void MVD_StreamedRecord_f( void ) { return; } - Com_Printf( "[%s] Recording into %s.\n", mvd->name, buffer ); + Com_Printf( "[%s] Recording into %s\n", mvd->name, buffer ); if( gzip ) { FS_FilterFile( f ); @@ -1022,197 +1369,119 @@ void MVD_StreamedRecord_f( void ) { MVD_EmitGamestate( mvd ); + // write magic magic = MVD_MAGIC; - FS_Write( &magic, 4, mvd->demorecording ); - FS_Write( msg_write.data, msg_write.cursize, mvd->demorecording ); + FS_Write( &magic, 4, f ); + + // write gamestate + msglen = LittleShort( msg_write.cursize ); + FS_Write( &msglen, 2, f ); + FS_Write( msg_write.data, msg_write.cursize, f ); SZ_Clear( &msg_write ); } static const cmd_option_t o_mvdconnect[] = { { "h", "help", "display this message" }, - { "e:string", "encoding", "specify default encoding as <string>" }, - { "i:number", "id", "specify remote stream ID as <number>" }, { "n:string", "name", "specify channel name as <string>" }, - { "r:string", "referer", "specify referer as <string> in HTTP request" }, + //{ "u:string", "user", "specify username as <string>" }, + //{ "p:string", "pass", "specify password as <string>" }, { NULL } }; -void MVD_Connect_c( genctx_t *ctx, int argnum ) { +static void MVD_Connect_c( genctx_t *ctx, int argnum ) { Cmd_Option_c( o_mvdconnect, Com_Address_g, ctx, argnum ); } /* ============== MVD_Connect_f - -[http://]host[:port][/resource] ============== */ -void MVD_Connect_f( void ) { +static void MVD_Connect_f( void ) { netadr_t adr; netstream_t stream; - char buffer[MAX_STRING_CHARS]; - char resource[MAX_STRING_CHARS]; - char credentials[MAX_STRING_CHARS]; - char *id = "", *name = NULL, *referer = NULL, *host, *p; - htcoding_t coding = HTTP_CODING_NONE; - mvd_t *mvd; - uint16_t port; + char *name = NULL; + gtv_t *gtv; int c; while( ( c = Cmd_ParseOptions( o_mvdconnect ) ) != -1 ) { switch( c ) { case 'h': - Cmd_PrintUsage( o_mvdconnect, "<uri>" ); - Com_Printf( "Create new MVD channel and connect to URI.\n" ); + Cmd_PrintUsage( o_mvdconnect, "<address[:port]>" ); + Com_Printf( "Connect to the specified MVD/GTV server.\n" ); Cmd_PrintHelp( o_mvdconnect ); - Com_Printf( - "Full URI syntax: [http://][user:pass@]<host>[:port][/resource]\n" - "If resource is given, default port is 80 and stream ID is ignored.\n" - "Otherwise, default port is %d and stream ID is undefined.\n\n" -#if USE_ZLIB - "Accepted content encodings: gzip, deflate.\n" -#endif - , PORT_SERVER ); return; - case 'e': - coding = MVD_FindCoding( cmd_optarg ); - if( coding == HTTP_CODING_UNKNOWN ) { - Com_Printf( "Unknown content encoding: %s.\n", cmd_optarg ); - Cmd_PrintHint(); - return; - } - break; - case 'i': - id = cmd_optarg; - break; case 'n': name = cmd_optarg; break; - case 'r': - referer = cmd_optarg; - break; default: return; } } if( !cmd_optarg[0] ) { - Com_Printf( "Missing URI argument.\n" ); + Com_Printf( "Missing address argument.\n" ); Cmd_PrintHint(); return; } - Cmd_ArgvBuffer( cmd_optind, buffer, sizeof( buffer ) ); - - // skip optional http:// prefix - host = buffer; - if( !strncmp( host, "http://", 7 ) ) { - host += 7; - } - - // parse credentials - p = strchr( host, '@' ); - if( p ) { - *p = 0; - strcpy( credentials, host ); - host = p + 1; - } else { - credentials[0] = 0; - } - - // parse resource - p = strchr( host, '/' ); - if( p ) { - *p = 0; - strcpy( resource, p + 1 ); - port = 80; - } else { - Q_concat( resource, sizeof( resource ), "mvdstream/", id, NULL ); - port = PORT_SERVER; - } - // resolve hostname - if( !NET_StringToAdr( host, &adr, port ) ) { - Com_Printf( "Bad server address: %s\n", host ); + if( !NET_StringToAdr( cmd_optarg, &adr, PORT_SERVER ) ) { + Com_Printf( "Bad server address: %s\n", cmd_optarg ); return; } + // don't allow multiple connections + LIST_FOR_EACH( gtv_t, gtv, &mvd_gtvs, entry ) { + if( NET_IsEqualAdr( &adr, >v->stream.address ) ) { + Com_Printf( "Already connected to %s\n", + NET_AdrToString( &adr ) ); + return; + } + } + + // create new socket and start connecting if( NET_Connect( &adr, &stream ) == NET_ERROR ) { - Com_Printf( "%s to %s\n", NET_ErrorString(), + Com_EPrintf( "%s to %s\n", NET_ErrorString(), NET_AdrToString( &adr ) ); return; } - Z_TagReserve( sizeof( *mvd ) + MAX_MSGLEN * 2 + 256, TAG_MVD ); - - mvd = Z_ReservedAllocz( sizeof( *mvd ) ); - mvd->id = mvd_chanid++; - mvd->state = MVD_CONNECTING; - mvd->contentCoding = coding; - mvd->stream = stream; - mvd->stream.recv.data = Z_ReservedAlloc( MAX_MSGLEN * 2 ); - mvd->stream.recv.size = MAX_MSGLEN * 2; - mvd->stream.send.data = Z_ReservedAlloc( 256 ); - mvd->stream.send.size = 256; - mvd->pool.edicts = mvd->edicts; - mvd->pool.edict_size = sizeof( edict_t ); - mvd->pool.max_edicts = MAX_EDICTS; - mvd->pm_type = PM_SPECTATOR; - mvd->lastReceived = svs.realtime; - mvd->waitDelay = mvd_wait_delay->value * 1000; - List_Init( &mvd->udpClients ); - List_Init( &mvd->tcpClients ); - List_Append( &mvd_channels, &mvd->entry ); - List_Init( &mvd->ready ); - List_Init( &mvd->active ); + // create new connection + gtv = MVD_Mallocz( sizeof( *gtv ) ); + gtv->id = mvd_chanid++; + gtv->state = GTV_CONNECTING; + gtv->stream = stream; + gtv->last_sent = gtv->last_rcvd = svs.realtime; + gtv->run = gtv_run; + gtv->drop = gtv_drop; + gtv->destroy = gtv_destroy; + List_Append( &mvd_gtvs, >v->entry ); // set channel name if( name ) { - Q_strlcpy( mvd->name, name, sizeof( mvd->name ) ); + Q_strlcpy( gtv->name, name, sizeof( gtv->name ) ); } else { - Q_snprintf( mvd->name, sizeof( mvd->name ), "net%d", mvd->id ); + Q_snprintf( gtv->name, sizeof( gtv->name ), "net%d", gtv->id ); } - Q_strlcpy( mvd->address, host, sizeof( mvd->address ) ); + Q_strlcpy( gtv->address, cmd_optarg, sizeof( gtv->address ) ); - Com_Printf( "[%s] Connecting to %s...\n", mvd->name, NET_AdrToString( &adr ) ); - - MVD_HttpPrintf( mvd, - "GET /%s HTTP/1.0\r\n" - "Host: %s\r\n" - "User-Agent: " APPLICATION "/" VERSION "\r\n" -#if USE_ZLIB - "Accept-Encoding: gzip, deflate\r\n" -#endif - "Accept: application/*\r\n", - resource, host ); - if( credentials[0] ) { - Q_Encode64( buffer, credentials, sizeof( buffer ) ); - MVD_HttpPrintf( mvd, "Authorization: Basic %s\r\n", buffer ); - } - if( referer ) { - MVD_HttpPrintf( mvd, "Referer: %s\r\n", referer ); - } - MVD_HttpPrintf( mvd, "\r\n" ); + Com_Printf( "[%s] Connecting to %s...\n", + gtv->name, NET_AdrToString( &adr ) ); } static void MVD_Disconnect_f( void ) { - mvd_t *mvd; - - mvd = MVD_SetChannel( 1 ); - if( !mvd ) { - return; - } + gtv_t *gtv; - if( mvd->state == MVD_DISCONNECTED ) { - Com_Printf( "[%s] Already disconnected.\n", mvd->name ); + gtv = gtv_set_conn( 1 ); + if( !gtv ) { return; } - Com_Printf( "[%s] Channel was disconnected.\n", mvd->name ); - MVD_Drop( mvd ); + Com_Printf( "[%s] Connection destroyed.\n", gtv->name ); + gtv->destroy( gtv ); } static void MVD_Kill_f( void ) { @@ -1235,17 +1504,21 @@ static void MVD_Pause_f( void ) { return; } + if( !mvd->gtv || !mvd->gtv->demoplayback ) { + Com_Printf( "[%s] Only demo channels can be paused.\n", mvd->name ); + return; + } + switch( mvd->state ) { case MVD_WAITING: - Com_Printf( "[%s] Channel was resumed.\n", mvd->name ); + //Com_Printf( "[%s] Channel was resumed.\n", mvd->name ); mvd->state = MVD_READING; break; case MVD_READING: - Com_Printf( "[%s] Channel was paused.\n", mvd->name ); - MVD_BeginWaiting( mvd ); + //Com_Printf( "[%s] Channel was paused.\n", mvd->name ); + mvd->state = MVD_WAITING; break; default: - Com_Printf( "[%s] Channel is not ready.\n", mvd->name ); break; } } @@ -1305,8 +1578,8 @@ static void MVD_Control_f( void ) { Q_strlcpy( mvd->name, name, sizeof( mvd->name ) ); } if( loop != -1 ) { - Com_Printf( "[%s] Loop count changed to %d.\n", mvd->name, loop ); - mvd->demoloop = loop; + //Com_Printf( "[%s] Loop count changed to %d.\n", mvd->name, loop ); + //mvd->demoloop = loop; } } @@ -1325,12 +1598,12 @@ static void MVD_Play_c( genctx_t *ctx, int argnum ) { Cmd_Option_c( o_mvdplay, MVD_File_g, ctx, argnum ); } -void MVD_Play_f( void ) { +static void MVD_Play_f( void ) { char *name = NULL, *s; char buffer[MAX_OSPATH]; int loop = 1; size_t len; - mvd_t *mvd; + gtv_t *gtv; int c, argc; string_entry_t *entry, *head; int i; @@ -1367,6 +1640,7 @@ void MVD_Play_f( void ) { return; } + // build the playlist head = NULL; for( i = argc - 1; i >= cmd_optind; i-- ) { s = Cmd_Argv( i ); @@ -1384,7 +1658,7 @@ void MVD_Play_f( void ) { } len = strlen( buffer ); - entry = Z_Malloc( sizeof( *entry ) + len ); + entry = MVD_Malloc( sizeof( *entry ) + len ); memcpy( entry->string, buffer, len + 1 ); entry->next = head; head = entry; @@ -1394,52 +1668,47 @@ void MVD_Play_f( void ) { return; } - Z_TagReserve( sizeof( *mvd ) + MAX_MSGLEN * 2, TAG_MVD ); - - mvd = Z_ReservedAllocz( sizeof( *mvd ) ); - mvd->id = mvd_chanid++; - mvd->state = MVD_PREPARING; - mvd->demohead = head; - mvd->demoloop = loop; - mvd->stream.recv.data = Z_ReservedAlloc( MAX_MSGLEN * 2 ); - mvd->stream.recv.size = MAX_MSGLEN * 2; - mvd->pool.edicts = mvd->edicts; - mvd->pool.edict_size = sizeof( edict_t ); - mvd->pool.max_edicts = MAX_EDICTS; - mvd->pm_type = PM_SPECTATOR; - List_Init( &mvd->udpClients ); - List_Init( &mvd->tcpClients ); - List_Append( &mvd_channels, &mvd->entry ); - List_Init( &mvd->ready ); - List_Init( &mvd->active ); + // create new connection + gtv = MVD_Mallocz( sizeof( *gtv ) ); + gtv->id = mvd_chanid++; + gtv->state = GTV_CONNECTED; + gtv->demohead = head; + gtv->demoloop = loop; + gtv->drop = demo_destroy; + gtv->destroy = demo_destroy; + //List_Append( &mvd_gtvs, >v->entry ); // set channel name if( name ) { - Q_strlcpy( mvd->name, name, sizeof( mvd->name ) ); + Q_strlcpy( gtv->name, name, sizeof( gtv->name ) ); } else { - Q_snprintf( mvd->name, sizeof( mvd->name ), "dem%d", mvd->id ); + Q_snprintf( gtv->name, sizeof( gtv->name ), "dem%d", gtv->id ); } - MVD_PlayNext( mvd, mvd->demohead ); + demo_play_next( gtv, gtv->demohead ); } void MVD_Shutdown( void ) { - mvd_t *mvd, *next; + gtv_t *gtv, *gtv_next; + mvd_t *mvd, *mvd_next; - LIST_FOR_EACH_SAFE( mvd_t, mvd, next, &mvd_channels, entry ) { - MVD_Disconnect( mvd ); + // kill all connections + LIST_FOR_EACH_SAFE( gtv_t, gtv, gtv_next, &mvd_gtvs, entry ) { + gtv->destroy( gtv ); + } + + // kill all channels + LIST_FOR_EACH_SAFE( mvd_t, mvd, mvd_next, &mvd_channels, entry ) { MVD_Free( mvd ); } + List_Init( &mvd_gtvs ); List_Init( &mvd_channels ); - List_Init( &mvd_ready ); List_Init( &mvd_active ); - if( mvd_clients ) { - Z_Free( mvd_clients ); - mvd_clients = NULL; - } + Z_Free( mvd_clients ); + mvd_clients = NULL; mvd_chanid = 0; @@ -1453,6 +1722,7 @@ static const cmdreg_t c_mvd[] = { { "mvdkill", MVD_Kill_f }, { "mvdspawn", MVD_Spawn_f }, { "mvdchannels", MVD_ListChannels_f }, + { "mvdgtvs", MVD_ListGtvs_f }, { "mvdcontrol", MVD_Control_f }, { "mvdpause", MVD_Pause_f }, @@ -1467,10 +1737,9 @@ MVD_Register */ void MVD_Register( void ) { mvd_shownet = Cvar_Get( "mvd_shownet", "0", 0 ); - mvd_debug = Cvar_Get( "mvd_debug", "0", 0 ); - mvd_timeout = Cvar_Get( "mvd_timeout", "120", 0 ); + mvd_timeout = Cvar_Get( "mvd_timeout", "90", 0 ); mvd_wait_delay = Cvar_Get( "mvd_wait_delay", "20", 0 ); - mvd_wait_percent = Cvar_Get( "mvd_wait_percent", "50", 0 ); + mvd_wait_percent = Cvar_Get( "mvd_wait_percent", "35", 0 ); mvd_chase_msgs = Cvar_Get( "mvd_chase_msgs", "0", 0 ); Cmd_Register( c_mvd ); diff --git a/source/mvd_game.c b/source/mvd_game.c index 55351f0..39fe128 100644 --- a/source/mvd_game.c +++ b/source/mvd_game.c @@ -36,7 +36,7 @@ static cvar_t *mvd_stats_hack; static cvar_t *mvd_freeze_hack; static cvar_t *mvd_chase_prefix; -udpClient_t *mvd_clients; +mvd_client_t *mvd_clients; mvd_player_t mvd_dummy; @@ -51,14 +51,14 @@ LAYOUTS ============================================================================== */ -static void MVD_LayoutClients( udpClient_t *client ) { +static void MVD_LayoutClients( mvd_client_t *client ) { static const char header[] = "xv 16 yv 0 string2 \" Name RTT Status\""; char layout[MAX_STRING_CHARS]; char buffer[MAX_QPATH]; char status[MAX_QPATH]; size_t len, total; - udpClient_t *cl; + mvd_client_t *cl; mvd_t *mvd = client->mvd; int y, i, prestep, flags; @@ -66,7 +66,7 @@ static void MVD_LayoutClients( udpClient_t *client ) { if( client->layout_cursor < 0 ) { client->layout_cursor = 0; } else if( client->layout_cursor ) { - total = List_Count( &mvd->udpClients ); + total = List_Count( &mvd->clients ); if( client->layout_cursor > total / 10 ) { client->layout_cursor = total / 10; } @@ -79,7 +79,7 @@ static void MVD_LayoutClients( udpClient_t *client ) { y = 8; i = 0; - LIST_FOR_EACH( udpClient_t, cl, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, cl, &mvd->clients, entry ) { if( ++i < prestep ) { continue; } @@ -122,7 +122,7 @@ static void MVD_LayoutClients( udpClient_t *client ) { client->layout_time = svs.realtime; } -static void MVD_LayoutChannels( udpClient_t *client ) { +static void MVD_LayoutChannels( mvd_client_t *client ) { static const char header[] = "xv 32 yv 8 picn inventory " "xv 240 yv 172 string2 " VERSION " " @@ -160,7 +160,7 @@ static void MVD_LayoutChannels( udpClient_t *client ) { mvd == client->mvd ? "2" : "", cursor == client->layout_cursor ? 0x8d : 0x20, mvd->name, mvd->mapname, - List_Count( &mvd->udpClients ), + List_Count( &mvd->clients ), mvd->numplayers ); if( len >= sizeof( buffer ) ) { continue; @@ -197,7 +197,7 @@ static void MVD_LayoutChannels( udpClient_t *client ) { #define YES "\xD9\xE5\xF3" #define NO "\xCE\xEF" -static void MVD_LayoutMenu( udpClient_t *client ) { +static void MVD_LayoutMenu( mvd_client_t *client ) { static const char format[] = "xv 32 yv 8 picn inventory " "xv 0 yv 32 cstring \"\020Main Menu\021\" xv 56 " @@ -228,7 +228,7 @@ static void MVD_LayoutMenu( udpClient_t *client ) { total = Q_snprintf( layout, sizeof( layout ), format, cur[0], client->target ? "Leave" : "Enter", cur[1], - cur[2], List_Count( &client->mvd->udpClients ), + cur[2], List_Count( &client->mvd->clients ), cur[3], List_Count( &mvd_active ), cur[4], cur[5], ( client->uf & UF_MUTE_OBSERVERS ) ? YES : NO, cur[6], ( client->uf & UF_MUTE_MISC ) ? YES : NO, @@ -245,7 +245,7 @@ static void MVD_LayoutMenu( udpClient_t *client ) { client->layout_time = svs.realtime; } -static void MVD_LayoutScores( udpClient_t *client, const char *layout ) { +static void MVD_LayoutScores( mvd_client_t *client, const char *layout ) { mvd_t *mvd = client->mvd; int flags = MSG_CLEAR; @@ -266,7 +266,7 @@ static void MVD_LayoutScores( udpClient_t *client, const char *layout ) { client->layout_time = svs.realtime; } -static void MVD_LayoutFollow( udpClient_t *client ) { +static void MVD_LayoutFollow( mvd_client_t *client ) { mvd_t *mvd = client->mvd; char *name = client->target ? client->target->name : "<no target>"; char layout[MAX_STRING_CHARS]; @@ -287,7 +287,7 @@ static void MVD_LayoutFollow( udpClient_t *client ) { client->layout_time = svs.realtime; } -static void MVD_SetDefaultLayout( udpClient_t *client ) { +static void MVD_SetDefaultLayout( mvd_client_t *client ) { mvd_t *mvd = client->mvd; if( mvd == &mvd_waitingRoom ) { @@ -305,7 +305,7 @@ static void MVD_SetDefaultLayout( udpClient_t *client ) { client->layout_cursor = 0; } -static void MVD_SetFollowLayout( udpClient_t *client ) { +static void MVD_SetFollowLayout( mvd_client_t *client ) { if( !client->layout_type ) { MVD_SetDefaultLayout( client ); } else if( client->layout_type == LAYOUT_FOLLOW ) { @@ -315,9 +315,9 @@ static void MVD_SetFollowLayout( udpClient_t *client ) { // this is the only function that actually writes layouts static void MVD_UpdateLayouts( mvd_t *mvd ) { - udpClient_t *client; + mvd_client_t *client; - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { if( client->cl->state != cs_spawned ) { continue; } @@ -377,7 +377,7 @@ CHASE CAMERA ============================================================================== */ -static void MVD_FollowStop( udpClient_t *client ) { +static void MVD_FollowStop( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_cs_t *cs; int i; @@ -415,7 +415,7 @@ static void MVD_FollowStop( udpClient_t *client ) { MVD_UpdateClient( client ); } -static void MVD_FollowStart( udpClient_t *client, mvd_player_t *target ) { +static void MVD_FollowStart( mvd_client_t *client, mvd_player_t *target ) { mvd_cs_t *cs; if( client->target == target ) { @@ -439,7 +439,7 @@ static void MVD_FollowStart( udpClient_t *client, mvd_player_t *target ) { MVD_UpdateClient( client ); } -static void MVD_FollowFirst( udpClient_t *client ) { +static void MVD_FollowFirst( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_player_t *target; int i; @@ -456,7 +456,7 @@ static void MVD_FollowFirst( udpClient_t *client ) { SV_ClientPrintf( client->cl, PRINT_MEDIUM, "[MVD] No players to chase.\n" ); } -static void MVD_FollowLast( udpClient_t *client ) { +static void MVD_FollowLast( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_player_t *target; int i; @@ -473,7 +473,7 @@ static void MVD_FollowLast( udpClient_t *client ) { SV_ClientPrintf( client->cl, PRINT_MEDIUM, "[MVD] No players to chase.\n" ); } -static void MVD_FollowNext( udpClient_t *client ) { +static void MVD_FollowNext( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_player_t *target = client->target; @@ -496,7 +496,7 @@ static void MVD_FollowNext( udpClient_t *client ) { MVD_FollowStart( client, target ); } -static void MVD_FollowPrev( udpClient_t *client ) { +static void MVD_FollowPrev( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_player_t *target = client->target; @@ -521,13 +521,13 @@ static void MVD_FollowPrev( udpClient_t *client ) { static mvd_player_t *MVD_MostFollowed( mvd_t *mvd ) { int count[MAX_CLIENTS]; - udpClient_t *other; + mvd_client_t *other; mvd_player_t *player, *target = NULL; int i, maxcount = -1; memset( count, 0, sizeof( count ) ); - LIST_FOR_EACH( udpClient_t, other, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, other, &mvd->clients, entry ) { if( other->cl->state == cs_spawned && other->target ) { count[ other->target - mvd->players ]++; } @@ -541,7 +541,7 @@ static mvd_player_t *MVD_MostFollowed( mvd_t *mvd ) { return target; } -void MVD_UpdateClient( udpClient_t *client ) { +void MVD_UpdateClient( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_player_t *target = client->target; int i; @@ -593,7 +593,7 @@ void MVD_BroadcastPrintf( mvd_t *mvd, int level, int mask, const char *fmt, ... va_list argptr; char text[MAX_STRING_CHARS]; size_t len; - udpClient_t *other; + mvd_client_t *other; client_t *cl; va_start( argptr, fmt ); @@ -616,7 +616,7 @@ void MVD_BroadcastPrintf( mvd_t *mvd, int level, int mask, const char *fmt, ... MSG_WriteByte( level ); MSG_WriteData( text, len + 1 ); - LIST_FOR_EACH( udpClient_t, other, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, other, &mvd->clients, entry ) { cl = other->cl; if( cl->state < cs_spawned ) { continue; @@ -644,11 +644,11 @@ static void MVD_SetServerState( client_t *cl, mvd_t *mvd ) { cl->maxclients = mvd->maxclients; } -void MVD_SwitchChannel( udpClient_t *client, mvd_t *mvd ) { +void MVD_SwitchChannel( mvd_client_t *client, mvd_t *mvd ) { client_t *cl = client->cl; List_Remove( &client->entry ); - List_Append( &mvd->udpClients, &client->entry ); + List_Append( &mvd->clients, &client->entry ); client->mvd = mvd; client->begin_time = 0; client->target = client->oldtarget = NULL; @@ -661,7 +661,7 @@ void MVD_SwitchChannel( udpClient_t *client, mvd_t *mvd ) { SV_ClientAddMessage( cl, MSG_RELIABLE|MSG_CLEAR ); } -void MVD_TrySwitchChannel( udpClient_t *client, mvd_t *mvd ) { +void MVD_TrySwitchChannel( mvd_client_t *client, mvd_t *mvd ) { if( mvd == client->mvd ) { SV_ClientPrintf( client->cl, PRINT_HIGH, "[MVD] You are already %s.\n", mvd == &mvd_waitingRoom ? @@ -684,7 +684,7 @@ void MVD_TrySwitchChannel( udpClient_t *client, mvd_t *mvd ) { MVD_SwitchChannel( client, mvd ); } -static void MVD_Admin_f( udpClient_t *client ) { +static void MVD_Admin_f( mvd_client_t *client ) { char *s = mvd_admin_password->string; if( client->admin ) { @@ -708,7 +708,7 @@ static void MVD_Admin_f( udpClient_t *client ) { SV_ClientPrintf( client->cl, PRINT_HIGH, "[MVD] Granted admin status.\n" ); } -static void MVD_Say_f( udpClient_t *client, int argnum ) { +static void MVD_Say_f( mvd_client_t *client, int argnum ) { mvd_t *mvd = client->mvd; unsigned delta, delay = mvd_flood_waitdelay->value * 1000; unsigned treshold = mvd_flood_persecond->value * 1000; @@ -761,7 +761,7 @@ static void MVD_Say_f( udpClient_t *client, int argnum ) { 0 : UF_MUTE_OBSERVERS, "%s\n", text ); } -static void MVD_Observe_f( udpClient_t *client ) { +static void MVD_Observe_f( mvd_client_t *client ) { if( client->mvd == &mvd_waitingRoom ) { SV_ClientPrintf( client->cl, PRINT_HIGH, "[MVD] Please enter a channel first.\n" ); @@ -779,7 +779,7 @@ static void MVD_Observe_f( udpClient_t *client ) { } } -static void MVD_Follow_f( udpClient_t *client ) { +static void MVD_Follow_f( mvd_client_t *client ) { mvd_t *mvd = client->mvd; mvd_player_t *player; entity_state_t *ent; @@ -864,7 +864,7 @@ follow: MVD_FollowStart( client, player ); } -static void MVD_Invuse_f( udpClient_t *client ) { +static void MVD_Invuse_f( mvd_client_t *client ) { mvd_t *mvd; int uf = client->uf; @@ -927,7 +927,7 @@ static void MVD_Invuse_f( udpClient_t *client ) { } } -static void MVD_Join_f( udpClient_t *client ) { +static void MVD_Join_f( mvd_client_t *client ) { mvd_t *mvd; SV_BeginRedirect( RD_CLIENT ); @@ -975,15 +975,15 @@ static void print_channel( client_t *cl, mvd_t *mvd ) { SV_ClientPrintf( cl, PRINT_HIGH, "%2d %-12.12s %-8.8s %3d %3d %s\n", mvd->id, mvd->name, mvd->mapname, - List_Count( &mvd->udpClients ), + List_Count( &mvd->clients ), mvd->numplayers, buffer ); } -static void MVD_Channels_f( udpClient_t *client ) { +static void MVD_Channels_f( mvd_client_t *client ) { mvd_t *mvd; if( Cmd_Argc() > 1 ) { - if( LIST_EMPTY( &mvd_ready ) ) { + if( LIST_EMPTY( &mvd_channels ) ) { SV_ClientPrintf( client->cl, PRINT_HIGH, "No ready channels.\n" ); return; @@ -1001,7 +1001,7 @@ static void MVD_Channels_f( udpClient_t *client ) { "-- ------------ -------- --- --- --------------\n" ); if( Cmd_Argc() > 1 ) { - LIST_FOR_EACH( mvd_t, mvd, &mvd_ready, ready ) { + LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { print_channel( client->cl, mvd ); } } else { @@ -1011,14 +1011,14 @@ static void MVD_Channels_f( udpClient_t *client ) { } } -static void MVD_Clients_f( udpClient_t *client ) { +static void MVD_Clients_f( mvd_client_t *client ) { // TODO: dump them in console client->layout_type = LAYOUT_CLIENTS; client->layout_time = 0; client->layout_cursor = 0; } -static void MVD_Commands_f( udpClient_t *client ) { +static void MVD_Commands_f( mvd_client_t *client ) { SV_ClientPrintf( client->cl, PRINT_HIGH, "chase [player_id] toggle chasecam mode\n" "observe toggle observer mode\n" @@ -1032,9 +1032,13 @@ static void MVD_Commands_f( udpClient_t *client ) { } static void MVD_GameClientCommand( edict_t *ent ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); char *cmd; + if( client->cl->state < cs_spawned ) { + return; + } + cmd = Cmd_Argv( 0 ); if( !strcmp( cmd, "!mvdadmin" ) ) { @@ -1140,7 +1144,7 @@ MISC GAME FUNCTIONS void MVD_RemoveClient( client_t *client ) { int index = client - svs.udp_client_pool; - udpClient_t *cl = &mvd_clients[index]; + mvd_client_t *cl = &mvd_clients[index]; List_Remove( &cl->entry ); @@ -1172,9 +1176,9 @@ static void MVD_GameInit( void ) { Cvar_Set( "g_features", va( "%d", MVD_FEATURES ) ); Z_TagReserve( ( sizeof( edict_t ) + - sizeof( udpClient_t ) ) * sv_maxclients->integer + + sizeof( mvd_client_t ) ) * sv_maxclients->integer + sizeof( edict_t ), TAG_MVD ); - mvd_clients = Z_ReservedAllocz( sizeof( udpClient_t ) * + mvd_clients = Z_ReservedAllocz( sizeof( mvd_client_t ) * sv_maxclients->integer ); edicts = Z_ReservedAllocz( sizeof( edict_t ) * ( sv_maxclients->integer + 1 ) ); @@ -1210,7 +1214,7 @@ static void MVD_GameInit( void ) { strcpy( mvd->name, "Waiting Room" ); Cvar_VariableStringBuffer( "game", mvd->gamedir, sizeof( mvd->gamedir ) ); Q_strlcpy( mvd->mapname, mvd_default_map->string, sizeof( mvd->mapname ) ); - List_Init( &mvd->udpClients ); + List_Init( &mvd->clients ); strcpy( mvd->configstrings[CS_NAME], "Waiting Room" ); strcpy( mvd->configstrings[CS_SKY], "unit1_" ); @@ -1254,7 +1258,7 @@ static void MVD_GameReadLevel( const char *filename ) { } static qboolean MVD_GameClientConnect( edict_t *ent, char *userinfo ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); mvd_t *mvd; int count; @@ -1266,7 +1270,7 @@ static qboolean MVD_GameClientConnect( edict_t *ent, char *userinfo ) { } else { mvd = &mvd_waitingRoom; } - List_Append( &mvd->udpClients, &client->entry ); + List_Append( &mvd->clients, &client->entry ); client->mvd = mvd; // override server state @@ -1276,7 +1280,7 @@ static qboolean MVD_GameClientConnect( edict_t *ent, char *userinfo ) { } static void MVD_GameClientBegin( edict_t *ent ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); mvd_t *mvd = client->mvd; mvd_player_t *target; @@ -1318,7 +1322,7 @@ static void MVD_GameClientBegin( edict_t *ent ) { } static void MVD_GameClientUserinfoChanged( edict_t *ent, char *userinfo ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); char *s; float fov; @@ -1339,7 +1343,7 @@ static void MVD_GameClientUserinfoChanged( edict_t *ent, char *userinfo ) { } void MVD_GameClientNameChanged( edict_t *ent, const char *name ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); client_t *cl = client->cl; if( client->begin_time ) { @@ -1350,7 +1354,7 @@ void MVD_GameClientNameChanged( edict_t *ent, const char *name ) { // called early from SV_Drop to prevent multiple disconnect messages void MVD_GameClientDrop( edict_t *ent, const char *reason ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); client_t *cl = client->cl; if( client->begin_time ) { @@ -1361,7 +1365,7 @@ void MVD_GameClientDrop( edict_t *ent, const char *reason ) { } static void MVD_GameClientDisconnect( edict_t *ent ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); client_t *cl = client->cl; if( client->begin_time ) { @@ -1387,7 +1391,7 @@ static int MVD_PointContents( vec3_t p ) { } static void MVD_GameClientThink( edict_t *ent, usercmd_t *cmd ) { - udpClient_t *client = EDICT_MVDCL( ent ); + mvd_client_t *client = EDICT_MVDCL( ent ); usercmd_t *old = &client->lastcmd; pmove_t pm; @@ -1435,7 +1439,7 @@ void MVD_CheckActive( mvd_t *mvd ) { mvd_t *cur; // demo channels are always marked as active - if( mvd->numplayers || mvd->demoplayback ) { + if( mvd->numplayers /*|| mvd->demoplayback*/ ) { if( LIST_EMPTY( &mvd->active ) ) { // sort this one into the list of active channels LIST_FOR_EACH( mvd_t, cur, &mvd_active, active ) { @@ -1456,7 +1460,7 @@ void MVD_CheckActive( mvd_t *mvd ) { } static void MVD_IntermissionStart( mvd_t *mvd ) { - udpClient_t *client; + mvd_client_t *client; // set this early so MVD_SetDefaultLayout works mvd->intermission = qtrue; @@ -1468,7 +1472,7 @@ static void MVD_IntermissionStart( mvd_t *mvd ) { // force all clients to switch to the MVD dummy // and open the scoreboard, unless they had some special layout up - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { if( client->cl->state != cs_spawned ) { continue; } @@ -1481,7 +1485,7 @@ static void MVD_IntermissionStart( mvd_t *mvd ) { } static void MVD_IntermissionStop( mvd_t *mvd ) { - udpClient_t *client; + mvd_client_t *client; mvd_player_t *target; // set this early so MVD_SetDefaultLayout works @@ -1489,7 +1493,7 @@ static void MVD_IntermissionStop( mvd_t *mvd ) { // force all clients to switch to previous mode // and close the scoreboard - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { if( client->cl->state != cs_spawned ) { continue; } @@ -1509,21 +1513,15 @@ static void MVD_IntermissionStop( mvd_t *mvd ) { static void MVD_GameRunFrame( void ) { mvd_t *mvd, *next; - udpClient_t *u; - tcpClient_t *t; - uint16_t length; + mvd_client_t *client; - LIST_FOR_EACH_SAFE( mvd_t, mvd, next, &mvd_ready, ready ) { + LIST_FOR_EACH_SAFE( mvd_t, mvd, next, &mvd_channels, entry ) { if( setjmp( mvd_jmpbuf ) ) { continue; } // parse stream - if( mvd->state < MVD_READING ) { - goto update; - } - - if( !MVD_Parse( mvd ) ) { + if( !mvd->read_frame( mvd ) ) { goto update; } @@ -1544,26 +1542,15 @@ static void MVD_GameRunFrame( void ) { MVD_CheckActive( mvd ); // update UDP clients - LIST_FOR_EACH( udpClient_t, u, &mvd->udpClients, entry ) { - if( u->cl->state == cs_spawned ) { - MVD_UpdateClient( u ); - } - } - - // send this message to TCP clients - length = LittleShort( msg_read.cursize ); - LIST_FOR_EACH( tcpClient_t, t, &mvd->tcpClients, mvdEntry ) { - if( t->state == cs_spawned ) { - SV_HttpWrite( t, &length, 2 ); - SV_HttpWrite( t, msg_read.data, msg_read.cursize ); -#if USE_ZLIB - t->noflush++; -#endif + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { + if( client->cl->state == cs_spawned ) { + MVD_UpdateClient( client ); } } // write this message to demofile if( mvd->demorecording ) { + uint16_t length = LittleShort( msg_read.cursize ); FS_Write( &length, 2, mvd->demorecording ); FS_Write( msg_read.data, msg_read.cursize, mvd->demorecording ); } @@ -1586,7 +1573,7 @@ void MVD_PrepWorldFrame( void ) { int i; // reset events and old origins - LIST_FOR_EACH( mvd_t, mvd, &mvd_ready, ready ) { + LIST_FOR_EACH( mvd_t, mvd, &mvd_channels, entry ) { for( i = 1, ent = &mvd->edicts[1]; i < mvd->pool.num_edicts; i++, ent++ ) { if( !ent->inuse ) { continue; diff --git a/source/mvd_local.h b/source/mvd_local.h index 3dd0d06..55ca6ca 100644 --- a/source/mvd_local.h +++ b/source/mvd_local.h @@ -18,30 +18,16 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#define MVD_DEBUG( s ) do { if( mvd_debug->integer ) \ - Com_Printf( S_COLOR_BLUE "%s: %s", __func__, s ); } while( 0 ) - #define MVD_Malloc( size ) Z_TagMalloc( size, TAG_MVD ) #define MVD_Mallocz( size ) Z_TagMallocz( size, TAG_MVD ) #define MVD_CopyString( s ) Z_TagCopyString( s, TAG_MVD ) -#define EDICT_MVDCL( ent ) (( udpClient_t * )( (ent)->client )) +#define EDICT_MVDCL( ent ) (( mvd_client_t * )( (ent)->client )) #define CS_NUM( c, n ) ( ( char * )(c) + (n) * MAX_QPATH ) // game features MVD client supports #define MVD_FEATURES (GMF_CLIENTNUM|GMF_PROPERINUSE|GMF_WANT_ALL_DISCONNECTS) -typedef enum { - MVD_DEAD, // not active at all - MVD_CONNECTING, // connect() in progress - MVD_CONNECTED, // HTTP request sent - MVD_CHECKING, // got response, checking magic - MVD_PREPARING, // got magic, waiting for gamestate - MVD_WAITING, // stalled, buffering more data - MVD_READING, // actively running - MVD_DISCONNECTED // disconnected, running until EOB -} mvdState_t; - #define LAYOUT_MSEC 3000 typedef enum { @@ -97,48 +83,42 @@ typedef struct { usercmd_t lastcmd; //short delta_angles[3]; int jump_held; -} udpClient_t; +} mvd_client_t; #define MAX_MVD_NAME 16 +typedef enum { + MVD_DEAD, // no gamestate received yet, unusable + MVD_WAITING, // buffering more frames + MVD_READING // reading frames +} mvd_state_t; + +struct gtv_s; + typedef struct mvd_s { list_t entry; - list_t ready; list_t active; - int id; - char name[MAX_MVD_NAME]; + mvd_state_t state; + int id; + char name[MAX_MVD_NAME]; + struct gtv_s *gtv; + qboolean (*read_frame)( struct mvd_s * ); // demo related variables - fileHandle_t demoplayback; fileHandle_t demorecording; - int demoloop; - string_entry_t *demohead, *demoentry; - - // connection variables - mvdState_t state; - int servercount; - int clientNum; - netstream_t stream; - char address[MAX_QPATH]; - char response[MAX_NET_STRING]; - size_t responseLength; - size_t contentLength; - htcoding_t contentCoding; - int statusCode; - char statusText[MAX_QPATH]; + + // delay buffer + fifo_t delay; size_t msglen; -#if USE_ZLIB - z_stream z; -#endif - fifo_t zbuf; + unsigned num_packets, min_packets; + unsigned underflows, overflows; unsigned framenum; - unsigned lastReceived; - unsigned waitTime, waitDelay, waitCount; // game state char gamedir[MAX_QPATH]; char mapname[MAX_QPATH]; + int servercount; int maxclients; edict_pool_t pool; cm_t cm; @@ -150,13 +130,13 @@ typedef struct mvd_s { mvd_player_t *players; // [maxclients] mvd_player_t *dummy; // &players[clientNum] int numplayers; // number of active players in frame + int clientNum; char layout[MAX_STRING_CHARS]; char oldscores[MAX_STRING_CHARS]; // layout is copied here qboolean intermission; - // client lists - list_t udpClients; - list_t tcpClients; + // UDP client list + list_t clients; } mvd_t; @@ -165,28 +145,19 @@ typedef struct mvd_s { // extern list_t mvd_channels; -extern list_t mvd_ready; extern list_t mvd_active; extern mvd_t mvd_waitingRoom; extern qboolean mvd_dirty; extern cvar_t *mvd_shownet; -extern cvar_t *mvd_debug; extern cvar_t *mvd_timeout; extern cvar_t *mvd_chase_msgs; -void MVD_DPrintf( const char *fmt, ... ) q_printf( 1, 2 ); -void MVD_Dropf( mvd_t *mvd, const char *fmt, ... ) - q_noreturn q_printf( 2, 3 ); void MVD_Destroyf( mvd_t *mvd, const char *fmt, ... ) q_noreturn q_printf( 2, 3 ); void MVD_Disconnect( mvd_t *mvd ); void MVD_BeginWaiting( mvd_t *mvd ); -void MVD_ClearState( mvd_t *mvd ); -void MVD_ChangeLevel( mvd_t *mvd ); void MVD_Finish( mvd_t *mvd, const char *reason ) q_noreturn; -void MVD_GetStream( const char *uri ); -void MVD_GetStatus( void ); void MVD_Free( mvd_t *mvd ); void MVD_Shutdown( void ); @@ -194,7 +165,6 @@ mvd_t *MVD_SetChannel( int arg ); void MVD_File_g( genctx_t *ctx ); -void MVD_Connect_f( void ); void MVD_Spawn_f( void ); void MVD_StreamedStop_f( void ); @@ -207,19 +177,19 @@ int MVD_Frame( void ); // mvd_parse.c // -qboolean MVD_Parse( mvd_t *mvd ); +void MVD_ParseMessage( mvd_t *mvd ); void MVD_ParseEntityString( mvd_t *mvd, const char *data ); +void MVD_FreePlayer( mvd_player_t *player ); // // mvd_game.c // -extern udpClient_t *mvd_clients; /* [svs.maxclients] */ -extern game_export_t mvd_ge; +extern mvd_client_t *mvd_clients; // [maxclients] -void MVD_UpdateClient( udpClient_t *client ); -void MVD_SwitchChannel( udpClient_t *client, mvd_t *mvd ); +void MVD_UpdateClient( mvd_client_t *client ); +void MVD_SwitchChannel( mvd_client_t *client, mvd_t *mvd ); void MVD_RemoveClient( client_t *client ); void MVD_BroadcastPrintf( mvd_t *mvd, int level, int mask, const char *fmt, ... ) q_printf( 4, 5 ); diff --git a/source/mvd_parse.c b/source/mvd_parse.c index 4a8a336..4b558cd 100644 --- a/source/mvd_parse.c +++ b/source/mvd_parse.c @@ -194,7 +194,7 @@ void MVD_ParseEntityString( mvd_t *mvd, const char *data ) { } static void MVD_ParseMulticast( mvd_t *mvd, mvd_ops_t op, int extrabits ) { - udpClient_t *client; + mvd_client_t *client; client_t *cl; byte mask[MAX_MAP_VIS]; mleaf_t *leaf1, *leaf2; @@ -242,7 +242,7 @@ static void MVD_ParseMulticast( mvd_t *mvd, mvd_ops_t op, int extrabits ) { } // send the data to all relevent clients - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { cl = client->cl; if( cl->state < cs_primed ) { continue; @@ -271,11 +271,11 @@ static void MVD_ParseMulticast( mvd_t *mvd, mvd_ops_t op, int extrabits ) { static void MVD_UnicastSend( mvd_t *mvd, qboolean reliable, byte *data, size_t length, mvd_player_t *player ) { mvd_player_t *target; - udpClient_t *client; + mvd_client_t *client; client_t *cl; // send to all relevant clients - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { cl = client->cl; if( cl->state < cs_spawned ) { continue; @@ -288,7 +288,7 @@ static void MVD_UnicastSend( mvd_t *mvd, qboolean reliable, byte *data, size_t l } static void MVD_UnicastLayout( mvd_t *mvd, qboolean reliable, mvd_player_t *player ) { - udpClient_t *client; + mvd_client_t *client; if( player != mvd->dummy ) { MSG_ReadString( NULL, 0 ); @@ -298,7 +298,7 @@ static void MVD_UnicastLayout( mvd_t *mvd, qboolean reliable, mvd_player_t *play MSG_ReadString( mvd->layout, sizeof( mvd->layout ) ); // force an update to all relevant clients - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { if( client->cl->state < cs_spawned ) { continue; } @@ -355,7 +355,7 @@ static void MVD_UnicastPrint( mvd_t *mvd, qboolean reliable, mvd_player_t *playe int level; byte *data; size_t readcount, length; - udpClient_t *client; + mvd_client_t *client; client_t *cl; mvd_player_t *target; @@ -368,7 +368,7 @@ static void MVD_UnicastPrint( mvd_t *mvd, qboolean reliable, mvd_player_t *playe length = msg_read.readcount - readcount; // send to all relevant clients - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { cl = client->cl; if( cl->state < cs_spawned ) { continue; @@ -489,7 +489,7 @@ static void MVD_ParseSound( mvd_t *mvd, int extrabits ) { int volume, attenuation, offset, sendchan; int entnum; vec3_t origin; - udpClient_t *client; + mvd_client_t *client; client_t *cl; byte mask[MAX_MAP_VIS]; mleaf_t *leaf; @@ -523,7 +523,7 @@ static void MVD_ParseSound( mvd_t *mvd, int extrabits ) { return; } - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { cl = client->cl; // do not send unreliables to connecting clients @@ -610,6 +610,15 @@ static void MVD_ParseSound( mvd_t *mvd, int extrabits ) { } } +void MVD_FreePlayer( mvd_player_t *player ) { + mvd_cs_t *cs, *next; + + for( cs = player->configstrings; cs; cs = next ) { + next = cs->next; + Z_Free( cs ); + } +} + static void set_player_name( mvd_player_t *player, const char *string ) { char *p; @@ -624,7 +633,7 @@ static void MVD_ParseConfigstring( mvd_t *mvd ) { int index; size_t len, maxlen; char *string; - udpClient_t *client; + mvd_client_t *client; mvd_player_t *player; mvd_cs_t *cs, **pcs; int i; @@ -645,7 +654,7 @@ static void MVD_ParseConfigstring( mvd_t *mvd ) { // update player name player = &mvd->players[ index - CS_PLAYERSKINS ]; set_player_name( player, string ); - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { if( client->cl->state < cs_spawned ) { continue; } @@ -675,7 +684,7 @@ static void MVD_ParseConfigstring( mvd_t *mvd ) { MSG_WriteData( string, len + 1 ); // broadcast configstring change - LIST_FOR_EACH( udpClient_t, client, &mvd->udpClients, entry ) { + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { if( client->cl->state < cs_primed ) { continue; } @@ -889,6 +898,68 @@ static void MVD_ParseFrame( mvd_t *mvd ) { mvd->framenum++; } +static void MVD_ClearState( mvd_t *mvd ) { + mvd_player_t *player; + int i; + + // clear all entities, don't trust num_edicts as it is possible + // to miscount removed entities + memset( mvd->edicts, 0, sizeof( mvd->edicts ) ); + mvd->pool.num_edicts = 0; + + // clear all players + for( i = 0; i < mvd->maxclients; i++ ) { + player = &mvd->players[i]; + MVD_FreePlayer( player ); + memset( player, 0, sizeof( *player ) ); + } + mvd->numplayers = 0; + + // free current map + CM_FreeMap( &mvd->cm ); + + if( mvd->intermission ) { + // save oldscores + //strcpy( mvd->oldscores, mvd->layout ); + } + + memset( mvd->configstrings, 0, sizeof( mvd->configstrings ) ); + mvd->layout[0] = 0; + + mvd->framenum = 0; + // intermission flag will be cleared in MVD_ChangeLevel +} + +static void MVD_ChangeLevel( mvd_t *mvd ) { + mvd_client_t *client; + + if( sv.state != ss_broadcast ) { + MVD_Spawn_f(); // the game is just starting + return; + } + + // cause all UDP clients to reconnect + MSG_WriteByte( svc_stufftext ); + MSG_WriteString( va( "changing map=%s; reconnect\n", mvd->mapname ) ); + + LIST_FOR_EACH( mvd_client_t, client, &mvd->clients, entry ) { + if( mvd->intermission && client->cl->state == cs_spawned ) { + // make them switch to previous target instead of MVD dummy + client->target = client->oldtarget; + client->oldtarget = NULL; + } + SV_ClientReset( client->cl ); + client->cl->spawncount = mvd->servercount; + SV_ClientAddMessage( client->cl, MSG_RELIABLE ); + } + + SZ_Clear( &msg_write ); + + mvd->intermission = qfalse; + + SV_SendAsyncPackets(); +} + static void MVD_ParseServerData( mvd_t *mvd ) { int protocol; size_t len, maxlen; @@ -920,7 +991,7 @@ static void MVD_ParseServerData( mvd_t *mvd ) { mvd->clientNum = MSG_ReadShort(); // change gamedir unless playing a demo - if( !mvd->demoplayback ) { + /*if( !mvd->demoplayback )*/ { Cvar_UserSet( "game", mvd->gamedir ); if( FS_NeedRestart() ) { FS_Restart(); @@ -961,6 +1032,7 @@ static void MVD_ParseServerData( mvd_t *mvd ) { mvd->players = MVD_Mallocz( sizeof( mvd_player_t ) * index ); mvd->maxclients = index; } else if( index != mvd->maxclients ) { + // TODO: allow this! MVD_Destroyf( mvd, "Unexpected maxclients change" ); } @@ -1005,18 +1077,17 @@ static void MVD_ParseServerData( mvd_t *mvd ) { MVD_ParseFrame( mvd ); // if the channel has been just created, init some things - if( mvd->state < MVD_WAITING ) { + if( !mvd->state ) { mvd_t *cur; // sort this one into the list of ready channels - LIST_FOR_EACH( mvd_t, cur, &mvd_ready, ready ) { + LIST_FOR_EACH( mvd_t, cur, &mvd_channels, entry ) { if( cur->id > mvd->id ) { break; } } - List_Append( &cur->ready, &mvd->ready ); - mvd->state = mvd->demoplayback ? MVD_READING : MVD_WAITING; - mvd->waitTime = svs.realtime; + List_Append( &cur->entry, &mvd->entry ); + mvd->state = MVD_WAITING; // for local client MVD_CheckActive( mvd ); @@ -1026,54 +1097,9 @@ static void MVD_ParseServerData( mvd_t *mvd ) { MVD_ChangeLevel( mvd ); } -static qboolean MVD_ParseMessage( mvd_t *mvd, fifo_t *fifo ) { - uint16_t msglen; - uint32_t magic; - void *data; - size_t length; +void MVD_ParseMessage( mvd_t *mvd ) { int cmd, extrabits; - // parse magic - if( mvd->state == MVD_CHECKING ) { - if( !FIFO_TryRead( fifo, &magic, 4 ) ) { - return qfalse; - } - if( magic != MVD_MAGIC ) { - MVD_Destroyf( mvd, "Not a MVD stream" ); - } - mvd->state = MVD_PREPARING; - } - - // parse msglen - if( !mvd->msglen ) { - if( !FIFO_TryRead( fifo, &msglen, 2 ) ) { - return qfalse; - } - if( !msglen ) { - MVD_Finish( mvd, "End of MVD stream reached" ); - } - msglen = LittleShort( msglen ); - if( msglen > MAX_MSGLEN ) { - MVD_Destroyf( mvd, "Invalid MVD message length: %u bytes", msglen ); - } - mvd->msglen = msglen; - } - - // first, try to read in a single block - data = FIFO_Peek( fifo, &length ); - if( length < mvd->msglen ) { - if( !FIFO_TryRead( fifo, msg_read_buffer, mvd->msglen ) ) { - return qfalse; // not yet available - } - SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) ); - } else { - SZ_Init( &msg_read, data, mvd->msglen ); - FIFO_Decommit( fifo, mvd->msglen ); - } - - msg_read.cursize = mvd->msglen; - mvd->msglen = 0; - if( mvd_shownet->integer == 1 ) { Com_Printf( "%"PRIz" ", msg_read.cursize ); } else if( mvd_shownet->integer > 1 ) { @@ -1087,21 +1113,21 @@ static qboolean MVD_ParseMessage( mvd_t *mvd, fifo_t *fifo ) { if( msg_read.readcount > msg_read.cursize ) { MVD_Destroyf( mvd, "Read past end of message" ); } - - if( ( cmd = MSG_ReadByte() ) == -1 ) { + if( msg_read.readcount == msg_read.cursize ) { if( mvd_shownet->integer > 1 ) { Com_Printf( "%3"PRIz":END OF MESSAGE\n", msg_read.readcount - 1 ); } break; } + cmd = MSG_ReadByte(); extrabits = cmd >> SVCMD_BITS; cmd &= SVCMD_MASK; if( mvd_shownet->integer > 1 ) { MVD_ShowSVC( cmd ); } - + switch( cmd ) { case mvd_serverdata: MVD_ParseServerData( mvd ); @@ -1124,10 +1150,6 @@ static qboolean MVD_ParseMessage( mvd_t *mvd, fifo_t *fifo ) { case mvd_frame: MVD_ParseFrame( mvd ); break; - //case mvd_frame_nodelta: - //MVD_ResetFrame( mvd ); - //MVD_ParseFrame( mvd ); - //break; case mvd_sound: MVD_ParseSound( mvd, extrabits ); break; @@ -1139,81 +1161,5 @@ static qboolean MVD_ParseMessage( mvd_t *mvd, fifo_t *fifo ) { msg_read.readcount - 1, cmd ); } } - - return qtrue; -} - -#if USE_ZLIB -static int MVD_Decompress( mvd_t *mvd ) { - byte *data; - size_t avail_in, avail_out; - z_streamp z = &mvd->z; - int ret = Z_BUF_ERROR; - - do { - data = FIFO_Peek( &mvd->stream.recv, &avail_in ); - if( !avail_in ) { - break; - } - z->next_in = data; - z->avail_in = ( uInt )avail_in; - - data = FIFO_Reserve( &mvd->zbuf, &avail_out ); - if( !avail_out ) { - break; - } - z->next_out = data; - z->avail_out = ( uInt )avail_out; - - ret = inflate( z, Z_SYNC_FLUSH ); - - FIFO_Decommit( &mvd->stream.recv, avail_in - z->avail_in ); - FIFO_Commit( &mvd->zbuf, avail_out - z->avail_out ); - } while( ret == Z_OK ); - - return ret; -} -#endif - -qboolean MVD_Parse( mvd_t *mvd ) { - fifo_t *fifo; - -#if USE_ZLIB - if( mvd->z.state ) { - int ret = MVD_Decompress( mvd ); - - switch( ret ) { - case Z_BUF_ERROR: - case Z_OK: - break; - case Z_STREAM_END: - Com_DPrintf( "End of zlib stream reached\n" ); - inflateEnd( &mvd->z ); - break; - default: - MVD_Destroyf( mvd, "inflate() failed: %s", mvd->z.msg ); - } - } - if( mvd->zbuf.data ) { - fifo = &mvd->zbuf; - } else -#endif - { - fifo = &mvd->stream.recv; - } - - if( MVD_ParseMessage( mvd, fifo ) ) { - return qtrue; - } - - // ran out of buffers - if( mvd->state == MVD_DISCONNECTED ) { - MVD_Finish( mvd, "MVD stream was truncated" ); - } - if( mvd->state == MVD_READING ) { - Com_Printf( "[%s] Buffering data...\n", mvd->name ); - MVD_BeginWaiting( mvd ); - } - return qfalse; } diff --git a/source/net_common.c b/source/net_common.c index c522d10..3d225d8 100644 --- a/source/net_common.c +++ b/source/net_common.c @@ -923,15 +923,67 @@ neterr_t NET_Connect( const netadr_t *peer, netstream_t *s ) { return NET_OK; } -neterr_t NET_Run( netstream_t *s ) { +neterr_t NET_RunConnect( netstream_t *s ) { struct timeval tv; - fd_set rfd, wfd, efd; + fd_set fd; + socklen_t len; int ret, err; - size_t length; + + if( s->state != NS_CONNECTING ) { + return NET_AGAIN; + } + + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO( &fd ); + FD_SET( s->socket, &fd ); + + ret = select( s->socket + 1, NULL, +#ifdef _WIN32 + NULL, &fd, +#else + &fd, NULL, +#endif + &tv ); + if( ret == -1 ) { + goto error1; + } + if( !ret ) { + return NET_AGAIN; + } + if( !FD_ISSET( s->socket, &fd ) ) { + return NET_AGAIN; + } + + len = sizeof( err ); + ret = getsockopt( s->socket, SOL_SOCKET, SO_ERROR, ( char * )&err, &len ); + if( ret == -1 ) { + goto error1; + } + if( err ) { + net_error = err; + goto error2; + } + + s->state = NS_CONNECTED; + return NET_OK; + +error1: + NET_GET_ERROR(); +error2: + s->state = NS_BROKEN; + return NET_ERROR; +} + +neterr_t NET_RunStream( netstream_t *s ) { + struct timeval tv; + fd_set rfd, wfd; + int ret; + size_t len; byte *data; neterr_t result = NET_AGAIN; - if( s->state < NS_CONNECTING || s->state > NS_CONNECTED ) { + if( s->state != NS_CONNECTED ) { return result; } @@ -941,9 +993,7 @@ neterr_t NET_Run( netstream_t *s ) { FD_SET( s->socket, &rfd ); FD_ZERO( &wfd ); FD_SET( s->socket, &wfd ); - FD_ZERO( &efd ); - FD_SET( s->socket, &efd ); - ret = select( s->socket + 1, &rfd, &wfd, &efd, &tv ); + ret = select( s->socket + 1, &rfd, &wfd, NULL, &tv ); if( ret == -1 ) { goto error; } @@ -952,36 +1002,11 @@ neterr_t NET_Run( netstream_t *s ) { return result; } - result = NET_AGAIN; - if( s->state == NS_CONNECTING ) { - socklen_t length; - - if( !FD_ISSET( s->socket, &wfd ) && !FD_ISSET( s->socket, &efd ) ) { - return result; - } - - length = sizeof( err ); - ret = getsockopt( s->socket, SOL_SOCKET, SO_ERROR, - ( char * )&err, &length ); - if( ret == -1 ) { - goto error; - } - - if( err ) { - net_error = err; - s->state = NS_BROKEN; - return NET_ERROR; - } - - s->state = NS_CONNECTED; - result = NET_OK; - } - if( FD_ISSET( s->socket, &rfd ) ) { // read as much as we can - data = FIFO_Reserve( &s->recv, &length ); - if( length ) { - ret = recv( s->socket, data, length, 0 ); + data = FIFO_Reserve( &s->recv, &len ); + if( len ) { + ret = recv( s->socket, data, len, 0 ); if( !ret ) { goto closed; } @@ -1002,9 +1027,9 @@ neterr_t NET_Run( netstream_t *s ) { if( FD_ISSET( s->socket, &wfd ) ) { // write as much as we can - data = FIFO_Peek( &s->send, &length ); - if( length ) { - ret = send( s->socket, data, length, 0 ); + data = FIFO_Peek( &s->send, &len ); + if( len ) { + ret = send( s->socket, data, len, 0 ); if( !ret ) { goto closed; } diff --git a/source/net_stream.h b/source/net_stream.h index 32f5acd..97ee2aa 100644 --- a/source/net_stream.h +++ b/source/net_stream.h @@ -38,5 +38,6 @@ void NET_Close( netstream_t *s ); neterr_t NET_Listen( qboolean listen ); neterr_t NET_Accept( netadr_t *peer, netstream_t *s ); neterr_t NET_Connect( const netadr_t *peer, netstream_t *s ); -neterr_t NET_Run( netstream_t *s ); +neterr_t NET_RunConnect( netstream_t *s ); +neterr_t NET_RunStream( netstream_t *s ); diff --git a/source/q_fifo.h b/source/q_fifo.h index 9d70bce..3bb5124 100644 --- a/source/q_fifo.h +++ b/source/q_fifo.h @@ -28,21 +28,21 @@ typedef struct { size_t ax, ay, bs; } fifo_t; -static inline void *FIFO_Reserve( fifo_t *fifo, size_t *reserved ) { +static inline void *FIFO_Reserve( fifo_t *fifo, size_t *len ) { size_t tail; if( fifo->bs ) { - *reserved = fifo->ax - fifo->bs; + *len = fifo->ax - fifo->bs; return fifo->data + fifo->bs; } tail = fifo->size - fifo->ay; - if( fifo->ax < tail ) { - *reserved = tail; + if( tail ) { + *len = tail; return fifo->data + fifo->ay; } - *reserved = fifo->ax; + *len = fifo->ax; return fifo->data; } @@ -55,7 +55,7 @@ static inline void FIFO_Commit( fifo_t *fifo, size_t len ) { } tail = fifo->size - fifo->ay; - if( fifo->ax < tail ) { + if( tail ) { fifo->ay += len; return; } @@ -112,3 +112,5 @@ static inline qboolean FIFO_TryWrite( fifo_t *fifo, void *buffer, size_t len ) { return qtrue; } +qboolean FIFO_ReadMessage( fifo_t *fifo, size_t msglen ); + diff --git a/source/q_shared.c b/source/q_shared.c index 3d50803..dce4a17 100644 --- a/source/q_shared.c +++ b/source/q_shared.c @@ -1038,203 +1038,6 @@ size_t Q_UnescapeString( char *out, const char *in, size_t bufsize, escape_t fla } #endif -int Q_EscapeMarkup( char *out, const char *in, int bufsize ) { - char *p, *m, *s; - int c; - size_t l; - - if( bufsize < 1 ) { - Com_Error( ERR_FATAL, "%s: bad bufsize: %d", __func__, bufsize ); - } - - p = out; - m = out + bufsize - 1; - while( *in && p < m ) { - c = *in++; - c &= 0x7f; - - switch( c ) { - case '<': s = "<"; break; - case '>': s = ">"; break; - case '&': s = "&"; break; - case '"': s = """; break; - case 0x7f: - continue; - default: - if( c < 0x20 ) { - continue; - } - case '\t': - case '\r': - case '\n': - *p++ = c; - continue; - } - - l = strlen( s ); - if( p + l > m ) { - break; - } - - memcpy( p, s, l ); - p += l; - } - - *p = 0; - - return p - out; -} - -int Q_EscapeURI( char *out, const char *in, int bufsize ) { - char *p, *m; - int c; - - if( bufsize < 1 ) { - Com_Error( ERR_FATAL, "%s: bad bufsize: %d", __func__, bufsize ); - } - - p = out; - m = out + bufsize - 1; - while( *in && p < m ) { - c = *in++; - c &= 0x7f; - - if( Q_isalnum( c ) || c == '-' || c == '_' || c == '.' || - c == '!' || c == '~' || c == '*' || c == '(' || c == ')' ) - { - *p++ = c; - continue; - } - - if( p + 3 > m ) { - break; - } - p[0] = '%'; - p[1] = hexchars[c >> 4]; - p[2] = hexchars[c & 15]; - p += 3; - } - - *p = 0; - - return p - out; -} - -#define B64( c ) ( Q_isupper( c ) ? ( (c) - 'A' ) : \ - Q_islower( c ) ? ( (c) - 'a' + 26 ) : \ - Q_isdigit( c ) ? ( (c) - '0' + 52 ) : \ - (c) == '+' ? 62 : (c) == '/' ? 63 : -1 ) - -int Q_Decode64( char *out, const char *in, int bufsize ) { - char *p, *m; - int c1, c2; - - if( bufsize < 1 ) { - Com_Error( ERR_FATAL, "%s: bad bufsize: %d", __func__, bufsize ); - } - - p = out; - m = out + bufsize - 1; - while( *in ) { - c1 = B64( in[0] ); - c2 = B64( in[1] ); - if( c1 == -1 || c2 == -1 ) { - return -1; - } - if( p < m ) { - *p++ = ( c1 << 2 ) | ( c2 >> 4 ); - } - - if( in[2] == 0 ) { - return -1; - } - if( in[2] == '=' ) { - if( in[3] != '=' ) { - return -1; - } - break; - } - c1 = B64( in[2] ); - if( c1 == -1 ) { - return -1; - } - if( p < m ) { - *p++ = ( c1 >> 2 ) | ( ( c2 << 4 ) & 0xF0 ); - } - - if( in[3] == '=' ) { - if( in[4] != 0 ) { - return -1; - } - break; - } - c2 = B64( in[3] ); - if( c2 == -1 ) { - return -1; - } - if( p < m ) { - *p++ = c2 | ( ( c1 << 6 ) & 0xC0 ); - } - - in += 4; - } - - *p = 0; - - return p - out; -} - -#undef B64 - - -#define B64( c ) ( (c) < 26 ? ( (c) + 'A' ) : \ - (c) < 52 ? ( (c) + 'a' - 26 ) : \ - (c) < 62 ? ( (c) + '0' - 52 ) : \ - (c) == 62 ? '+' : (c) == 63 ? '/' : -1 ) - -int Q_Encode64( char *out, const char *in, int bufsize ) { - char *p, *m; - int c1, c2; - - if( bufsize < 1 ) { - Com_Error( ERR_FATAL, "%s: bad bufsize: %d", __func__, bufsize ); - } - - p = out; - m = out + bufsize - 1; - while( *in && p + 4 < m ) { - c1 = in[0]; - c2 = in[1]; - p[0] = B64( c1 >> 2 ); - p[1] = B64( ( ( c1 << 4 ) & 0x30 ) | ( c2 >> 4 ) ); - if( c2 ) { - c1 = in[2]; - p[2] = B64( ( c1 >> 6 ) | ( ( c2 << 2 ) & 0x3C ) ); - if( c1 ) { - p[3] = B64( c1 & 0x3F ); - } else { - p[3] = '='; - p += 4; - break; - } - } else { - p[2] = '='; - p[3] = '='; - p += 4; - break; - } - p += 4; - in += 3; - } - - *p = 0; - - return p - out; -} - -#undef B64 - - /* ================ Com_LocalTime diff --git a/source/q_shared.h b/source/q_shared.h index 24c0cc8..22b3783 100644 --- a/source/q_shared.h +++ b/source/q_shared.h @@ -397,10 +397,6 @@ char *Q_UnescapeString( const char *string ); int Q_ClearColorStr( char *out, const char *in, int bufsize ); int Q_ClearStr( char *out, const char *in, int bufsize ); int Q_HighlightStr( char *out, const char *in, int bufsize ); -int Q_EscapeMarkup( char *out, const char *in, int bufsize ); -int Q_EscapeURI( char *out, const char *in, int bufsize ); -int Q_Decode64( char *out, const char *in, int bufsize ); -int Q_Encode64( char *out, const char *in, int bufsize ); //============================================= diff --git a/source/sv_ac.c b/source/sv_ac.c index 1d919f5..4de9da4 100644 --- a/source/sv_ac.c +++ b/source/sv_ac.c @@ -92,7 +92,7 @@ typedef struct { qboolean ready; qboolean ping_pending; unsigned last_ping; - netstream_t stream; + netstream_t stream; size_t msglen; } ac_locals_t; @@ -134,8 +134,8 @@ static ac_static_t acs; static LIST_DECL( ac_required_list ); static LIST_DECL( ac_exempt_list ); -static byte ac_send_buffer[AC_SEND_SIZE]; -static byte ac_recv_buffer[AC_RECV_SIZE]; +static byte ac_send_buffer[AC_SEND_SIZE]; +static byte ac_recv_buffer[AC_RECV_SIZE]; static cvar_t *ac_required; static cvar_t *ac_server_address; @@ -840,8 +840,6 @@ static void AC_ParseError( void ) { static qboolean AC_ParseMessage( void ) { uint16_t msglen; - byte *data; - size_t length; int cmd; // parse msglen @@ -861,19 +859,11 @@ static qboolean AC_ParseMessage( void ) { ac.msglen = msglen; } - // first, try to read in a single block - data = FIFO_Peek( &ac.stream.recv, &length ); - if( length < ac.msglen ) { - if( !FIFO_TryRead( &ac.stream.recv, msg_read_buffer, ac.msglen ) ) { - return qfalse; // not yet available - } - SZ_Init( &msg_read, msg_read_buffer, sizeof( msg_read_buffer ) ); - } else { - SZ_Init( &msg_read, data, ac.msglen ); - FIFO_Decommit( &ac.stream.recv, ac.msglen ); + // read this message + if( !FIFO_ReadMessage( &ac.stream.recv, ac.msglen ) ) { + return qfalse; } - msg_read.cursize = ac.msglen; ac.msglen = 0; cmd = MSG_ReadByte(); @@ -1305,7 +1295,7 @@ fail: void AC_Run( void ) { - neterr_t ret; + neterr_t ret = NET_AGAIN; time_t clock; if( acs.retry_time ) { @@ -1321,28 +1311,32 @@ void AC_Run( void ) { return; } - ret = NET_Run( &ac.stream ); - switch( ret ) { - case NET_AGAIN: - if( ac.connected ) { - AC_CheckTimeouts(); + if( !ac.connected ) { + ret = NET_RunConnect( &ac.stream ); + if( ret == NET_OK ) { + Com_Printf( "ANTICHEAT: Connected to anticheat server!\n" ); + ac.connected = qtrue; + AC_SendHello(); } - break; + } + + if( ac.connected ) { + ret = NET_RunStream( &ac.stream ); + if( ret == NET_OK ) { + while( AC_ParseMessage() ) + ; + } + AC_CheckTimeouts(); + } + + switch( ret ) { case NET_ERROR: Com_EPrintf( "ANTICHEAT: %s to %s.\n", NET_ErrorString(), NET_AdrToString( &ac.stream.address ) ); case NET_CLOSED: AC_Drop(); break; - case NET_OK: - if( !ac.connected ) { - Com_Printf( "ANTICHEAT: Connected to anticheat server!\n" ); - ac.connected = qtrue; - AC_SendHello(); - } - while( AC_ParseMessage() ) - ; - AC_CheckTimeouts(); + default: break; } } diff --git a/source/sv_ccmds.c b/source/sv_ccmds.c index fd0550f..a4d4935 100644 --- a/source/sv_ccmds.c +++ b/source/sv_ccmds.c @@ -296,7 +296,7 @@ void SV_Kick_f( void ) { sv_player = NULL; } -static void SV_DumpUdpClients( void ) { +static void SV_DumpClients( void ) { client_t *client; Com_Printf( @@ -304,9 +304,12 @@ static void SV_DumpUdpClients( void ) { "--- ----- ---- ---------------- ------- --------------------- ----- -----\n" ); FOR_EACH_CLIENT( client ) { Com_Printf( "%3i ", client->number ); +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { Com_Printf( " " ); - } else { + } else +#endif + { Com_Printf( "%5i ", client->edict->client->ps.stats[STAT_FRAGS] ); } @@ -339,7 +342,7 @@ static void SV_DumpUdpClients( void ) { } -static void SV_DumpUdpVersions( void ) { +static void SV_DumpVersions( void ) { client_t *client; Com_Printf( @@ -353,58 +356,6 @@ static void SV_DumpUdpVersions( void ) { } } -static void SV_DumpTcpClients( void ) { - tcpClient_t *client; - int count; - - Com_Printf( -"num resource buf lastmsg address state\n" -"--- -------------------- --- ------- --------------------- -----\n" ); - count = 0; - LIST_FOR_EACH( tcpClient_t, client, &svs.tcp_client_list, entry ) { - Com_Printf( "%3d %-20.20s %3"PRIz" %7u %-21s ", - count, client->resource ? client->resource : "", - FIFO_Usage( &client->stream.send ), - svs.realtime - client->lastmessage, - NET_AdrToString( &client->stream.address ) ); - - switch( client->state ) { - case cs_zombie: - Com_Printf( "ZMBI " ); - break; - case cs_assigned: - Com_Printf( "ASGN " ); - break; - case cs_connected: - Com_Printf( "CNCT " ); - break; - default: - Com_Printf( "SEND " ); - break; - } - Com_Printf( "\n" ); - - count++; - } -} - -static void SV_DumpTcpVersions( void ) { - tcpClient_t *client; - int count; - - Com_Printf( -"num address user-agent\n" -"--- --------------------- -----------------------------------------\n" ); - - count = 0; - LIST_FOR_EACH( tcpClient_t, client, &svs.tcp_client_list, entry ) { - Com_Printf( "%3i %-21s %-40.40s\n", - count, NET_AdrToString( &client->stream.address ), - client->agent ? client->agent : "" ); - count++; - } -} - /* ================ @@ -425,23 +376,16 @@ static void SV_Status_f( void ) { Com_Printf( "No UDP clients.\n" ); } else { if( Cmd_Argc() > 1 ) { - SV_DumpUdpVersions(); + SV_DumpVersions(); } else { - SV_DumpUdpClients(); + SV_DumpClients(); } } Com_Printf( "\n" ); - if( LIST_EMPTY( &svs.tcp_client_list ) ) { - Com_Printf( "No TCP clients.\n" ); - } else { - if( Cmd_Argc() > 1 ) { - SV_DumpTcpVersions(); - } else { - SV_DumpTcpClients(); - } - } - Com_Printf( "\n" ); +#if USE_MVD_SERVER + SV_MvdStatus_f(); +#endif } /* diff --git a/source/sv_game.c b/source/sv_game.c index 9051e7a..fe211a9 100644 --- a/source/sv_game.c +++ b/source/sv_game.c @@ -99,10 +99,13 @@ static void PF_Unicast( edict_t *ent, qboolean reliable ) { if( msg_write.data[0] == svc_disconnect ) { // fix anti-kicking exploit for broken mods client->flags |= CF_DROP; - } else { - SV_MvdUnicast( ent, clientNum, reliable ); + goto clear; } +#if USE_MVD_SERVER + SV_MvdUnicast( ent, clientNum, reliable ); +#endif + clear: SZ_Clear( &msg_write ); } @@ -131,7 +134,9 @@ static void PF_bprintf( int level, const char *fmt, ... ) { return; } +#if USE_MVD_SERVER SV_MvdBroadcastPrint( level, string ); +#endif MSG_WriteByte( svc_print ); MSG_WriteByte( level ); @@ -224,7 +229,9 @@ static void PF_cprintf( edict_t *ent, int level, const char *fmt, ... ) { SV_ClientAddMessage( client, MSG_RELIABLE ); } +#if USE_MVD_SERVER SV_MvdUnicast( ent, clientNum, qtrue ); +#endif SZ_Clear( &msg_write ); } @@ -354,7 +361,9 @@ void PF_Configstring( int index, const char *val ) { return; } +#if USE_MVD_SERVER SV_MvdConfigstring( index, val ); +#endif // send the update to everyone MSG_WriteByte( svc_configstring ); @@ -586,8 +595,10 @@ static void PF_StartSound( edict_t *edict, int channel, client->msg_bytes += MAX_SOUND_PACKET; } +#if USE_MVD_SERVER SV_MvdStartSound( ent, channel, flags, soundindex, volume * 255, attenuation * 64, timeofs * 1000 ); +#endif } static void PF_PositionedSound( vec3_t origin, edict_t *entity, int channel, diff --git a/source/sv_http.c b/source/sv_http.c deleted file mode 100644 index 58fb210..0000000 --- a/source/sv_http.c +++ /dev/null @@ -1,820 +0,0 @@ -/* -Copyright (C) 2003-2006 Andrey Nazarov - -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. - -*/ - -#include "sv_local.h" -#include "mvd_local.h" - -char http_host[MAX_STRING_CHARS]; -char http_header[MAX_STRING_CHARS]; -tcpClient_t *http_client; - -void SV_HttpHeader( const char *title ) { - SV_HttpPrintf( - "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 3.2//EN\">" - "<html><head><title>%s</title></head><body>", title ); -} - -void SV_HttpFooter( void ) { - SV_HttpPrintf( "<hr><address>" APPLICATION " " - VERSION " at <a href=\"http://%s/\">%s</a></address></body></html>", - http_host, http_host ); -} - -void SV_HttpReject( const char *error, const char *reason ) { - if( http_client->state <= cs_zombie ) { - return; - } - - // construct HTTP response header - SV_HttpPrintf( "HTTP/1.0 %s\r\n", error ); - if( http_header[0] ) { - SV_HttpPrintf( "%s", http_header ); - } - if( reason && http_client->method != HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "Content-Type: text/html; charset=us-ascii\r\n" ); - } - SV_HttpPrintf( "\r\n" ); - - // add optional response body - if( reason && http_client->method != HTTP_METHOD_HEAD ) { - SV_HttpHeader( error ); - SV_HttpPrintf( "<h1>%s</h1><p>%s</p>", error, reason ); - SV_HttpFooter(); - } - - SV_HttpDrop( http_client, error ); -} - -static void SV_HttpPrintTime( int sec ) { - int min, hour, day; - - min = sec / 60; sec %= 60; - hour = min / 60; min %= 60; - day = hour / 24; hour %= 24; - - if( day ) { - SV_HttpPrintf( - "%d day%s, %d hour%s, %d min%s", - day, day == 1 ? "" : "s", - hour, hour == 1 ? "" : "s", - min, min == 1 ? "" : "s" ); - } else if( hour ) { - SV_HttpPrintf( - "%d hour%s, %d min%s", - hour, hour == 1 ? "" : "s", - min, min == 1 ? "" : "s" ); - } else if( min ) { - SV_HttpPrintf( - "%d min%s", - min, min == 1 ? "" : "s" ); - } else { - SV_HttpPrintf( "< 1 min" ); - } -} - -static void SV_GetStatus( void ) { - char buffer[MAX_STRING_CHARS]; - cvar_t *var; - client_t *cl; - int count, len, sec; - time_t clock; - - if( sv_status_show->integer < 1 ) { - SV_HttpReject( "403 Forbidden", - "You do not have permission to view " - "the status page of this server." ); - return; - } - - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" ); - - if( http_client->method == HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "\r\n" ); - SV_HttpDrop( http_client, "200 OK" ); - return; - } - - SV_HttpPrintf( - "Content-Type: text/html; charset=us-ascii\r\n" - "\r\n" ); - - count = SV_CountClients(); - len = Q_EscapeMarkup( buffer, sv_hostname->string, sizeof( buffer ) ); - Q_snprintf( buffer + len, sizeof( buffer ) - len, " - %d/%d", - count, sv_maxclients->integer - sv_reserved_slots->integer ); - - SV_HttpHeader( buffer ); - - buffer[len] = 0; - SV_HttpPrintf( "<h1>%s</h1>", buffer ); - - time( &clock ); - - if( sv_status_show->integer > 1 ) { - SV_HttpPrintf( "<h2>Player Info</h2>" ); - if( count ) { - SV_HttpPrintf( "<table border=\"1\">" - "<tr><th>Score</th><th>Ping</th><th>Time</th><th>Name</th></tr>" ); - FOR_EACH_CLIENT( cl ) { - if( cl->state < cs_connected ) { - continue; - } - - SV_HttpPrintf( "<tr><td>%d</td><td>%d</td><td>", - cl->edict->client->ps.stats[STAT_FRAGS], cl->ping ); - - if( cl->connect_time > clock ) { - cl->connect_time = clock; - } - sec = clock - cl->connect_time; - - SV_HttpPrintTime( sec ); - - Q_EscapeMarkup( buffer, cl->name, sizeof( buffer ) ); - SV_HttpPrintf( "</td><td>%s</td></tr>", buffer ); - } - SV_HttpPrintf( "</table>" ); - } else { - SV_HttpPrintf( "<p>No players.</p>" ); - } - } - - SV_HttpPrintf( - "<h2>Server Info</h2><table border=\"1\"><tr>" - "<th>Key</th><th>Value</th></tr>" ); - - for( var = cvar_vars; var; var = var->next ) { - if( !( var->flags & CVAR_SERVERINFO ) ) { - continue; - } - if( !var->string[0] ) { - continue; - } - - Q_EscapeMarkup( buffer, var->name, sizeof( buffer ) ); - SV_HttpPrintf( "<tr><td>%s</td>", buffer ); - - // XXX: ugly hack to hide reserved slots - if( var == sv_maxclients && sv_reserved_slots->integer ) { - SV_HttpPrintf( "<td>%d</td></tr>", - sv_maxclients->integer - sv_reserved_slots->integer ); - } else { - Q_EscapeMarkup( buffer, var->string, sizeof( buffer ) ); - SV_HttpPrintf( "<td>%s</td></tr>", buffer ); - } - } - - // add uptime - if( sv_uptime->integer ) { - if( com_startTime > clock ) { - com_startTime = clock; - } - sec = clock - com_startTime; - - SV_HttpPrintf( "<tr><td>uptime</td><td>" ); - SV_HttpPrintTime( sec ); - SV_HttpPrintf( "</td></tr>" ); - } - - SV_HttpPrintf( "</table>" - "<p><a href=\"quake2://%s\">Join this server</a></p>", http_host ); - if( sv_mvd_enable->integer ) { - SV_HttpPrintf( - "<p><a href=\"http://%s/mvdstream\">Download MVD stream</a></p>", - http_host ); - } - SV_HttpFooter(); - - SV_HttpDrop( http_client, "200 OK" ); -} - -static void uri_status( const char *uri ) { - if( sv.state == ss_game ) { - SV_GetStatus(); - } else { - MVD_GetStatus(); - } -} - -static void uri_mvdstream( const char *uri ) { - if( !sv_mvd_enable->integer ) { - SV_HttpReject( "403 Forbidden", - "You do not have permission to access " - "live MVD stream on this server." ); - return; - } - - if( sv_mvd_auth->string[0] && ( http_client->credentials == NULL || - strcmp( http_client->credentials, sv_mvd_auth->string ) ) ) - { - strcpy( http_header, - "WWW-Authenticate: Basic realm=\"mvdstream\"\r\n" ); - SV_HttpReject( "401 Not Authorized", - "You are not authorized to access " - "live MVD stream on this server." ); - return; - } - - if( sv.state == ss_game ) { - SV_MvdGetStream( uri ); - } else { - MVD_GetStream( uri ); - } -} - -void SV_ConsoleOutput( const char *msg ) { - tcpClient_t *client; - char text[MAXPRINTMSG]; - char *p, *maxp; - size_t len; - int c; - - if( !svs.initialized ) { - return; - } - if( LIST_EMPTY( &svs.console_list ) ) { - return; - } - - p = text; - maxp = text + sizeof( text ) - 1; - while( *msg ) { - if( Q_IsColorString( msg ) ) { - msg += 2; - continue; - } - - if( p == maxp ) { - break; - } - - c = *msg++; - c &= 127; - - *p++ = c; - } - *p = 0; - - len = p - text; - - LIST_FOR_EACH( tcpClient_t, client, &svs.console_list, mvdEntry ) { - if( FIFO_Write( &client->stream.send, text, len ) != len ) { - SV_HttpDrop( client, "overflowed" ); - } - } -} - -static void uri_console( const char *uri ) { - char *auth = sv_console_auth->string; - char *cred = http_client->credentials; - - if( !auth[0] || !cred || strcmp( cred, auth ) ) { - strcpy( http_header, - "WWW-Authenticate: Basic realm=\"console\"\r\n" ); - SV_HttpReject( "401 Not Authorized", - "You are not authorized to access " - "console stream on this server." ); - return; - } - - if( http_client->method == HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n\r\n" ); - SV_HttpDrop( http_client, "200 OK " ); - return; - } - - SV_HttpPrintf( - "HTTP/1.0 200 OK\r\n" - "Content-Type: text/plain\r\n" - "\r\n" ); - List_Append( &svs.console_list, &http_client->mvdEntry ); - http_client->state = cs_spawned; -} - -#if 0 -static void uri_root( const char *uri ) { - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" ); - - if( http_client->method == HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "\r\n" ); - SV_HttpDrop( http_client, "200 OK " ); - return; - } - - SV_HttpPrintf( - "Content-Type: text/html\r\n" - "\r\n" ); - SV_HttpPrintf( - "<html><head><title>Hello</title></head><body>" - "<h1>Hello</h1><p>Hello world!</p>" ); - SV_HttpFooter(); - - SV_HttpDrop( http_client, "200 OK" ); -} -#endif - -typedef struct { - const char *uri; - void (*handler)( const char * ); -} uriEntry_t; - -static const uriEntry_t rootURIs[] = { - { "", uri_status }, - { "status", uri_status }, - { "mvdstream", uri_mvdstream }, - { "console", uri_console }, - { NULL } -}; - -void SV_HttpHandle( const uriEntry_t *e, const char *uri ) { - const char *p; - size_t length; - - if( *uri == '/' ) { - uri++; - } - - if( ( p = strchr( uri, '/' ) ) != NULL ) { - length = p - uri; - } else { - length = strlen( uri ); - p = uri + length; - } - - for( ; e->uri; e++ ) { - if( !strncmp( e->uri, uri, length ) ) { - e->handler( p ); - break; - } - } - - if( !e->uri ) { - SV_HttpReject( "404 Not Found", - "The requested URL was not found on this server." ); - } -} - -void SV_HttpWrite( tcpClient_t *client, void *data, size_t len ) { - fifo_t *fifo = &client->stream.send; - - if( client->state <= cs_zombie ) { - return; - } - -#if USE_ZLIB - if( client->z.state ) { - z_streamp z = &client->z; - int param = Z_NO_FLUSH; - - if( client->noflush > 120 ) { - param = Z_SYNC_FLUSH; - } - - z->next_in = data; - z->avail_in = ( uInt )len; - - while( z->avail_in ) { - data = FIFO_Reserve( fifo, &len ); - if( !len ) { - SV_HttpDrop( client, "overflowed" ); - return; - } - - z->next_out = data; - z->avail_out = ( uInt )len; - - if( deflate( z, param ) != Z_OK ) { - SV_HttpDrop( client, "deflate failed" ); - return; - } - - len -= z->avail_out; - if( len > 0 ) { - FIFO_Commit( fifo, len ); - client->noflush = 0; - } - } - return; - } -#endif - - if( !FIFO_TryWrite( fifo, data, len ) ) { - SV_HttpDrop( client, "overflowed" ); - } -} - -void SV_HttpFinish( tcpClient_t *client ) { -#if USE_ZLIB - fifo_t *fifo = &client->stream.send; - z_streamp z = &client->z; - byte *data; - size_t len; - int ret; - - if( client->state <= cs_zombie ) { - return; - } - - if( !z->state ) { - return; - } - - z->next_in = NULL; - z->avail_in = 0; - - do { - data = FIFO_Reserve( fifo, &len ); - if( !len ) { - SV_HttpDrop( client, "overflowed" ); - return; - } - - z->next_out = data; - z->avail_out = ( uInt )len; - - ret = deflate( z, Z_FINISH ); - - FIFO_Commit( fifo, len - z->avail_out ); - } while( ret == Z_OK ); - - if( ret != Z_STREAM_END ) { - SV_HttpDrop( client, "deflate failed" ); - } -#endif -} - -void SV_HttpPrintf( const char *fmt, ... ) { - char buffer[MAX_STRING_CHARS]; - va_list argptr; - size_t len; - - if( http_client->state <= cs_zombie ) { - return; - } - - va_start( argptr, fmt ); - len = Q_vsnprintf( buffer, sizeof( buffer ), fmt, argptr ); - va_end( argptr ); - - if( len >= sizeof( buffer ) || FIFO_Write( &http_client->stream.send, buffer, len ) != len ) { - SV_HttpDrop( http_client, "overflowed" ); - } -} - -void SV_HttpDrop( tcpClient_t *client, const char *error ) { - if( client->state <= cs_zombie ) { - return; - } - if( error ) { - Com_DPrintf( "HTTP client %s dropped: %s\n", - NET_AdrToString( &client->stream.address ), error ); - } - - if( client->resource ) { - Z_Free( client->resource ); - client->resource = NULL; - } - if( client->host ) { - Z_Free( client->host ); - client->host = NULL; - } - if( client->agent ) { - Z_Free( client->agent ); - client->agent = NULL; - } - if( client->credentials ) { - Z_Free( client->credentials ); - client->credentials = NULL; - } -#if USE_ZLIB - if( client->z.state ) { - deflateEnd( &client->z ); - } -#endif - - List_Remove( &client->mvdEntry ); - client->mvd = NULL; - client->state = cs_zombie; - client->lastmessage = svs.realtime; -} - -void SV_HttpRemove( tcpClient_t *client ) { - char *addr, *dest; - int count; - - addr = NET_AdrToString( &client->stream.address ); - NET_Close( &client->stream ); - List_Remove( &client->entry ); - - count = List_Count( &svs.tcp_client_pool ); - if( count < sv_http_minclients->integer ) { - List_Insert( &svs.tcp_client_pool, &client->entry ); - dest = "pool"; - } else { - Z_Free( client ); - dest = "memory"; - } - - Com_DPrintf( "HTTP client %s removed into %s\n", addr, dest ); -} - -static void SV_HttpAccept( netstream_t *stream ) { - tcpClient_t *client; - netstream_t *s; - int count; - char *from; - - count = List_Count( &svs.tcp_client_list ); - if( count >= sv_http_maxclients->integer ) { - Com_DPrintf( "HTTP client %s rejected: too many clients\n", - NET_AdrToString( &stream->address ) ); - // NET_TrySend( stream, "HTTP/1.0 503 Service Unavailable\r\n\r\n" ); - NET_Close( stream ); - return; - } - - if( sv_iplimit->integer > 0 ) { - count = 0; - LIST_FOR_EACH( tcpClient_t, client, &svs.tcp_client_list, entry ) { - if( NET_IsEqualBaseAdr( &client->stream.address, &stream->address ) ) { - count++; - } - } - if( count >= sv_iplimit->integer ) { - Com_DPrintf( "HTTP client %s rejected: too many connections " - "from single IP address\n", - NET_AdrToString( &stream->address ) ); - NET_Close( stream ); - return; - } - } - - if( LIST_EMPTY( &svs.tcp_client_pool ) ) { - client = SV_Malloc( sizeof( *client ) + 256 + MAX_MSGLEN * 2 ); - from = "memory"; - } else { - client = LIST_FIRST( tcpClient_t, &svs.tcp_client_pool, entry ); - List_Remove( &client->entry ); - from = "pool"; - } - - memset( client, 0, sizeof( *client ) ); - List_Init( &client->mvdEntry ); - - s = &client->stream; - s->recv.data = ( byte * )( client + 1 ); - s->recv.size = 256; - s->send.data = s->recv.data + 256; - s->send.size = MAX_MSGLEN * 2; - s->socket = stream->socket; - s->address = stream->address; - s->state = stream->state; - - client->lastmessage = svs.realtime; - client->state = cs_assigned; - List_SeqAdd( &svs.tcp_client_list, &client->entry ); - - Com_DPrintf( "HTTP client %s accepted from %s\n", - NET_AdrToString( &stream->address ), from ); -} - -static qboolean SV_HttpParseRequest( tcpClient_t *client ) { - char key[MAX_TOKEN_CHARS]; - char *p, *token; - const char *line; - byte *b, *data; - size_t length; - int major, minor; - - while( 1 ) { - data = FIFO_Peek( &client->stream.recv, &length ); - if( !length ) { - break; - } - if( ( b = memchr( data, '\n', length ) ) != NULL ) { - // TODO: support folded lines - length = b - data + 1; - } - if( client->requestLength + length > MAX_NET_STRING - 1 ) { - SV_HttpReject( "400 Bad Request", NULL ); - break; - } - - memcpy( client->request + client->requestLength, data, length ); - client->requestLength += length; - client->request[client->requestLength] = 0; - - FIFO_Decommit( &client->stream.recv, length ); - - if( !b ) { - continue; - } - - line = client->request; - client->requestLength = 0; - - if( !client->method ) { - // parse request line - token = COM_SimpleParse( &line, NULL ); - if( !token[0] ) { - continue; // ignore empty lines - } - - // parse method - if( !strcmp( token, "GET" ) ) { - client->method = HTTP_METHOD_GET; - } else if( !strcmp( token, "HEAD" ) ) { - client->method = HTTP_METHOD_HEAD; - /*} else if( !strcmp( token, "POST" ) ) { - client->method = HTTP_METHOD_POST;*/ - } else { - SV_HttpReject( "501 Not Implemented", NULL ); - break; - } - - // parse URI - token = COM_SimpleParse( &line, NULL ); - if( !Q_stricmpn( token, "http://", 7 ) ) { - p = strchr( token + 7, '/' ); - if( !p ) { - SV_HttpReject( "400 Bad Request", NULL ); - break; - } - client->resource = SV_CopyString( p ); - *p = 0; - client->host = SV_CopyString( token + 7 ); - } else { - if( *token != '/' ) { - SV_HttpReject( "400 Bad Request", NULL ); - break; - } - client->resource = SV_CopyString( token ); - } - - // parse version - token = COM_SimpleParse( &line, NULL ); - if( strncmp( token, "HTTP/", 5 ) ) { - SV_HttpReject( "400 Bad Request", NULL ); - break; - } - major = strtoul( token + 5, &token, 10 ); - if( *token != '.' ) { - SV_HttpReject( "400 Bad Request", NULL ); - break; - } - minor = strtoul( token + 1, &token, 10 ); - if( major != 1 || ( minor != 0 && minor != 1 ) ) { - SV_HttpReject( "505 HTTP Version not supported", NULL ); - break; - } - } else { - token = COM_SimpleParse( &line, NULL ); - if( !token[0] ) { - if( !client->host || !client->resource ) { - SV_HttpReject( "400 Bad Request", NULL ); - return qfalse; - } - return qtrue; // end of header - } - // parse header fields - strcpy( key, token ); - p = strchr( key, ':' ); - if( !p ) { - SV_HttpReject( "400 Bad Request", NULL ); - break; - } - *p = 0; - Q_strlwr( key ); - - token = COM_SimpleParse( &line, NULL ); - if( !strcmp( key, "host" ) ) { - if( !client->host ) { - client->host = SV_CopyString( token ); - } - continue; - } - - if( !strcmp( key, "authorization" ) ) { - if( Q_stricmp( token, "Basic" ) ) { - continue; - } - token = COM_SimpleParse( &line, NULL ); - if( Q_Decode64( key, token, sizeof( key ) ) == -1 ) { - continue; - } - client->credentials = SV_CopyString( key ); - continue; - } - - if( !strcmp( key, "user-agent" ) ) { - if( !client->agent ) { - client->agent = SV_CopyString( token ); - } - continue; - } - } - } - - return qfalse; -} - - -void SV_HttpRun( void ) { - tcpClient_t *client, *next; - neterr_t ret; - netstream_t stream; - unsigned zombie_time = 1000 * sv_zombietime->value; - unsigned drop_time = 1000 * sv_timeout->value; - unsigned ghost_time = 1000 * sv_ghostime->value; - unsigned delta; - - // accept new connections - ret = NET_Accept( &net_from, &stream ); - if( ret == NET_ERROR ) { - Com_DPrintf( "%s from %s, ignored\n", NET_ErrorString(), - NET_AdrToString( &net_from ) ); - } else if( ret == NET_OK ) { - SV_HttpAccept( &stream ); - } - - // run existing connections - LIST_FOR_EACH_SAFE( tcpClient_t, client, next, &svs.tcp_client_list, entry ) { - http_client = client; - http_header[0] = 0; - - // check timeouts - delta = svs.realtime - client->lastmessage; - switch( client->state ) { - case cs_zombie: - if( delta > zombie_time || !FIFO_Usage( &client->stream.send ) ) { - SV_HttpRemove( client ); - continue; - } - break; - case cs_assigned: - if( delta > ghost_time || delta > drop_time ) { - SV_HttpReject( "408 Request Timeout", NULL ); - continue; - } - break; - default: - if( delta > drop_time ) { - SV_HttpDrop( client, "connection timed out" ); - SV_HttpRemove( client ); - continue; - } - break; - } - - // run network stream - ret = NET_Run( &client->stream ); - if( ret == NET_AGAIN ) { - // don't timeout - if( client->state >= cs_connected && !FIFO_Usage( &client->stream.send ) ) { - client->lastmessage = svs.realtime; - } - continue; - } - if( ret != NET_OK ) { - SV_HttpDrop( client, "connection reset by peer" ); - SV_HttpRemove( client ); - continue; - } - - // don't timeout - if( client->state >= cs_connected ) { - client->lastmessage = svs.realtime; - } - - // parse the request - if( client->state == cs_assigned ) { - if( SV_HttpParseRequest( client ) ) { - Com_DPrintf( "GET %s\n", client->resource ); - client->state = cs_connected; - Q_EscapeMarkup( http_host, client->host, sizeof( http_host ) ); - SV_HttpHandle( rootURIs, client->resource ); - } - - http_client = NULL; - } - - } -} - diff --git a/source/sv_init.c b/source/sv_init.c index 49283a5..a10efa4 100644 --- a/source/sv_init.c +++ b/source/sv_init.c @@ -19,7 +19,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sv_local.h" -#include "mvd_local.h" server_static_t svs; // persistant server info server_t sv; // local server @@ -116,8 +115,10 @@ static void SV_SpawnServer( cm_t *cm, const char *server, const char *spawnpoint // all precaches are complete sv.state = ss_game; +#if USE_MVD_SERVER // respawn dummy MVD client, set base states, etc SV_MvdMapChanged(); +#endif // set serverinfo variable SV_InfoSet( "mapname", sv.name ); @@ -134,7 +135,6 @@ static void SV_SpawnServer( cm_t *cm, const char *server, const char *spawnpoint Com_Printf ("-------------------------------------\n"); } - /* ============== SV_InitGame @@ -143,7 +143,7 @@ A brand new game has been started. If ismvd is true, load the built-in MVD game module. ============== */ -void SV_InitGame( qboolean ismvd ){ +void SV_InitGame( qboolean ismvd ) { int i, entnum; edict_t *ent; client_t *client; @@ -204,10 +204,6 @@ void SV_InitGame( qboolean ismvd ){ // enable networking if( sv_maxclients->integer > 1 ) { NET_Config( NET_SERVER ); - if( sv_http_enable->integer && NET_Listen( qtrue ) == NET_ERROR ) { - Com_EPrintf( "%s while opening server TCP port.\n", NET_ErrorString() ); - Cvar_Set( "sv_http_enable", "0" ); - } } svs.udp_client_pool = SV_Mallocz( sizeof( client_t ) * sv_maxclients->integer ); @@ -215,15 +211,18 @@ void SV_InitGame( qboolean ismvd ){ svs.numEntityStates = sv_maxclients->integer * UPDATE_BACKUP * MAX_PACKET_ENTITIES; svs.entityStates = SV_Mallocz( sizeof( entity_state_t ) * svs.numEntityStates ); +#if USE_MVD_SERVER // initialize MVD server if( !ismvd ) { SV_MvdInit(); } +#endif - Cvar_ClampInteger( sv_http_minclients, 0, sv_http_maxclients->integer ); Cvar_ClampInteger( sv_reserved_slots, 0, sv_maxclients->integer - 1 ); #if USE_ZLIB + svs.z.zalloc = SV_Zalloc; + svs.z.zfree = SV_Zfree; if( deflateInit2( &svs.z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY ) != Z_OK ) { @@ -232,15 +231,16 @@ void SV_InitGame( qboolean ismvd ){ #endif // init game +#if USE_MVD_CLIENT if( ismvd ) { if( ge ) { SV_ShutdownGameProgs(); } ge = &mvd_ge; ge->Init(); - } else { + } else +#endif SV_InitGameProgs(); - } // init rate limits SV_RateInit( &svs.ratelimit_status, sv_status_limit->integer, 1000 ); @@ -248,9 +248,6 @@ void SV_InitGame( qboolean ismvd ){ SV_RateInit( &svs.ratelimit_badrcon, 1, sv_badauth_time->value * 1000 ); List_Init( &svs.udp_client_list ); - List_Init( &svs.tcp_client_list ); - List_Init( &svs.tcp_client_pool ); - List_Init( &svs.console_list ); for( i = 0; i < sv_maxclients->integer; i++ ) { client = svs.udp_client_pool + i; @@ -324,7 +321,7 @@ void SV_Map (const char *levelstring, qboolean restart) { return; } - if( sv.state == ss_dead || sv.state == ss_broadcast || restart ) { + if( sv.state != ss_game || restart ) { SV_InitGame( qfalse ); // the game is just starting } diff --git a/source/sv_local.h b/source/sv_local.h index cebfade..d8cf615 100644 --- a/source/sv_local.h +++ b/source/sv_local.h @@ -38,6 +38,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "g_public.h" #include "sv_public.h" #include "cl_public.h" +#if USE_MVD_CLIENT +#include "mvd_public.h" +#endif #if USE_ZLIB #include <zlib.h> #endif @@ -260,40 +263,6 @@ typedef struct client_s { #endif } client_t; -typedef enum { - HTTP_METHOD_BAD, - HTTP_METHOD_GET, - HTTP_METHOD_HEAD, - HTTP_METHOD_POST -} htmethod_t; - -typedef enum { - HTTP_CODING_NONE, - HTTP_CODING_GZIP, - HTTP_CODING_DEFLATE, - HTTP_CODING_UNKNOWN -} htcoding_t; - -typedef struct { - list_t entry; - clstate_t state; - netstream_t stream; -#if USE_ZLIB - z_stream z; - int noflush; -#endif - unsigned lastmessage; - - char request[MAX_NET_STRING]; - size_t requestLength; - htmethod_t method; - char *resource, *host, *agent, *credentials; - - // MVD clients specific - list_t mvdEntry; - struct mvd_s *mvd; -} tcpClient_t; - // a client can leave the server in one of four ways: // dropping properly by quiting or disconnecting // timing out if no valid messages are received for timeout.value seconds @@ -360,11 +329,6 @@ typedef struct server_static_s { unsigned nextEntityStates; // next entityState to use entity_state_t *entityStates; // [numEntityStates] - list_t tcp_client_pool; - list_t tcp_client_list; - - list_t console_list; - #if USE_ZLIB z_stream z; // for compressing messages at once #endif @@ -409,13 +373,12 @@ extern cvar_t *sv_enforcetime; extern cvar_t *sv_force_reconnect; extern cvar_t *sv_iplimit; -extern cvar_t *sv_http_enable; -extern cvar_t *sv_http_maxclients; -extern cvar_t *sv_http_minclients; -extern cvar_t *sv_console_auth; +//extern cvar_t *sv_console_auth; +#if USE_CLIENT extern cvar_t *sv_debug_send; extern cvar_t *sv_pad_packets; +#endif extern cvar_t *sv_lan_force_rate; extern cvar_t *sv_calcpings_method; extern cvar_t *sv_changemapcmd; @@ -465,6 +428,10 @@ addrmatch_t *SV_MatchAddress( list_t *list, netadr_t *address ); int SV_CountClients( void ); +#if USE_ZLIB +voidpf SV_Zalloc OF(( voidpf opaque, uInt items, uInt size )); +void SV_Zfree OF(( voidpf opaque, voidpf address )); +#endif void Master_Heartbeat (void); void Master_Packet (void); @@ -514,16 +481,13 @@ void SV_CalcSendTime( client_t *client, size_t messageSize ); // // sv_mvd.c // - -extern cvar_t *sv_mvd_enable; -extern cvar_t *sv_mvd_auth; -extern cvar_t *sv_mvd_noblend; -extern cvar_t *sv_mvd_nogun; - void SV_MvdRegister( void ); void SV_MvdInit( void ); -void SV_MvdShutdown( void ) ; -void SV_MvdRunFrame( void ); +void SV_MvdShutdown( killtype_t type ); +void SV_MvdBeginFrame( void ); +void SV_MvdEndFrame( void ); +void SV_MvdRunClients( void ); +void SV_MvdStatus_f( void ); void SV_MvdMapChanged( void ); void SV_MvdClientDropped( client_t *client, const char *reason ); @@ -535,35 +499,11 @@ void SV_MvdStartSound( int entnum, int channel, int flags, int soundindex, int volume, int attenuation, int timeofs ); -void SV_MvdInitStream( void ); -void SV_MvdGetStream( const char *uri ); - -// -// sv_http.c -// - -extern tcpClient_t *http_client; -extern char http_host[MAX_STRING_CHARS]; -extern char http_header[MAX_STRING_CHARS]; - -void SV_HttpRun( void ); - -void SV_HttpRemove( tcpClient_t *client ); -void SV_HttpDrop( tcpClient_t *client, const char *error ); -void SV_HttpWrite( tcpClient_t *client, void *data, size_t length ); -void SV_HttpFinish( tcpClient_t *client ); - -void SV_HttpPrintf( const char *fmt, ... ) q_printf( 1, 2 ); -void SV_HttpHeader( const char *title ); -void SV_HttpFooter( void ); -void SV_HttpReject( const char *error, const char *reason ); - #if USE_ANTICHEAT // // sv_ac.c // -// char *AC_ClientConnect( client_t *cl ); void AC_ClientDisconnect( client_t *cl ); qboolean AC_ClientBegin( client_t *cl ); diff --git a/source/sv_main.c b/source/sv_main.c index cdb0147..d3d899d 100644 --- a/source/sv_main.c +++ b/source/sv_main.c @@ -19,7 +19,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sv_local.h" -#include "mvd_local.h" netadr_t master_adr[MAX_MASTERS]; // address of group servers @@ -58,11 +57,6 @@ cvar_t *sv_qwmod; // atu QW Physics modificator cvar_t *sv_noreload; // don't reload level state when reentering cvar_t *sv_novis; -cvar_t *sv_http_enable; -cvar_t *sv_http_maxclients; -cvar_t *sv_http_minclients; -cvar_t *sv_console_auth; - cvar_t *sv_maxclients; cvar_t *sv_reserved_slots; cvar_t *sv_showclamp; @@ -72,8 +66,10 @@ cvar_t *sv_downloadserver; cvar_t *sv_hostname; cvar_t *sv_public; // should heartbeats be sent +#if USE_CLIENT cvar_t *sv_debug_send; cvar_t *sv_pad_packets; +#endif cvar_t *sv_lan_force_rate; cvar_t *sv_calcpings_method; cvar_t *sv_changemapcmd; @@ -110,10 +106,12 @@ void SV_RemoveClient( client_t *client ) { // unlink them from active client list List_Remove( &client->entry ); +#if USE_MVD_CLIENT // unlink them from MVD client list if( sv.state == ss_broadcast ) { MVD_RemoveClient( client ); } +#endif Com_DPrintf( "Going from cs_zombie to cs_free for %s\n", client->name ); @@ -174,9 +172,12 @@ void SV_DropClient( client_t *client, const char *reason ) { if( reason ) { if( oldstate == cs_spawned ) { // announce to others +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { MVD_GameClientDrop( client->edict, reason ); - } else { + } else +#endif + { SV_BroadcastPrintf( PRINT_HIGH, "%s was dropped: %s\n", client->name, reason ); } @@ -211,8 +212,10 @@ void SV_DropClient( client_t *client, const char *reason ) { Com_DPrintf( "Going to cs_zombie for %s\n", client->name ); +#if USE_MVD_SERVER // give MVD server a chance to detect if it's dummy client was dropped SV_MvdClientDropped( client, reason ); +#endif } @@ -310,9 +313,12 @@ static size_t SV_StatusString( char *status ) { if( cl->state == cs_zombie ) { continue; } +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { j = 0; - } else { + } else +#endif + { j = cl->edict->client->ps.stats[STAT_FRAGS]; } length = sprintf( entry, "%i %i \"%s\"\n", @@ -1241,9 +1247,9 @@ static void SV_PacketEvent( neterr_t ret ) { if( netchan->Process( netchan ) ) { // this is a valid, sequenced packet, so process it if( client->state != cs_zombie ) { - if( client->state != cs_assigned ) { + //if( client->state != cs_assigned ) { client->lastmessage = svs.realtime; // don't timeout - } + //} client->flags &= ~CF_ERROR; // don't drop SV_ExecuteClientMessage( client ); } @@ -1302,10 +1308,12 @@ void SV_SendAsyncPackets( void ) { // make sure all fragments are transmitted first if( netchan->fragment_pending ) { cursize = netchan->TransmitNextFragment( netchan ); +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( S_COLOR_BLUE"%s: frag: %"PRIz"\n", client->name, cursize ); } +#endif goto calctime; } @@ -1330,10 +1338,12 @@ void SV_SendAsyncPackets( void ) { netchan->reliable_length || retransmit ) { cursize = netchan->Transmit( netchan, 0, NULL ); +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( S_COLOR_BLUE"%s: send: %"PRIz"\n", client->name, cursize ); } +#endif calctime: SV_CalcSendTime( client, cursize ); } @@ -1430,6 +1440,11 @@ static void SV_RunGameFrame( void ) { sv.framenum++; sv.frametime -= 100; +#if USE_MVD_SERVER + // save the entire world state if recording a serverdemo + SV_MvdBeginFrame(); +#endif + ge->RunFrame(); if( msg_write.cursize ) { @@ -1439,8 +1454,10 @@ static void SV_RunGameFrame( void ) { SZ_Clear( &msg_write ); } +#if USE_MVD_SERVER // save the entire world state if recording a serverdemo - SV_MvdRunFrame(); + SV_MvdEndFrame(); +#endif #if USE_CLIENT if( host_speeds->integer ) @@ -1529,7 +1546,11 @@ void SV_Frame( unsigned msec ) { svs.realtime += msec; +#if USE_MVD_CLIENT mvdconns = MVD_Frame(); +#else + mvdconns = 0; +#endif // if server is not active, do nothing if( !svs.initialized ) { @@ -1563,10 +1584,6 @@ void SV_Frame( unsigned msec ) { AC_Run(); #endif - if( sv_http_enable->integer ) { - SV_HttpRun(); - } - // check timeouts SV_CheckTimeouts (); @@ -1591,16 +1608,24 @@ void SV_Frame( unsigned msec ) { // let everything in the world think and move SV_RunGameFrame(); - // send messages back to the clients that had packets read this frame + // send messages back to the UDP clients SV_SendClientMessages(); +#if USE_MVD_SERVER + // run TCP client connections + SV_MvdRunClients(); +#endif + // send a heartbeat to the master if needed SV_MasterHeartbeat(); // clear teleport flags, etc for next frame +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { MVD_PrepWorldFrame(); - } else { + } else +#endif + { SV_PrepWorldFrame(); } @@ -1699,9 +1724,12 @@ void SV_UserinfoChanged( client_t *cl ) { Com_Printf( "%s[%s] changed name to %s\n", cl->name, NET_AdrToString( &cl->netchan->remote_address ), name ); } +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { MVD_GameClientNameChanged( cl->edict, name ); - } else if( sv_show_name_changes->integer ) { + } else +#endif + if( sv_show_name_changes->integer ) { SV_BroadcastPrintf( PRINT_HIGH, "%s changed name to %s\n", cl->name, name ); } @@ -1763,6 +1791,16 @@ static void sv_hostname_changed( cvar_t *self ) { SV_SetConsoleTitle(); } +#if USE_ZLIB +voidpf SV_Zalloc OF(( voidpf opaque, uInt items, uInt size )) { + return SV_Malloc( items * size ); +} + +void SV_Zfree OF(( voidpf opaque, voidpf address )) { + Z_Free( address ); +} +#endif + /* =============== SV_Init @@ -1772,8 +1810,15 @@ Only called at quake2.exe startup, not for each game */ void SV_Init( void ) { SV_InitOperatorCommands (); + +#if USE_MVD_SERVER SV_MvdRegister(); +#endif + +#if USE_MVD_CLIENT MVD_Register(); +#endif + #if USE_ANTICHEAT & 2 AC_Register(); #endif @@ -1794,17 +1839,12 @@ void SV_Init( void ) { sv_hostname->changed = sv_hostname_changed; sv_timeout = Cvar_Get( "timeout", "90", 0 ); sv_zombietime = Cvar_Get( "zombietime", "2", 0 ); - sv_ghostime = Cvar_Get( "sv_ghostime", "10", 0 ); + sv_ghostime = Cvar_Get( "sv_ghostime", "6", 0 ); sv_showclamp = Cvar_Get( "showclamp", "0", 0 ); sv_enforcetime = Cvar_Get ( "sv_enforcetime", "1", 0 ); sv_force_reconnect = Cvar_Get ( "sv_force_reconnect", "", CVAR_LATCH ); sv_show_name_changes = Cvar_Get( "sv_show_name_changes", "0", 0 ); - sv_http_enable = Cvar_Get( "sv_http_enable", "0", CVAR_LATCH ); - sv_http_maxclients = Cvar_Get( "sv_http_maxclients", "4", 0 ); - sv_http_minclients = Cvar_Get( "sv_http_minclients", "4", 0 ); - sv_console_auth = Cvar_Get( "sv_console_auth", "", 0 ); - allow_download = Cvar_Get( "allow_download", "1", CVAR_ARCHIVE ); allow_download_players = Cvar_Get( "allow_download_players", "0", CVAR_ARCHIVE ); allow_download_models = Cvar_Get( "allow_download_models", "1", CVAR_ARCHIVE ); @@ -1824,8 +1864,10 @@ void SV_Init( void ) { sv_novis = Cvar_Get ("sv_novis", "0", 0); sv_downloadserver = Cvar_Get( "sv_downloadserver", "", 0 ); +#if USE_CLIENT sv_debug_send = Cvar_Get( "sv_debug_send", "0", 0 ); sv_pad_packets = Cvar_Get( "sv_pad_packets", "0", 0 ); +#endif sv_lan_force_rate = Cvar_Get( "sv_lan_force_rate", "0", CVAR_LATCH ); sv_calcpings_method = Cvar_Get( "sv_calcpings_method", "1", 0 ); sv_changemapcmd = Cvar_Get( "sv_changemapcmd", "", 0 ); @@ -1875,7 +1917,6 @@ server is going to totally exit after returning from this function. */ static void SV_FinalMessage( const char *message, int cmd ) { client_t *client, *next; - tcpClient_t *t, *tnext; netchan_t *netchan; int i; @@ -1908,19 +1949,6 @@ static void SV_FinalMessage( const char *message, int cmd ) { } SV_RemoveClient( client ); } - - // drop any TCP clients, flushing pending data - LIST_FOR_EACH_SAFE( tcpClient_t, t, tnext, &svs.tcp_client_list, entry ) { - SV_HttpDrop( t, NULL ); - NET_Run( &t->stream ); - NET_Close( &t->stream ); - Z_Free( t ); - } - - // free cached TCP client slots - LIST_FOR_EACH_SAFE( tcpClient_t, t, tnext, &svs.tcp_client_pool, entry ) { - Z_Free( t ); - } } @@ -1937,7 +1965,9 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ) { Cvar_Set( "sv_paused", "0" ); if( !svs.initialized ) { +#if USE_MVD_CLIENT MVD_Shutdown(); // make sure MVD client is down +#endif return; } @@ -1945,8 +1975,10 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ) { AC_Disconnect(); #endif +#if USE_MVD_SERVER // shutdown MVD server - SV_MvdShutdown(); + SV_MvdShutdown( type ); +#endif if( type == KILL_RESTART ) { SV_FinalMessage( finalmsg, svc_reconnect ); @@ -1954,9 +1986,6 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ) { SV_FinalMessage( finalmsg, svc_disconnect ); } - // close server TCP socket - NET_Listen( qfalse ); - SV_MasterShutdown(); SV_ShutdownGameProgs(); diff --git a/source/sv_mvd.c b/source/sv_mvd.c index 28f6a68..f4d5d42 100644 --- a/source/sv_mvd.c +++ b/source/sv_mvd.c @@ -1,5 +1,5 @@ /* -Copyright (C) 2003-2006 Andrey Nazarov +Copyright (C) 2003-2008 Andrey Nazarov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -19,20 +19,46 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // -// sv_mvd.c - MVD server and local recorder +// sv_mvd.c - GTV server and local MVD recorder // #include "sv_local.h" +#include "gtv.h" -cvar_t *sv_mvd_enable; -cvar_t *sv_mvd_auth; -cvar_t *sv_mvd_noblend; -cvar_t *sv_mvd_nogun; -cvar_t *sv_mvd_nomsgs; +#define FOR_EACH_GTV( client ) \ + LIST_FOR_EACH( gtv_client_t, client, >v_client_list, entry ) + +#define FOR_EACH_ACTIVE_GTV( client ) \ + LIST_FOR_EACH( gtv_client_t, client, >v_active_list, active ) + +typedef struct { + list_t entry; + list_t active; + clstate_t state; + netstream_t stream; +#if USE_ZLIB + z_stream z; +#endif + unsigned msglen; + unsigned lastmessage; + + unsigned flags; + unsigned maxbuf; + unsigned bufcount; + + byte buffer[256]; + byte *data; + + char name[MAX_CLIENT_NAME]; + char version[MAX_QPATH]; +} gtv_client_t; typedef struct { + qboolean active; client_t *dummy; unsigned layout_time; + unsigned clients_active; + unsigned players_active; // reliable data, may not be discarded sizebuf_t message; @@ -48,29 +74,47 @@ typedef struct { fileHandle_t recording; int numlevels; // stop after that many levels int numframes; // stop after that many frames + + // TCP client pool + gtv_client_t *clients; // [sv_mvd_maxclients] } mvd_server_t; static mvd_server_t mvd; -// TCP client list -static LIST_DECL( mvd_client_list ); - -static cvar_t *sv_mvd_max_size; -static cvar_t *sv_mvd_max_duration; -static cvar_t *sv_mvd_max_levels; -static cvar_t *sv_mvd_begincmd; -static cvar_t *sv_mvd_scorecmd; -static cvar_t *sv_mvd_autorecord; +// TCP client lists +static LIST_DECL( gtv_client_list ); +static LIST_DECL( gtv_active_list ); + +static LIST_DECL( gtv_host_list ); + +static cvar_t *sv_mvd_enable; +static cvar_t *sv_mvd_maxclients; +static cvar_t *sv_mvd_auth; +static cvar_t *sv_mvd_noblend; +static cvar_t *sv_mvd_nogun; +static cvar_t *sv_mvd_nomsgs; +static cvar_t *sv_mvd_maxsize; +static cvar_t *sv_mvd_maxtime; +static cvar_t *sv_mvd_maxmaps; +static cvar_t *sv_mvd_begincmd; +static cvar_t *sv_mvd_scorecmd; +static cvar_t *sv_mvd_autorecord; +static cvar_t *sv_mvd_capture_flags; + +static qboolean mvd_enable( void ); +static void mvd_disable( void ); +static void mvd_drop( gtv_serverop_t op ); + +static void write_stream( gtv_client_t *client, void *data, size_t len ); +static void write_message( gtv_client_t *client, gtv_serverop_t op ); #if USE_ZLIB -static cvar_t *sv_mvd_encoding; +static void flush_stream( gtv_client_t *client, int flush ); #endif -static cvar_t *sv_mvd_capture_flags; - -static qboolean init_mvd( void ); static void rec_stop( void ); static qboolean rec_allowed( void ); static void rec_start( fileHandle_t demofile ); +static void rec_write( void ); /* @@ -138,7 +182,7 @@ static void dummy_record_f( void ) { return; } - if( !init_mvd() ) { + if( !mvd_enable() ) { FS_FCloseFile( demofile ); return; } @@ -252,7 +296,7 @@ static void dummy_spawn( void ) { mvd.dummy->state = cs_spawned; } -static client_t *find_slot( void ) { +static client_t *dummy_find_slot( void ) { client_t *c; int i, j; @@ -284,7 +328,7 @@ static qboolean dummy_create( void ) { int number; // find a free client slot - newcl = find_slot(); + newcl = dummy_find_slot(); if( !newcl ) { Com_EPrintf( "No slot for dummy MVD client\n" ); return qfalse; @@ -357,22 +401,6 @@ static void dummy_run( void ) { } } -static void dummy_drop( const char *reason ) { - tcpClient_t *client; - - rec_stop(); - - LIST_FOR_EACH( tcpClient_t, client, &mvd_client_list, mvdEntry ) { - SV_HttpDrop( client, reason ); - } - - SV_RemoveClient( mvd.dummy ); - mvd.dummy = NULL; - - SZ_Clear( &mvd.datagram ); - SZ_Clear( &mvd.message ); -} - /* ============================================================================== @@ -531,12 +559,9 @@ static void emit_gamestate( void ) { player_state_t *ps; entity_state_t *es; size_t length; - uint8_t *patch; int flags, extra, portalbytes; byte portalbits[MAX_MAP_AREAS/8]; - patch = SZ_GetSpace( &msg_write, 2 ); - // send the serverdata MSG_WriteByte( mvd_serverdata ); MSG_WriteLong( PROTOCOL_VERSION_MVD ); @@ -601,10 +626,6 @@ static void emit_gamestate( void ) { es->number = j; } MSG_WriteShort( 0 ); - - length = msg_write.cursize - 2; - patch[0] = length & 255; - patch[1] = ( length >> 8 ) & 255; } @@ -735,92 +756,177 @@ static void emit_frame( void ) { MSG_WriteShort( 0 ); // end of packetentities } -// if dummy is not yet connected, create and spawn it -static qboolean init_mvd( void ) { +#define MVD_CLIENTS_TIMEOUT (15*60*1000) +#define MVD_PLAYERS_TIMEOUT (5*60*1000) + +/* +================== +SV_MvdBeginFrame +================== +*/ +void SV_MvdBeginFrame( void ) { + gtv_client_t *client; + int i; + + // do nothing if not active if( !mvd.dummy ) { - if( !dummy_create() ) { - return qfalse; + return; + } + + // disconnect MVD dummy if no MVD clients are active for 15 minutes + if( !sv_mvd_autorecord->integer ) { + if( mvd.recording || !LIST_EMPTY( >v_active_list ) ) { + mvd.clients_active = svs.realtime; + } else if( svs.realtime - mvd.clients_active > MVD_CLIENTS_TIMEOUT ) { + Com_Printf( "No MVD clients active for 15 minutes, " + "disconnecting dummy client.\n" ); + SV_DropClient( mvd.dummy, NULL ); + return; } - dummy_spawn(); + } + + // suspend all MVD streams if no players are active for 5 minutes + for( i = 0; i < sv_maxclients->integer; i++ ) { + edict_t * ent = EDICT_NUM( i + 1 ); + if( ent != mvd.dummy->edict && player_is_active( ent ) ) { + mvd.players_active = svs.realtime; + break; + } + } + + if( svs.realtime - mvd.players_active > MVD_PLAYERS_TIMEOUT ) { + if( mvd.active ) { + FOR_EACH_ACTIVE_GTV( client ) { + // send stream stop marker + write_message( client, GTS_STREAM_STOP ); +#if USE_ZLIB + flush_stream( client, Z_SYNC_FLUSH ); +#endif + } + Com_Printf( "No players active for 5 minutes, " + "suspending MVD streams.\n" ); + mvd.active = qfalse; + } + } else if( !mvd.active ) { + // build and emit gamestate build_gamestate(); + emit_gamestate(); + + FOR_EACH_ACTIVE_GTV( client ) { + // send stream start marker + write_message( client, GTS_STREAM_START ); + + // send gamestate + write_message( client, GTS_STREAM_DATA ); +#if USE_ZLIB + flush_stream( client, Z_SYNC_FLUSH ); +#endif + } + + // write it to demofile + if( mvd.recording ) { + rec_write(); + } + + // clear gamestate + SZ_Clear( &msg_write ); + + SZ_Clear( &mvd.datagram ); + SZ_Clear( &mvd.message ); + + Com_Printf( "Resuming MVD streams.\n" ); + mvd.active = qtrue; } - return qtrue; } /* ================== -SV_MvdRunFrame +SV_MvdEndFrame ================== */ -void SV_MvdRunFrame( void ) { - tcpClient_t *client; - uint16_t length; +void SV_MvdEndFrame( void ) { + gtv_client_t *client; + size_t total; + byte header[3]; + // do nothing if not active if( !mvd.dummy ) { return; } dummy_run(); + if( !mvd.active ) { + return; + } + + // if reliable message overflowed, kick all clients if( mvd.message.overflowed ) { - // if reliable message overflowed, kick all clients - Com_EPrintf( "Reliable MVD message overflowed\n" ); - dummy_drop( "overflowed" ); - goto clear; + Com_EPrintf( "Reliable MVD message overflowed!\n" ); + SV_DropClient( mvd.dummy, NULL ); + return; } if( mvd.datagram.overflowed ) { - Com_WPrintf( "Unreliable MVD datagram overflowed\n" ); + Com_WPrintf( "Unreliable MVD datagram overflowed.\n" ); SZ_Clear( &mvd.datagram ); } - // emit a single delta update + // emit a delta update common to all clients emit_frame(); - // check if frame fits - // FIXME: dumping frame should not be allowed in current design - if( mvd.message.cursize + msg_write.cursize > MAX_MSGLEN ) { - Com_WPrintf( "Dumping MVD frame: %"PRIz" bytes\n", msg_write.cursize ); + // if reliable message and frame update don't fit, kick all clients + if( mvd.message.cursize + msg_write.cursize >= MAX_MSGLEN ) { + Com_EPrintf( "MVD frame overflowed!\n" ); SZ_Clear( &msg_write ); + SV_DropClient( mvd.dummy, NULL ); + return; } // check if unreliable datagram fits - if( mvd.message.cursize + msg_write.cursize + mvd.datagram.cursize > MAX_MSGLEN ) { - Com_WPrintf( "Dumping unreliable MVD datagram: %"PRIz" bytes\n", mvd.datagram.cursize ); + if( mvd.message.cursize + msg_write.cursize + mvd.datagram.cursize >= MAX_MSGLEN ) { + Com_WPrintf( "Dumping unreliable MVD datagram.\n" ); SZ_Clear( &mvd.datagram ); } - length = LittleShort( mvd.message.cursize + msg_write.cursize + mvd.datagram.cursize ); + // build message header + total = mvd.message.cursize + msg_write.cursize + mvd.datagram.cursize + 1; + header[0] = total & 255; + header[1] = ( total >> 8 ) & 255; + header[2] = GTS_STREAM_DATA; // send frame to clients - LIST_FOR_EACH( tcpClient_t, client, &mvd_client_list, mvdEntry ) { - if( client->state == cs_spawned ) { - SV_HttpWrite( client, &length, 2 ); - SV_HttpWrite( client, mvd.message.data, mvd.message.cursize ); - SV_HttpWrite( client, msg_write.data, msg_write.cursize ); - SV_HttpWrite( client, mvd.datagram.data, mvd.datagram.cursize ); + FOR_EACH_ACTIVE_GTV( client ) { + write_stream( client, header, sizeof( header ) ); + write_stream( client, mvd.message.data, mvd.message.cursize ); + write_stream( client, msg_write.data, msg_write.cursize ); + write_stream( client, mvd.datagram.data, mvd.datagram.cursize ); #if USE_ZLIB - client->noflush++; -#endif + if( ++client->bufcount > client->maxbuf ) { + flush_stream( client, Z_SYNC_FLUSH ); } +#endif } // write frame to demofile if( mvd.recording ) { - FS_Write( &length, 2, mvd.recording ); + uint16_t len; + + len = LittleShort( total - 1 ); + FS_Write( &len, 2, mvd.recording ); FS_Write( mvd.message.data, mvd.message.cursize, mvd.recording ); FS_Write( msg_write.data, msg_write.cursize, mvd.recording ); FS_Write( mvd.datagram.data, mvd.datagram.cursize, mvd.recording ); - if( sv_mvd_max_size->value > 0 ) { + if( sv_mvd_maxsize->value > 0 ) { int numbytes = FS_RawTell( mvd.recording ); - if( numbytes > sv_mvd_max_size->value * 1000 ) { + if( numbytes > sv_mvd_maxsize->value * 1000 ) { Com_Printf( "Stopping MVD recording, maximum size reached.\n" ); rec_stop(); } - } else if( sv_mvd_max_duration->value > 0 && - ++mvd.numframes > sv_mvd_max_duration->value * 600 ) + } else if( sv_mvd_maxtime->value > 0 && + ++mvd.numframes > sv_mvd_maxtime->value * 600 ) { Com_Printf( "Stopping MVD recording, maximum duration reached.\n" ); rec_stop(); @@ -829,228 +935,10 @@ void SV_MvdRunFrame( void ) { SZ_Clear( &msg_write ); -clear: - SZ_Clear( &mvd.datagram ); - SZ_Clear( &mvd.message ); -} - -/* -============================================================================== - -CLIENT HANDLING - -============================================================================== -*/ - -static void SV_MvdClientNew( tcpClient_t *client ) { - Com_DPrintf( "Sending gamestate to MVD client %s\n", - NET_AdrToString( &client->stream.address ) ); - client->state = cs_spawned; - - emit_gamestate(); - -#if USE_ZLIB - client->noflush = 99999; -#endif - - SV_HttpWrite( client, msg_write.data, msg_write.cursize ); - SZ_Clear( &msg_write ); -} - -void SV_MvdInitStream( void ) { - uint32_t magic; -#if USE_ZLIB - int bits; -#endif - - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" ); - -#if USE_ZLIB - switch( sv_mvd_encoding->integer ) { - case 1: - SV_HttpPrintf( "Content-Encoding: gzip\r\n" ); - bits = 31; - break; - case 2: - SV_HttpPrintf( "Content-Encoding: deflate\r\n" ); - bits = 15; - break; - default: - bits = 0; - break; - } -#endif - - SV_HttpPrintf( - "Content-Type: application/octet-stream\r\n" - "Content-Disposition: attachment; filename=\"stream.mvd2\"\r\n" - "\r\n" ); - -#if USE_ZLIB - if( bits ) { - deflateInit2( &http_client->z, Z_DEFAULT_COMPRESSION, - Z_DEFLATED, bits, 8, Z_DEFAULT_STRATEGY ); - } -#endif - - magic = MVD_MAGIC; - SV_HttpWrite( http_client, &magic, 4 ); -} - -void SV_MvdGetStream( const char *uri ) { - if( http_client->method == HTTP_METHOD_HEAD ) { - SV_HttpPrintf( "HTTP/1.0 200 OK\r\n\r\n" ); - SV_HttpDrop( http_client, "200 OK " ); - return; - } - - if( !init_mvd() ) { - SV_HttpReject( "503 Service Unavailable", - "Unable to create dummy MVD client. Server is full." ); - return; - } - - List_Append( &mvd_client_list, &http_client->mvdEntry ); - - SV_MvdInitStream(); - - SV_MvdClientNew( http_client ); -} - -/* -============================================================================== - -SERVER HOOKS - -These hooks are called by server code when some event occurs. - -============================================================================== -*/ - -/* -================== -SV_MvdMapChanged - -Server has just changed the map, spawn the MVD dummy and go! -================== -*/ -void SV_MvdMapChanged( void ) { - tcpClient_t *client; - - if( !sv_mvd_enable->integer ) { - return; // do noting if disabled - } - - if( !mvd.dummy ) { - if( !sv_mvd_autorecord->integer ) { - return; // not listening for autorecord command - } - if( !dummy_create() ) { - return; - } - Com_Printf( "Spawning MVD dummy for auto-recording\n" ); - } - SZ_Clear( &mvd.datagram ); SZ_Clear( &mvd.message ); - - dummy_spawn(); - build_gamestate(); - emit_gamestate(); - - // send gamestate to all MVD clients - LIST_FOR_EACH( tcpClient_t, client, &mvd_client_list, mvdEntry ) { - SV_HttpWrite( client, msg_write.data, msg_write.cursize ); - } - - if( mvd.recording ) { - int maxlevels = sv_mvd_max_levels->integer; - - // check if it is time to stop recording - if( maxlevels > 0 && ++mvd.numlevels >= maxlevels ) { - Com_Printf( "Stopping MVD recording, " - "maximum number of level changes reached.\n" ); - rec_stop(); - } else { - FS_Write( msg_write.data, msg_write.cursize, mvd.recording ); - } - } - - SZ_Clear( &msg_write ); -} - -/* -================== -SV_MvdClientDropped - -Server has just dropped a client, check if that was our MVD dummy client. -================== -*/ -void SV_MvdClientDropped( client_t *client, const char *reason ) { - if( client == mvd.dummy ) { - dummy_drop( reason ); - } -} - -/* -================== -SV_MvdInit - -Server is initializing, prepare MVD server for this game. -================== -*/ -void SV_MvdInit( void ) { - if( !sv_mvd_enable->integer ) { - return; // do noting if disabled - } - - // allocate buffers - Z_TagReserve( sizeof( player_state_t ) * sv_maxclients->integer + - sizeof( entity_state_t ) * MAX_EDICTS + MAX_MSGLEN * 2, TAG_SERVER ); - SZ_Init( &mvd.message, Z_ReservedAlloc( MAX_MSGLEN ), MAX_MSGLEN ); - SZ_Init( &mvd.datagram, Z_ReservedAlloc( MAX_MSGLEN ), MAX_MSGLEN ); - mvd.players = Z_ReservedAlloc( sizeof( player_state_t ) * sv_maxclients->integer ); - mvd.entities = Z_ReservedAlloc( sizeof( entity_state_t ) * MAX_EDICTS ); - - // reserve the slot for dummy MVD client - if( !sv_reserved_slots->integer ) { - Cvar_Set( "sv_reserved_slots", "1" ); - } } -/* -================== -SV_MvdShutdown - -Server is shutting down, clean everything up. -================== -*/ -void SV_MvdShutdown( void ) { - tcpClient_t *t; - uint16_t length; - - // stop recording - rec_stop(); - - // send EOF to MVD clients - // NOTE: not clearing mvd_client_list as clients will be - // properly dropped by SV_FinalMessage just after - length = 0; - LIST_FOR_EACH( tcpClient_t, t, &mvd_client_list, mvdEntry ) { - SV_HttpWrite( t, &length, 2 ); - SV_HttpFinish( t ); - } - - // remove MVD dummy - if( mvd.dummy ) { - SV_RemoveClient( mvd.dummy ); - } - - // free static data - Z_Free( mvd.message.data ); - - memset( &mvd, 0, sizeof( mvd ) ); -} /* @@ -1079,7 +967,7 @@ void SV_MvdMulticast( int leafnum, multicast_t to ) { int bits; // do nothing if not active - if( !mvd.dummy ) { + if( !mvd.active ) { return; } @@ -1111,7 +999,7 @@ void SV_MvdUnicast( edict_t *ent, int clientNum, qboolean reliable ) { int bits; // do nothing if not active - if( !mvd.dummy ) { + if( !mvd.active ) { return; } @@ -1167,7 +1055,7 @@ SV_MvdConfigstring ============== */ void SV_MvdConfigstring( int index, const char *string ) { - if( mvd.dummy ) { + if( mvd.active ) { SZ_WriteByte( &mvd.message, mvd_configstring ); SZ_WriteShort( &mvd.message, index ); SZ_WriteString( &mvd.message, string ); @@ -1180,7 +1068,7 @@ SV_MvdBroadcastPrint ============== */ void SV_MvdBroadcastPrint( int level, const char *string ) { - if( mvd.dummy ) { + if( mvd.active ) { SZ_WriteByte( &mvd.message, mvd_print ); SZ_WriteByte( &mvd.message, level ); SZ_WriteString( &mvd.message, string ); @@ -1200,7 +1088,7 @@ void SV_MvdStartSound( int entnum, int channel, int flags, { int extrabits, sendchan; - if( !mvd.dummy ) { + if( !mvd.active ) { return; } @@ -1232,11 +1120,759 @@ void SV_MvdStartSound( int entnum, int channel, int flags, /* ============================================================================== +TCP CLIENTS HANDLING + +============================================================================== +*/ + + +static void remove_client( gtv_client_t *client ) { + NET_Close( &client->stream ); + List_Remove( &client->entry ); + if( client->data ) { + Z_Free( client->data ); + client->data = NULL; + } + client->state = cs_free; +} + +#if USE_ZLIB +static void flush_stream( gtv_client_t *client, int flush ) { + fifo_t *fifo = &client->stream.send; + z_streamp z = &client->z; + byte *data; + size_t len; + int ret; + + if( !z->state ) { + return; + } + + z->next_in = NULL; + z->avail_in = 0; + + do { + data = FIFO_Reserve( fifo, &len ); + if( !len ) { + // FIXME: this is not an error when flushing + return; + } + + z->next_out = data; + z->avail_out = ( uInt )len; + + ret = deflate( z, flush ); + + len -= z->avail_out; + if( len ) { + FIFO_Commit( fifo, len ); + client->bufcount = 0; + } + } while( ret == Z_OK ); +} +#endif + +static void drop_client( gtv_client_t *client, const char *error ) { + if( client->state <= cs_zombie ) { + return; + } + if( error ) { + // notify console + Com_Printf( "TCP client %s[%s] dropped: %s\n", client->name, + NET_AdrToString( &client->stream.address ), error ); + + } + +#if USE_ZLIB + if( client->z.state ) { + // finish zlib stream + flush_stream( client, Z_FINISH ); + deflateEnd( &client->z ); + } +#endif + + List_Remove( &client->active ); + client->state = cs_zombie; + client->lastmessage = svs.realtime; +} + + +static void write_stream( gtv_client_t *client, void *data, size_t len ) { + fifo_t *fifo = &client->stream.send; + + if( client->state <= cs_zombie ) { + return; + } + + if( !len ) { + return; + } + +#if USE_ZLIB + if( client->z.state ) { + z_streamp z = &client->z; + + z->next_in = data; + z->avail_in = ( uInt )len; + + do { + data = FIFO_Reserve( fifo, &len ); + if( !len ) { + drop_client( client, "overflowed" ); + return; + } + + z->next_out = data; + z->avail_out = ( uInt )len; + + if( deflate( z, Z_NO_FLUSH ) != Z_OK ) { + drop_client( client, "deflate() failed" ); + return; + } + + len -= z->avail_out; + if( len ) { + FIFO_Commit( fifo, len ); + client->bufcount = 0; + } + } while( z->avail_in ); + } else +#endif + + if( !FIFO_TryWrite( fifo, data, len ) ) { + drop_client( client, "overflowed" ); + } +} + +static void write_message( gtv_client_t *client, gtv_serverop_t op ) { + byte header[3]; + size_t len = msg_write.cursize + 1; + + header[0] = len & 255; + header[1] = ( len >> 8 ) & 255; + header[2] = op; + write_stream( client, header, sizeof( header ) ); + + write_stream( client, msg_write.data, msg_write.cursize ); +} + +static void parse_hello( gtv_client_t *client ) { + int protocol, flags, maxbuf; + byte *data; + + if( client->state >= cs_primed ) { + write_message( client, GTS_BADREQUEST ); + drop_client( client, "duplicated hello message" ); + return; + } + + protocol = MSG_ReadWord(); + if( protocol != GTV_PROTOCOL_VERSION ) { + write_message( client, GTS_BADREQUEST ); + drop_client( client, "bad protocol" ); + return; + } + + flags = MSG_ReadLong(); + maxbuf = MSG_ReadShort(); + MSG_ReadLong(); + MSG_ReadString( client->name, sizeof( client->name ) ); + MSG_ReadString( client->version, sizeof( client->version ) ); + + if( !SV_MatchAddress( >v_host_list, &client->stream.address ) ) { + write_message( client, GTS_NOACCESS ); + drop_client( client, "not authorized" ); + return; + } + + data = SV_Malloc( MAX_MSGLEN * 2 ); + + // TODO: expand recv buffer + client->stream.send.data = data; + client->stream.send.size = MAX_MSGLEN * 2; + client->data = data; + client->flags = flags; + client->maxbuf = maxbuf; + client->state = cs_primed; + + // send hello + MSG_WriteLong( flags ); + write_message( client, GTS_HELLO ); + SZ_Clear( &msg_write ); + +#if USE_ZLIB + // the rest of the stream will be deflated + if( flags & GTF_DEFLATE ) { + client->z.zalloc = SV_Zalloc; + client->z.zfree = SV_Zfree; + if( deflateInit( &client->z, Z_DEFAULT_COMPRESSION ) != Z_OK ) { + drop_client( client, "deflateInit failed" ); + return; + } + } +#endif + + mvd.clients_active = svs.realtime; + + Com_Printf( "Accepted MVD client %s[%s]\n", client->name, + NET_AdrToString( &client->stream.address ) ); +} + +static void parse_ping( gtv_client_t *client ) { + if( client->state < cs_primed ) { + return; + } + + // send ping reply + write_message( client, GTS_PONG ); +#if USE_ZLIB + flush_stream( client, Z_SYNC_FLUSH ); +#endif +} + +static void parse_stream_start( gtv_client_t *client ) { + if( client->state < cs_primed ) { + return; + } + + client->state = cs_spawned; + + List_Append( >v_active_list, &client->active ); + + if( !mvd_enable() ) { + write_message( client, GTS_ERROR ); + drop_client( client, "couldn't create MVD dummy" ); + return; + } + + // do nothing if not active + if( !mvd.active ) { + return; + } + + // send stream start marker + write_message( client, GTS_STREAM_START ); + + // send gamestate + emit_gamestate(); + write_message( client, GTS_STREAM_DATA ); +#if USE_ZLIB + flush_stream( client, Z_SYNC_FLUSH ); +#endif + SZ_Clear( &msg_write ); +} + +static void parse_stream_stop( gtv_client_t *client ) { + if( client->state < cs_spawned ) { + return; + } + + client->state = cs_primed; + + List_Delete( &client->active ); + + // do nothing if not active + if( !mvd.active ) { + return; + } + + // send stream stop marker + write_message( client, GTS_STREAM_STOP ); +#if USE_ZLIB + flush_stream( client, Z_SYNC_FLUSH ); +#endif +} + +static qboolean parse_message( gtv_client_t *client ) { + uint32_t magic; + uint16_t msglen; + int cmd; + + if( client->state <= cs_zombie ) { + return qfalse; + } + + // check magic + if( client->state < cs_connected ) { + if( !FIFO_TryRead( &client->stream.recv, &magic, 4 ) ) { + return qfalse; + } + if( magic != MVD_MAGIC ) { + drop_client( client, "not a MVD/GTV stream" ); + return qfalse; + } + client->state = cs_connected; + + // send it back + write_stream( client, &magic, 4 ); + } + + // parse msglen + if( !client->msglen ) { + if( !FIFO_TryRead( &client->stream.recv, &msglen, 2 ) ) { + return qfalse; + } + msglen = LittleShort( msglen ); + if( !msglen ) { + drop_client( client, "end of stream" ); + return qfalse; + } + if( msglen > 128 ) { + drop_client( client, "oversize message" ); + return qfalse; + } + client->msglen = msglen; + } + + // read this message + if( !FIFO_ReadMessage( &client->stream.recv, client->msglen ) ) { + return qfalse; + } + + client->msglen = 0; + + cmd = MSG_ReadByte(); + switch( cmd ) { + case GTC_HELLO: + parse_hello( client ); + break; + case GTC_PING: + parse_ping( client ); + break; + case GTC_STREAM_START: + parse_stream_start( client ); + break; + case GTC_STREAM_STOP: + parse_stream_stop( client ); + break; + default: + drop_client( client, "unknown command byte" ); + return qfalse; + } + + if( msg_read.readcount > msg_read.cursize ) { + drop_client( client, "read past end of message" ); + return qfalse; + } + + client->lastmessage = svs.realtime; // don't timeout + return qtrue; +} + +static gtv_client_t *find_slot( void ) { + gtv_client_t *client; + int i; + + for( i = 0; i < sv_mvd_maxclients->integer; i++ ) { + client = &mvd.clients[i]; + if( !client->state ) { + return client; + } + } + return NULL; +} + +static void accept_client( netstream_t *stream ) { + gtv_client_t *client; + netstream_t *s; + + // limit number of connections from single IP + if( sv_iplimit->integer > 0 ) { + int count = 0; + + FOR_EACH_GTV( client ) { + if( NET_IsEqualBaseAdr( &client->stream.address, &stream->address ) ) { + count++; + } + } + if( count >= sv_iplimit->integer ) { + Com_Printf( "TCP client [%s] rejected: too many connections\n", + NET_AdrToString( &stream->address ) ); + NET_Close( stream ); + return; + } + } + + // find a free client slot + client = find_slot(); + if( !client ) { + Com_Printf( "TCP client [%s] rejected: no free slots\n", + NET_AdrToString( &stream->address ) ); + NET_Close( stream ); + return; + } + + memset( client, 0, sizeof( *client ) ); + + s = &client->stream; + s->recv.data = client->buffer; + s->recv.size = 128; + s->send.data = client->buffer + 128; + s->send.size = 128; + s->socket = stream->socket; + s->address = stream->address; + s->state = stream->state; + + client->lastmessage = svs.realtime; + client->state = cs_assigned; + List_SeqAdd( >v_client_list, &client->entry ); + List_Init( &client->active ); + + Com_DPrintf( "TCP client [%s] accepted\n", + NET_AdrToString( &stream->address ) ); +} + +void SV_MvdRunClients( void ) { + gtv_client_t *client; + neterr_t ret; + netstream_t stream; + unsigned zombie_time = 1000 * sv_zombietime->value; + unsigned drop_time = 1000 * sv_timeout->value; + unsigned ghost_time = 1000 * sv_ghostime->value; + unsigned delta; + + // accept new connections + ret = NET_Accept( &net_from, &stream ); + if( ret == NET_ERROR ) { + Com_DPrintf( "%s from %s, ignored\n", NET_ErrorString(), + NET_AdrToString( &net_from ) ); + } else if( ret == NET_OK ) { + accept_client( &stream ); + } + + // run existing connections + FOR_EACH_GTV( client ) { + // check timeouts + delta = svs.realtime - client->lastmessage; + switch( client->state ) { + case cs_zombie: + if( delta > zombie_time || !FIFO_Usage( &client->stream.send ) ) { + remove_client( client ); + continue; + } + break; + case cs_assigned: + case cs_connected: + if( delta > ghost_time || delta > drop_time ) { + drop_client( client, "request timed out" ); + remove_client( client ); + continue; + } + break; + default: + if( delta > drop_time ) { + drop_client( client, "connection timed out" ); + remove_client( client ); + continue; + } + break; + } + + // run network stream + ret = NET_RunStream( &client->stream ); + switch( ret ) { + case NET_AGAIN: + break; + case NET_OK: + // parse the message + while( parse_message( client ) ) + ; + break; + case NET_CLOSED: + drop_client( client, "EOF from client" ); + remove_client( client ); + break; + case NET_ERROR: + drop_client( client, "connection reset by peer" ); + remove_client( client ); + break; + } + } +} + +static void dump_clients( void ) { + gtv_client_t *client; + int count; + + Com_Printf( +"num name buf lastmsg address state\n" +"--- ---------------- --- ------- --------------------- -----\n" ); + count = 0; + FOR_EACH_GTV( client ) { + Com_Printf( "%3d %-16.16s %3"PRIz" %7u %-21s ", + count, client->name, FIFO_Usage( &client->stream.send ), + svs.realtime - client->lastmessage, + NET_AdrToString( &client->stream.address ) ); + + switch( client->state ) { + case cs_zombie: + Com_Printf( "ZMBI " ); + break; + case cs_assigned: + Com_Printf( "ASGN " ); + break; + case cs_connected: + Com_Printf( "CNCT " ); + break; + case cs_primed: + Com_Printf( "PRIM " ); + break; + default: + Com_Printf( "SEND " ); + break; + } + Com_Printf( "\n" ); + + count++; + } +} + +static void dump_versions( void ) { + gtv_client_t *client; + int count; + + Com_Printf( +"num name version\n" +"--- ---------------- -----------------------------------------\n" ); + + FOR_EACH_GTV( client ) { + count = 0; + Com_Printf( "%3i %-16.16s %-40.40s\n", + count, client->name, client->version ); + count++; + } +} + +void SV_MvdStatus_f( void ) { + if( LIST_EMPTY( >v_client_list ) ) { + Com_Printf( "No TCP clients.\n" ); + } else { + if( Cmd_Argc() > 1 ) { + dump_versions(); + } else { + dump_clients(); + } + } + Com_Printf( "\n" ); +} + +static void mvd_disable( void ) { + // remove MVD dummy + if( mvd.dummy ) { + SV_RemoveClient( mvd.dummy ); + mvd.dummy = NULL; + } + + SZ_Clear( &mvd.datagram ); + SZ_Clear( &mvd.message ); + + mvd.active = qfalse; +} + +// something bad happened, remove all clients +static void mvd_drop( gtv_serverop_t op ) { + gtv_client_t *client; + + // stop recording + rec_stop(); + + // drop GTV clients + FOR_EACH_GTV( client ) { + switch( client->state ) { + case cs_spawned: + if( mvd.active ) { + write_message( client, GTS_STREAM_STOP ); + } + // fall through + case cs_primed: + write_message( client, op ); + drop_client( client, NULL ); + NET_RunStream( &client->stream ); + NET_RunStream( &client->stream ); + remove_client( client ); + break; + default: + drop_client( client, NULL ); + remove_client( client ); + break; + } + } + + mvd_disable(); +} + +// if dummy is not yet connected, create and spawn it +static qboolean mvd_enable( void ) { + if( !mvd.dummy ) { + if( !dummy_create() ) { + return qfalse; + } + dummy_spawn(); + build_gamestate(); + mvd.clients_active = mvd.players_active = svs.realtime; + mvd.active = qtrue; + } + return qtrue; +} + + +/* +============================================================================== + +SERVER HOOKS + +These hooks are called by server code when some event occurs. + +============================================================================== +*/ + +/* +================== +SV_MvdMapChanged + +Server has just changed the map, spawn the MVD dummy and go! +================== +*/ +void SV_MvdMapChanged( void ) { + gtv_client_t *client; + + if( !sv_mvd_enable->integer ) { + return; // do noting if disabled + } + + if( !mvd.dummy ) { + if( !sv_mvd_autorecord->integer ) { + return; // not listening for autorecord command + } + if( !dummy_create() ) { + return; + } + Com_Printf( "Spawning MVD dummy for auto-recording\n" ); + } + + dummy_spawn(); + + if( mvd.active ) { + // build and emit gamestate + build_gamestate(); + emit_gamestate(); + + // send gamestate to all MVD clients + FOR_EACH_ACTIVE_GTV( client ) { + write_message( client, GTS_STREAM_DATA ); + } + } + + if( mvd.recording ) { + int maxlevels = sv_mvd_maxmaps->integer; + + // check if it is time to stop recording + if( maxlevels > 0 && ++mvd.numlevels >= maxlevels ) { + Com_Printf( "Stopping MVD recording, " + "maximum number of level changes reached.\n" ); + rec_stop(); + } else if( mvd.active ) { + // write gamestate to demofile + rec_write(); + } + } + + // clear gamestate + SZ_Clear( &msg_write ); + + SZ_Clear( &mvd.datagram ); + SZ_Clear( &mvd.message ); +} + +/* +================== +SV_MvdClientDropped + +Server has just dropped a client, check if that was our MVD dummy client. +================== +*/ +void SV_MvdClientDropped( client_t *client, const char *reason ) { + if( client == mvd.dummy ) { + mvd_drop( GTS_ERROR ); + } +} + +/* +================== +SV_MvdInit + +Server is initializing, prepare MVD server for this game. +================== +*/ +void SV_MvdInit( void ) { + if( !sv_mvd_enable->integer ) { + return; // do noting if disabled + } + + // allocate buffers + Z_TagReserve( sizeof( player_state_t ) * sv_maxclients->integer + + sizeof( entity_state_t ) * MAX_EDICTS + MAX_MSGLEN * 2, TAG_SERVER ); + SZ_Init( &mvd.message, Z_ReservedAlloc( MAX_MSGLEN ), MAX_MSGLEN ); + SZ_Init( &mvd.datagram, Z_ReservedAlloc( MAX_MSGLEN ), MAX_MSGLEN ); + mvd.players = Z_ReservedAlloc( sizeof( player_state_t ) * sv_maxclients->integer ); + mvd.entities = Z_ReservedAlloc( sizeof( entity_state_t ) * MAX_EDICTS ); + + // reserve the slot for dummy MVD client + if( !sv_reserved_slots->integer ) { + Cvar_Set( "sv_reserved_slots", "1" ); + } + + Cvar_ClampInteger( sv_mvd_maxclients, 1, 256 ); + + // open server TCP socket + if( sv_mvd_enable->integer > 1 ) { + if( NET_Listen( qtrue ) == NET_OK ) { + mvd.clients = SV_Mallocz( sizeof( gtv_client_t ) * sv_mvd_maxclients->integer ); + } else { + Com_EPrintf( "%s while opening server TCP port.\n", NET_ErrorString() ); + Cvar_Set( "sv_mvd_enable", "1" ); + } + } +} + +/* +================== +SV_MvdShutdown + +Server is shutting down, clean everything up. +================== +*/ +void SV_MvdShutdown( killtype_t type ) { + // drop all clients + mvd_drop( type == KILL_RESTART ? GTS_RECONNECT : GTS_DISCONNECT ); + + // free static data + Z_Free( mvd.message.data ); + Z_Free( mvd.clients ); + + // close server TCP socket + NET_Listen( qfalse ); + + memset( &mvd, 0, sizeof( mvd ) ); +} + + +/* +============================================================================== + LOCAL MVD RECORDER ============================================================================== */ +static void rec_write( void ) { + uint16_t msglen; + + msglen = LittleShort( msg_write.cursize ); + FS_Write( &msglen, 2, mvd.recording ); + FS_Write( msg_write.data, msg_write.cursize, mvd.recording ); +} + /* ============== rec_stop @@ -1245,15 +1881,15 @@ Stops server local MVD recording. ============== */ static void rec_stop( void ) { - uint16_t length; + uint16_t msglen; if( !mvd.recording ) { return; } // write demo EOF marker - length = 0; - FS_Write( &length, 2, mvd.recording ); + msglen = 0; + FS_Write( &msglen, 2, mvd.recording ); FS_FCloseFile( mvd.recording ); mvd.recording = 0; @@ -1277,12 +1913,13 @@ static void rec_start( fileHandle_t demofile ) { mvd.recording = demofile; mvd.numlevels = 0; mvd.numframes = 0; + mvd.clients_active = svs.realtime; magic = MVD_MAGIC; FS_Write( &magic, 4, demofile ); emit_gamestate(); - FS_Write( msg_write.data, msg_write.cursize, demofile ); + rec_write(); SZ_Clear( &msg_write ); } @@ -1293,14 +1930,13 @@ const cmd_option_t o_mvdrecord[] = { { NULL } }; -extern void MVD_StreamedStop_f( void ); -extern void MVD_StreamedRecord_f( void ); -extern void MVD_File_g( genctx_t *ctx ); - static void SV_MvdRecord_c( genctx_t *ctx, int argnum ) { +#if USE_MVD_CLIENT + // TODO if( argnum == 1 ) { MVD_File_g( ctx ); } +#endif } /* @@ -1319,9 +1955,12 @@ static void SV_MvdRecord_f( void ) { size_t len; if( sv.state != ss_game ) { +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { MVD_StreamedRecord_f(); - } else { + } else +#endif + { Com_Printf( "No server running.\n" ); } return; @@ -1366,7 +2005,7 @@ static void SV_MvdRecord_f( void ) { return; } - if( !init_mvd() ) { + if( !mvd_enable() ) { FS_FCloseFile( demofile ); return; } @@ -1389,10 +2028,12 @@ Ends server MVD recording ============== */ static void SV_MvdStop_f( void ) { +#if USE_MVD_CLIENT if( sv.state == ss_broadcast ) { MVD_StreamedStop_f(); return; } +#endif if( !mvd.recording ) { Com_Printf( "Not recording a local MVD.\n" ); return; @@ -1411,20 +2052,34 @@ static void SV_MvdStuff_f( void ) { } } +static void SV_AddGtvHost_f( void ) { + SV_AddMatch_f( >v_host_list ); +} +static void SV_DelGtvHost_f( void ) { + SV_DelMatch_f( >v_host_list ); +} +static void SV_ListGtvHosts_f( void ) { + SV_ListMatches_f( >v_host_list ); +} + static const cmdreg_t c_svmvd[] = { { "mvdrecord", SV_MvdRecord_f, SV_MvdRecord_c }, { "mvdstop", SV_MvdStop_f }, { "mvdstuff", SV_MvdStuff_f }, + { "addgtvhost", SV_AddGtvHost_f }, + { "delgtvhost", SV_DelGtvHost_f }, + { "listgtvhosts", SV_ListGtvHosts_f }, { NULL } }; void SV_MvdRegister( void ) { sv_mvd_enable = Cvar_Get( "sv_mvd_enable", "0", CVAR_LATCH ); + sv_mvd_maxclients = Cvar_Get( "sv_mvd_maxclients", "8", CVAR_LATCH ); sv_mvd_auth = Cvar_Get( "sv_mvd_auth", "", CVAR_PRIVATE ); - sv_mvd_max_size = Cvar_Get( "sv_mvd_max_size", "0", 0 ); - sv_mvd_max_duration = Cvar_Get( "sv_mvd_max_duration", "0", 0 ); - sv_mvd_max_levels = Cvar_Get( "sv_mvd_max_levels", "1", 0 ); + sv_mvd_maxsize = Cvar_Get( "sv_mvd_maxsize", "0", 0 ); + sv_mvd_maxtime = Cvar_Get( "sv_mvd_maxtime", "0", 0 ); + sv_mvd_maxmaps = Cvar_Get( "sv_mvd_maxmaps", "1", 0 ); sv_mvd_noblend = Cvar_Get( "sv_mvd_noblend", "0", CVAR_LATCH ); sv_mvd_nogun = Cvar_Get( "sv_mvd_nogun", "1", CVAR_LATCH ); sv_mvd_nomsgs = Cvar_Get( "sv_mvd_nomsgs", "1", CVAR_LATCH ); @@ -1432,10 +2087,7 @@ void SV_MvdRegister( void ) { "wait 50; putaway; wait 10; help;", 0 ); sv_mvd_scorecmd = Cvar_Get( "sv_mvd_scorecmd", "putaway; wait 10; help;", 0 ); - sv_mvd_autorecord = Cvar_Get( "sv_mvd_autorecord", "0", 0 ); -#if USE_ZLIB - sv_mvd_encoding = Cvar_Get( "sv_mvd_encoding", "1", 0 ); -#endif + sv_mvd_autorecord = Cvar_Get( "sv_mvd_autorecord", "0", CVAR_LATCH ); sv_mvd_capture_flags = Cvar_Get( "sv_mvd_capture_flags", "5", 0 ); dummy_buffer.text = dummy_buffer_text; diff --git a/source/sv_null.c b/source/sv_null.c index 7811846..19bdc6d 100644 --- a/source/sv_null.c +++ b/source/sv_null.c @@ -9,7 +9,6 @@ cvar_t *allow_download_demos; cvar_t *allow_download_other; cvar_t *rcon_password; -cvar_t *mvd_running; void SV_Init( void ) { allow_download = Cvar_Get( "allow_download", "1", CVAR_ARCHIVE ); @@ -21,7 +20,6 @@ void SV_Init( void ) { allow_download_other = Cvar_Get( "allow_download_other", "0", 0 ); rcon_password = Cvar_Get( "rcon_password", "", CVAR_PRIVATE ); - mvd_running = Cvar_Get( "mvd_running", "0", CVAR_ROM ); } void SV_Shutdown( const char *finalmsg, killtype_t type ) { @@ -30,15 +28,14 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ) { void SV_Frame( int msec ) { } -void SV_PacketEvent( int ret ) { -} - -void MVD_PacketEvent( int ret ) { +void SV_ProcessEvents( void ) { } +#if 0 qboolean MVD_GetDemoPercent( int *percent, int *bufferPercent ) { *percent = 0; *bufferPercent = 0; return qfalse; } +#endif diff --git a/source/sv_public.h b/source/sv_public.h index c6c5e91..c12e2d2 100644 --- a/source/sv_public.h +++ b/source/sv_public.h @@ -22,7 +22,9 @@ typedef enum { ss_dead, // no map loaded ss_loading, // spawning level edicts ss_game, // actively running - ss_broadcast +#if USE_MVD_CLIENT + ss_broadcast // running MVD client +#endif } server_state_t; typedef enum { @@ -37,5 +39,5 @@ void SV_Shutdown( const char *finalmsg, killtype_t type ); void SV_Frame (unsigned msec); void SV_SetConsoleTitle( void ); void SV_ConsoleOutput( const char *msg ); -qboolean MVD_GetDemoPercent( int *percent, int *bufferPercent ); +//qboolean MVD_GetDemoPercent( int *percent, int *bufferPercent ); diff --git a/source/sv_send.c b/source/sv_send.c index 6d85d29..99ca99f 100644 --- a/source/sv_send.c +++ b/source/sv_send.c @@ -20,7 +20,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // sv_send.c #include "sv_local.h" -#include "mvd_local.h" /* ============================================================================= @@ -308,8 +307,10 @@ void SV_Multicast( vec3_t origin, multicast_t to ) { SV_ClientAddMessage( client, flags ); } +#if USE_MVD_SERVER // add to MVD datagram SV_MvdMulticast( leafnum, to ); +#endif // clear the buffer SZ_Clear( &msg_write ); @@ -327,10 +328,12 @@ unless told otherwise. ======================= */ void SV_ClientAddMessage( client_t *client, int flags ) { +#if USE_CLIENT if( sv_debug_send->integer > 1 ) { Com_Printf( S_COLOR_BLUE"%s: add%c: %"PRIz"\n", client->name, ( flags & MSG_RELIABLE ) ? 'r' : 'u', msg_write.cursize ); } +#endif if( !msg_write.cursize ) { return; @@ -425,10 +428,12 @@ static void emit_snd( client_t *client, message_packet_t *msg ) { } } if( i == frame->numEntities ) { +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( S_COLOR_BLUE "Forcing position on entity %d for %s\n", entnum, client->name ); } +#endif flags |= SND_POS; // entity is not present in frame } } @@ -519,9 +524,11 @@ void SV_ClientWriteReliableMessages_Old( client_t *client, size_t maxsize ) { int count; if( client->netchan->reliable_length ) { +#if USE_CLIENT if( sv_debug_send->integer > 1 ) { Com_Printf( S_COLOR_BLUE"%s: unacked\n", client->name ); } +#endif return; // there is still outgoing reliable message pending } @@ -537,10 +544,12 @@ void SV_ClientWriteReliableMessages_Old( client_t *client, size_t maxsize ) { break; } +#if USE_CLIENT if( sv_debug_send->integer > 1 ) { Com_Printf( S_COLOR_BLUE"%s: wrt%d: %d\n", client->name, count, msg->cursize ); } +#endif SZ_Write( &client->netchan->message, msg->data, msg->cursize ); SV_PacketizedRemove( client, msg ); @@ -630,10 +639,12 @@ void SV_ClientWriteDatagram_Old( client_t *client ) { // and the player_state_t client->WriteFrame( client ); if( msg_write.cursize > maxsize ) { +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( S_COLOR_BLUE"Frame overflowed for %s: %"PRIz" > %"PRIz"\n", client->name, msg_write.cursize, maxsize ); } +#endif SZ_Clear( &msg_write ); } @@ -703,6 +714,7 @@ void SV_ClientWriteDatagram_New( client_t *client ) { emit_messages( client, msg_write.maxsize ); } +#if USE_CLIENT if( sv_pad_packets->integer ) { size_t pad = msg_write.cursize + sv_pad_packets->integer; @@ -713,6 +725,7 @@ void SV_ClientWriteDatagram_New( client_t *client ) { MSG_WriteByte( svc_nop ); } } +#endif // send the datagram cursize = client->netchan->Transmit( client->netchan, @@ -769,10 +782,12 @@ void SV_SendClientMessages( void ) { // don't overrun bandwidth if( SV_RateDrop( client ) ) { +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( "Frame %d surpressed for %s\n", sv.framenum, client->name ); } +#endif client->surpressCount++; } else { // don't write any frame data until all fragments are sent diff --git a/source/sv_user.c b/source/sv_user.c index 5b4f221..87fbf3f 100644 --- a/source/sv_user.c +++ b/source/sv_user.c @@ -20,7 +20,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // sv_user.c -- server code for moving users #include "sv_local.h" -#include "mvd_local.h" edict_t *sv_player; @@ -208,10 +207,12 @@ static void write_compressed_gamestate( void ) { return; } +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( S_COLOR_BLUE"%s: comp: %lu into %lu\n", sv_client->name, svs.z.total_in, svs.z.total_out ); } +#endif patch[0] = svs.z.total_out & 255; patch[1] = ( svs.z.total_out >> 8 ) & 255; @@ -226,10 +227,12 @@ static inline int z_flush( byte *buffer ) { return ret; } +#if USE_CLIENT if( sv_debug_send->integer ) { Com_Printf( S_COLOR_BLUE"%s: comp: %lu into %lu\n", sv_client->name, svs.z.total_in, svs.z.total_out ); } +#endif MSG_WriteByte( svc_zpacket ); MSG_WriteShort( svs.z.total_out ); |