summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/server.mk13
-rwxr-xr-xconfigure18
-rw-r--r--source/cl_console.c1
-rw-r--r--source/cl_demo.c12
-rw-r--r--source/common.c24
-rw-r--r--source/files.c24
-rw-r--r--source/gtv.h44
-rw-r--r--source/mvd_client.c1847
-rw-r--r--source/mvd_game.c155
-rw-r--r--source/mvd_local.h88
-rw-r--r--source/mvd_parse.c242
-rw-r--r--source/net_common.c101
-rw-r--r--source/net_stream.h3
-rw-r--r--source/q_fifo.h14
-rw-r--r--source/q_shared.c197
-rw-r--r--source/q_shared.h4
-rw-r--r--source/sv_ac.c58
-rw-r--r--source/sv_ccmds.c78
-rw-r--r--source/sv_game.c15
-rw-r--r--source/sv_http.c820
-rw-r--r--source/sv_init.c25
-rw-r--r--source/sv_local.h90
-rw-r--r--source/sv_main.c113
-rw-r--r--source/sv_mvd.c1296
-rw-r--r--source/sv_null.c9
-rw-r--r--source/sv_public.h6
-rw-r--r--source/sv_send.c17
-rw-r--r--source/sv_user.c5
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
diff --git a/configure b/configure
index 6cee521..a670cf0 100755
--- a/configure
+++ b/configure
@@ -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, &gtv->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( &gtv->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( &gtv->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( &gtv->z_buf, &gtv->stream.recv, &gtv->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( &gtv->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( &gtv->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( &gtv->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, &gtv->z_buf ) );
+ } else
#endif
+ while( parse_message( gtv, &gtv->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, &gtv->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( &gtv->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( &gtv->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( &gtv->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( &gtv->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( &gtv->stream );
+#if USE_ZLIB
+ inflateReset( &gtv->z_str );
+ FIFO_Clear( &gtv->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( &gtv->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, &gtv->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, &gtv->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, &gtv->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 = "&lt;"; break;
- case '>': s = "&gt;"; break;
- case '&': s = "&amp;"; break;
- case '"': s = "&quot;"; 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( "&lt; 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, &gtv_client_list, entry )
+
+#define FOR_EACH_ACTIVE_GTV( client ) \
+ LIST_FOR_EACH( gtv_client_t, client, &gtv_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( &gtv_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( &gtv_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( &gtv_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( &gtv_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( &gtv_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( &gtv_host_list );
+}
+static void SV_DelGtvHost_f( void ) {
+ SV_DelMatch_f( &gtv_host_list );
+}
+static void SV_ListGtvHosts_f( void ) {
+ SV_ListMatches_f( &gtv_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 );