diff options
author | Andrey Nazarov <skuller@skuller.net> | 2008-10-11 15:05:50 +0000 |
---|---|---|
committer | Andrey Nazarov <skuller@skuller.net> | 2008-10-11 15:05:50 +0000 |
commit | d02633af4e780c4b6f6d938c67d84d2c968adb79 (patch) | |
tree | 3379b9615e285346ad6b1f87639912e01ecd44c7 | |
parent | f8abe42a0d1a42653b39f6cf320d3fbdd1279bb3 (diff) |
Major redesign of GTV protocol: added support for persistent GTV connections,
bidirectional pinging, low traffic (`suspended') modes.
HTTP server is now gone (remote console logging is temporary gone too),
custom binary protocol is used for GTV connections now.
MVD client no longer serves other MVD clients, only regular spectators.
Changed FIFO buffers to be regular circular buffers, not BIP-buffers.
Removed `sv_http_*', `sv_console_auth' variables.
Added `sv_mvd_maxclients' variable, `addgtvhost', `delgtvhost' and
`listgtvhosts' commands.
Renamed `sv_mvd_max*' cvars for consistency.
Reset `sv_ghostime' default value back to 6, but changed semantics:
it now waits for any packet from client, not just `begin' packet.
Added `--disable-mvd-server' and `--disable-mvd-client' options to
configure script.
FS_Restart() no longer chokes on real files opened for reading.
Fixed client chat prompt length.
Stubbed out more debugging stuff from dedicated server builds.
-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 ); |