From da1df94452cc38f7a9cc0a1b39e2d0437b171e26 Mon Sep 17 00:00:00 2001
From: Bill Currie <bill@taniwha.org>
Date: Fri, 6 May 2005 07:24:30 +0000
Subject: [PATCH] split out the qw protocol parsing from the server managmement
 code

---
 qtv/include/server.h   |   4 +
 qtv/source/Makefile.am |   2 +-
 qtv/source/server.c    | 479 ++-------------------------------------
 qtv/source/sv_parse.c  | 503 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 524 insertions(+), 464 deletions(-)
 create mode 100644 qtv/source/sv_parse.c

diff --git a/qtv/include/server.h b/qtv/include/server.h
index 9f01b2927..0ef8d5cb9 100644
--- a/qtv/include/server.h
+++ b/qtv/include/server.h
@@ -61,4 +61,8 @@ typedef struct server_s {
 void Server_Init (void);
 void Server_Frame (void);
 
+struct qmsg_s;
+void sv_parse (server_t *sv, struct msg_s *msg, int reliable);
+void sv_stringcmd (server_t *sv, struct msg_s *msg);
+
 #endif//__server_h
diff --git a/qtv/source/Makefile.am b/qtv/source/Makefile.am
index b13694159..1c8719211 100644
--- a/qtv/source/Makefile.am
+++ b/qtv/source/Makefile.am
@@ -44,7 +44,7 @@ qtv_LIBS= \
 		$(top_builddir)/libs/console/libQFconsole.la \
 		$(top_builddir)/libs/util/libQFutil.la
 
-qtv_SOURCES=	client.c connection.c qtv.c server.c
+qtv_SOURCES=	client.c connection.c qtv.c server.c sv_parse.c
 qtv_LDADD=	$(qtv_LIBS) $(NET_LIBS) $(DL_LIBS) $(CURSES_LIBS)
 qtv_LDFLAGS=	$(common_ldflags)
 qtv_DEPENDENCIES=	$(qtv_LIBS)
diff --git a/qtv/source/server.c b/qtv/source/server.c
index 80cedbb36..fd3bb6994 100644
--- a/qtv/source/server.c
+++ b/qtv/source/server.c
@@ -104,338 +104,33 @@ server_compare (const void *a, const void *b)
 	return strcmp ((*(server_t **) a)->name, (*(server_t **) b)->name);
 }
 
-static void
-qtv_serverdata (server_t *sv)
-{
-	const char *str;
-
-	sv->ver = MSG_ReadLong (net_message);
-	sv->spawncount = MSG_ReadLong (net_message);
-	sv->gamedir = strdup (MSG_ReadString (net_message));
-
-	sv->message = strdup (MSG_ReadString (net_message));
-	sv->movevars.gravity = MSG_ReadFloat (net_message);
-	sv->movevars.stopspeed = MSG_ReadFloat (net_message);
-	sv->movevars.maxspeed = MSG_ReadFloat (net_message);
-	sv->movevars.spectatormaxspeed = MSG_ReadFloat (net_message);
-	sv->movevars.accelerate = MSG_ReadFloat (net_message);
-	sv->movevars.airaccelerate = MSG_ReadFloat (net_message);
-	sv->movevars.wateraccelerate = MSG_ReadFloat (net_message);
-	sv->movevars.friction = MSG_ReadFloat (net_message);
-	sv->movevars.waterfriction = MSG_ReadFloat (net_message);
-	sv->movevars.entgravity = MSG_ReadFloat (net_message);
-
-	sv->cdtrack = MSG_ReadByte (net_message);
-	sv->sounds = MSG_ReadByte (net_message);
-
-	COM_TokenizeString (MSG_ReadString (net_message), qtv_args);
-	cmd_args = qtv_args;
-	Info_Destroy (sv->info);
-	sv->info = Info_ParseString (Cmd_Argv (1), MAX_SERVERINFO_STRING, 0);
-
-	str = Info_ValueForKey (sv->info, "hostname");
-	if (strcmp (str, "unnamed"))
-		qtv_printf ("%s: %s\n", sv->name, str);
-	str = Info_ValueForKey (sv->info, "*version");
-	qtv_printf ("%s: QW %s\n", sv->name, str);
-	str = Info_ValueForKey (sv->info, "*qf_version");
-	if (str[0])
-		qtv_printf ("%s: QuakeForge %s\n", sv->name, str);
-	qtv_printf ("%s: gamedir: %s\n", sv->name, sv->gamedir);
-	str = Info_ValueForKey (sv->info, "map");
-	qtv_printf ("%s: (%s) %s\n", sv->name, str, sv->message);
-
-	MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-	MSG_WriteString (&sv->netchan.message,
-					 va ("soundlist %i %i", sv->spawncount, 0));
-	sv->next_run = realtime;
-}
-
-static void
-qtv_soundlist (server_t *sv)
-{
-	int         numsounds = MSG_ReadByte (net_message);
-	int         n;
-	const char *str;
-
-	for (;;) {
-		str = MSG_ReadString (net_message);
-		if (!str[0])
-			break;
-		//qtv_printf ("%s\n", str);
-		numsounds++;
-		if (numsounds == MAX_SOUNDS) {
-			while (str[0])
-				str = MSG_ReadString (net_message);
-			MSG_ReadByte (net_message);
-			return;
-		}
-		// save sound name
-	}
-	n = MSG_ReadByte (net_message);
-	if (n) {
-		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-		MSG_WriteString (&sv->netchan.message,
-						 va ("soundlist %d %d", sv->spawncount, n));
-	} else {
-		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-		MSG_WriteString (&sv->netchan.message,
-						 va ("modellist %d %d", sv->spawncount, 0));
-	}
-	sv->next_run = realtime;
-}
-
-static void
-qtv_modellist (server_t *sv)
-{
-	int         nummodels = MSG_ReadByte (net_message);
-	int         n;
-	const char *str;
-
-	for (;;) {
-		str = MSG_ReadString (net_message);
-		if (!str[0])
-			break;
-		//qtv_printf ("%s\n", str);
-		nummodels++;
-		if (nummodels == MAX_SOUNDS) {
-			while (str[0])
-				str = MSG_ReadString (net_message);
-			MSG_ReadByte (net_message);
-			return;
-		}
-		// save sound name
-	}
-	n = MSG_ReadByte (net_message);
-	if (n) {
-		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-		MSG_WriteString (&sv->netchan.message,
-						 va ("modellist %d %d", sv->spawncount, n));
-	} else {
-		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-		MSG_WriteString (&sv->netchan.message,
-						 va ("prespawn %d 0 0", sv->spawncount));
-	}
-	sv->next_run = realtime;
-}
-
-static void
-qtv_cmd_f (server_t *sv)
-{
-	if (Cmd_Argc () > 1) {
-		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-		SZ_Print (&sv->netchan.message, Cmd_Args (1));
-	}
-	sv->next_run = realtime;
-}
-
-static void
-qtv_skins_f (server_t *sv)
-{
-	// we don't actually bother checking skins here, but this is a good way
-	// to get everything ready at the last miniute before we start getting
-	// actual in-game update messages
-	MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
-	MSG_WriteString (&sv->netchan.message, va ("begin %d", sv->spawncount));
-	sv->next_run = realtime;
-	sv->connected = 2;
-	sv->delta = -1;
-}
-
-typedef struct {
-	const char *name;
-	void      (*func) (server_t *sv);
-} svcmd_t;
-
-svcmd_t svcmds[] = {
-	{"cmd",			qtv_cmd_f},
-	{"skins",		qtv_skins_f},
-
-	{0,				0},
-};
-
-static void
-qtv_stringcmd_f (server_t *sv)
-{
-	svcmd_t    *c;
-	const char *name;
-
-	COM_TokenizeString (MSG_ReadString (net_message), qtv_args);
-	cmd_args = qtv_args;
-	name = Cmd_Argv (0);
-
-	for (c = svcmds; c->name; c++)
-		if (strcmp (c->name, name) == 0)
-			break;
-	if (!c->name) {
-		qtv_printf ("Bad QTV command: %s\n", name);
-		return;
-	}
-	c->func (sv);
-}
-
 static void
 qtv_p_signon_f (server_t *sv, int len)
 {
-	int         c, start;
-	vec3_t      v, a;
+	sizebuf_t   sub_message_buf;
+	qmsg_t      sub_message;
 
-	start = net_message->readcount;
-	while (net_message->readcount - start < len) {
-		c = MSG_ReadByte (net_message);
-		if (c == -1)
-			break;
-		//qtv_printf ("svc: %d\n", c);
-		switch (c) {
-			case svc_serverdata:
-				qtv_serverdata (sv);
-				break;
-			case svc_stufftext:
-				qtv_stringcmd_f (sv);
-				break;
-			case svc_soundlist:
-				qtv_soundlist (sv);
-				break;
-			case svc_modellist:
-				qtv_modellist (sv);
-				break;
-			case svc_spawnstaticsound:
-				MSG_ReadCoordV (net_message, v);
-				MSG_ReadByte (net_message);
-				MSG_ReadByte (net_message);
-				MSG_ReadByte (net_message);
-				break;
-			case svc_spawnbaseline:
-				MSG_ReadShort (net_message);
-			case svc_spawnstatic:
-				MSG_ReadByte (net_message);
-				MSG_ReadByte (net_message);
-				MSG_ReadByte (net_message);
-				MSG_ReadByte (net_message);
-				MSG_ReadCoordAngleV (net_message, v, a);
-				break;
-			case svc_lightstyle:
-				MSG_ReadByte (net_message);
-				MSG_ReadString (net_message);
-				break;
-			case svc_updatestatlong:
-				MSG_ReadByte (net_message);
-				MSG_ReadLong (net_message);
-				break;
-			case svc_updatestat:
-				MSG_ReadByte (net_message);
-				MSG_ReadByte (net_message);
-				break;
-			default:
-				qtv_printf ("unknown svc: %d\n", c);
-				return;
-		}
-	}
-}
+	memset (&sub_message_buf, 0, sizeof (sub_message_buf));
+	memset (&sub_message, 0, sizeof (sub_message));
+	sub_message.message = &sub_message_buf;
 
-static void
-qtv_parse_delta (server_t *sv, qmsg_t *msg, int bits)
-{
-	bits &= ~511;
+	sub_message_buf.cursize = len;
+	sub_message_buf.maxsize = sub_message_buf.cursize;
+	sub_message_buf.data = net_message->message->data;
+	sub_message_buf.data += net_message->readcount;
+	sub_message.readcount = 0;
+	sub_message.badread = 0;
+	net_message->readcount += sub_message_buf.cursize;
 
-	if (bits & U_MOREBITS)
-		bits |= MSG_ReadByte (msg);
-	if (bits & U_EXTEND1) {
-		bits |= MSG_ReadByte (msg) << 16;
-		if (bits & U_EXTEND2)
-			bits |= MSG_ReadByte (msg) << 24;
-	}
-	if (bits & U_MODEL)
-		MSG_ReadByte (msg);
-	if (bits & U_FRAME)
-		MSG_ReadByte (msg);
-	if (bits & U_COLORMAP)
-		MSG_ReadByte (msg);
-	if (bits & U_SKIN)
-		MSG_ReadByte (msg);
-	if (bits & U_EFFECTS)
-		MSG_ReadByte (msg);
-	if (bits & U_ORIGIN1)
-		MSG_ReadCoord (msg);
-	if (bits & U_ANGLE1)
-		MSG_ReadAngle (msg);
-	if (bits & U_ORIGIN2)
-		MSG_ReadCoord (msg);
-	if (bits & U_ANGLE2)
-		MSG_ReadAngle (msg);
-	if (bits & U_ORIGIN3)
-		MSG_ReadCoord (msg);
-	if (bits & U_ANGLE3)
-		MSG_ReadAngle (msg);
-	if (bits & U_SOLID) {
-		// FIXME
-	}
-	if (!(bits & U_EXTEND1))
-		return;
-	if (bits & U_ALPHA)
-		MSG_ReadByte (msg);
-	if (bits & U_SCALE)
-		MSG_ReadByte (msg);
-	if (bits & U_EFFECTS2)
-		MSG_ReadByte (msg);
-	if (bits & U_GLOWSIZE)
-		MSG_ReadByte (msg);
-	if (bits & U_GLOWCOLOR)
-		MSG_ReadByte (msg);
-	if (bits & U_COLORMOD)
-		MSG_ReadByte (msg);
-	if (!(bits & U_EXTEND1))
-		return;
-	if (bits & U_FRAME2)
-		MSG_ReadByte (msg);
-}
-
-static void
-qtv_packetentities (server_t *sv, qmsg_t *msg, int delta)
-{
-	unsigned short word;
-	int         newnum, oldnum;
-
-	newnum = oldnum = 0;
-	if (delta) {
-		/*from =*/ MSG_ReadByte (msg);
-	} else {
-	}
-	sv->delta = sv->netchan.incoming_sequence;
-	while (1) {
-		word = (unsigned short) MSG_ReadShort (msg);
-		if (msg->badread) {     // something didn't parse right...
-			qtv_printf ("msg_badread in packetentities\n");
-			return;
-		}
-		if (!word) {
-			// copy rest of ents from old packet
-			break;
-		}
-		if (newnum < oldnum) {
-			if (word & U_REMOVE) {
-				continue;
-			}
-			qtv_parse_delta (sv, msg, word);
-			continue;
-		}
-		if (newnum == oldnum) {
-			if (word & U_REMOVE) {
-				continue;
-			}
-			qtv_parse_delta (sv, msg, word);
-			continue;
-		}
-	}
+	sv_parse (sv, &sub_message, 1);
 }
 
 static void
 qtv_parse_updates (server_t *sv, int reliable, int len)
 {
-	int         c, msec, type, to, start;
+	int         msec, type, to, start;
 	sizebuf_t   sub_message_buf;
 	qmsg_t      sub_message;
-	vec3_t      v;
 
 	memset (&sub_message_buf, 0, sizeof (sub_message_buf));
 	memset (&sub_message, 0, sizeof (sub_message));
@@ -459,149 +154,7 @@ qtv_parse_updates (server_t *sv, int reliable, int len)
 		sub_message.badread = 0;
 		net_message->readcount += sub_message_buf.cursize;
 
-		while (1) {
-			c = MSG_ReadByte (&sub_message);
-			if (c == -1)
-				break;
-			//qtv_printf ("qtv_parse_updates: svc: %d\n", c);
-			switch (c) {
-				default:
-					qtv_printf ("qtv_parse_updates: unknown svc: %d\n", c);
-					return;
-				case svc_nop:
-					break;
-				case svc_updatestat:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadByte (&sub_message);
-					break;
-				case svc_setview:
-					break;
-				case svc_sound:
-					c = MSG_ReadShort (&sub_message);
-					if (c & SND_VOLUME)
-						MSG_ReadByte (&sub_message);
-					if (c & SND_ATTENUATION)
-						MSG_ReadByte (&sub_message);
-					MSG_ReadByte (&sub_message);
-					MSG_ReadCoordV (&sub_message, v);
-					break;
-				case svc_print:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_stufftext:
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_setangle:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadAngleV(&sub_message, v);
-					break;
-				case svc_lightstyle:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_updatefrags:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadShort (&sub_message);
-					break;
-				case svc_stopsound:
-					MSG_ReadShort (&sub_message);
-					break;
-				case svc_damage:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadByte (&sub_message);
-					MSG_ReadCoordV (&sub_message, v);
-					break;
-				case svc_temp_entity:
-					//XXX
-					break;
-				case svc_setpause:
-					MSG_ReadByte (&sub_message);
-					break;
-				case svc_centerprint:
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_killedmonster:
-					//XXX
-					break;
-				case svc_foundsecret:
-					//XXX
-					break;
-				case svc_intermission:
-					MSG_ReadCoordV (&sub_message, v);
-					MSG_ReadAngleV (&sub_message, v);
-					break;
-				case svc_finale:
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_cdtrack:
-					MSG_ReadByte (&sub_message);
-					break;
-				case svc_sellscreen:
-					//XXX
-					break;
-				case svc_smallkick:
-					//XXX
-					break;
-				case svc_bigkick:
-					//XXX
-					break;
-				case svc_updateping:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadShort (&sub_message);
-					break;
-				case svc_updateentertime:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadFloat (&sub_message);
-					break;
-				case svc_updatestatlong:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadLong (&sub_message);
-					break;
-				case svc_muzzleflash:
-					MSG_ReadShort (&sub_message);
-					break;
-				case svc_updateuserinfo:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadLong (&sub_message);
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_playerinfo:
-					//XXX
-					break;
-				case svc_nails:
-					//XXX
-					break;
-				case svc_packetentities:
-					qtv_packetentities (sv, &sub_message, 0);
-					break;
-				case svc_deltapacketentities:
-					qtv_packetentities (sv, &sub_message, 1);
-					break;
-				case svc_maxspeed:
-					MSG_ReadFloat (&sub_message);
-					break;
-				case svc_entgravity:
-					MSG_ReadFloat (&sub_message);
-					break;
-				case svc_setinfo:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadString (&sub_message);
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_serverinfo:
-					MSG_ReadString (&sub_message);
-					MSG_ReadString (&sub_message);
-					break;
-				case svc_updatepl:
-					MSG_ReadByte (&sub_message);
-					MSG_ReadByte (&sub_message);
-					break;
-				case svc_nails2:
-					//XXX
-					break;
-			}
-		}
+		sv_parse (sv, &sub_message, reliable);
 	}
 }
 
@@ -680,7 +233,7 @@ server_handler (connection_t *con, void *object)
 				qtv_printf ("%s: disconnected\n", sv->name);
 				break;
 			case qtv_stringcmd:
-				qtv_stringcmd_f (sv);
+				sv_stringcmd (sv, net_message);
 				break;
 			case qtv_packet:
 				qtv_packet_f (sv);
diff --git a/qtv/source/sv_parse.c b/qtv/source/sv_parse.c
new file mode 100644
index 000000000..7389af62d
--- /dev/null
+++ b/qtv/source/sv_parse.c
@@ -0,0 +1,503 @@
+/*
+	#FILENAME#
+
+	#DESCRIPTION#
+
+	Copyright (C) 2004 #AUTHOR#
+
+	Author: #AUTHOR#
+	Date: #DATE#
+
+	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:
+
+		Free Software Foundation, Inc.
+		59 Temple Place - Suite 330
+		Boston, MA  02111-1307, USA
+
+*/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+static __attribute__ ((unused)) const char rcsid[] =
+	"$Id$";
+
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "QF/cmd.h"
+#include "QF/console.h"
+#include "QF/dstring.h"
+#include "QF/hash.h"
+#include "QF/idparse.h"
+#include "QF/info.h"
+#include "QF/msg.h"
+#include "QF/qendian.h"
+#include "QF/sys.h"
+#include "QF/va.h"
+
+#include "qw/protocol.h"
+
+#include "connection.h"
+#include "qtv.h"
+#include "server.h"
+
+static void
+qtv_serverdata (server_t *sv, qmsg_t *msg)
+{
+	const char *str;
+
+	sv->ver = MSG_ReadLong (msg);
+	sv->spawncount = MSG_ReadLong (msg);
+	sv->gamedir = strdup (MSG_ReadString (msg));
+
+	sv->message = strdup (MSG_ReadString (msg));
+	sv->movevars.gravity = MSG_ReadFloat (msg);
+	sv->movevars.stopspeed = MSG_ReadFloat (msg);
+	sv->movevars.maxspeed = MSG_ReadFloat (msg);
+	sv->movevars.spectatormaxspeed = MSG_ReadFloat (msg);
+	sv->movevars.accelerate = MSG_ReadFloat (msg);
+	sv->movevars.airaccelerate = MSG_ReadFloat (msg);
+	sv->movevars.wateraccelerate = MSG_ReadFloat (msg);
+	sv->movevars.friction = MSG_ReadFloat (msg);
+	sv->movevars.waterfriction = MSG_ReadFloat (msg);
+	sv->movevars.entgravity = MSG_ReadFloat (msg);
+
+	sv->cdtrack = MSG_ReadByte (msg);
+	sv->sounds = MSG_ReadByte (msg);
+
+	COM_TokenizeString (MSG_ReadString (msg), qtv_args);
+	cmd_args = qtv_args;
+	Info_Destroy (sv->info);
+	sv->info = Info_ParseString (Cmd_Argv (1), MAX_SERVERINFO_STRING, 0);
+
+	str = Info_ValueForKey (sv->info, "hostname");
+	if (strcmp (str, "unnamed"))
+		qtv_printf ("%s: %s\n", sv->name, str);
+	str = Info_ValueForKey (sv->info, "*version");
+	qtv_printf ("%s: QW %s\n", sv->name, str);
+	str = Info_ValueForKey (sv->info, "*qf_version");
+	if (str[0])
+		qtv_printf ("%s: QuakeForge %s\n", sv->name, str);
+	qtv_printf ("%s: gamedir: %s\n", sv->name, sv->gamedir);
+	str = Info_ValueForKey (sv->info, "map");
+	qtv_printf ("%s: (%s) %s\n", sv->name, str, sv->message);
+
+	MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+	MSG_WriteString (&sv->netchan.message,
+					 va ("soundlist %i %i", sv->spawncount, 0));
+	sv->next_run = realtime;
+}
+
+static void
+qtv_soundlist (server_t *sv, qmsg_t *msg)
+{
+	int         numsounds = MSG_ReadByte (msg);
+	int         n;
+	const char *str;
+
+	for (;;) {
+		str = MSG_ReadString (msg);
+		if (!str[0])
+			break;
+		//qtv_printf ("%s\n", str);
+		numsounds++;
+		if (numsounds == MAX_SOUNDS) {
+			while (str[0])
+				str = MSG_ReadString (msg);
+			MSG_ReadByte (msg);
+			return;
+		}
+		// save sound name
+	}
+	n = MSG_ReadByte (msg);
+	if (n) {
+		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+		MSG_WriteString (&sv->netchan.message,
+						 va ("soundlist %d %d", sv->spawncount, n));
+	} else {
+		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+		MSG_WriteString (&sv->netchan.message,
+						 va ("modellist %d %d", sv->spawncount, 0));
+	}
+	sv->next_run = realtime;
+}
+
+static void
+qtv_modellist (server_t *sv, qmsg_t *msg)
+{
+	int         nummodels = MSG_ReadByte (msg);
+	int         n;
+	const char *str;
+
+	for (;;) {
+		str = MSG_ReadString (msg);
+		if (!str[0])
+			break;
+		//qtv_printf ("%s\n", str);
+		nummodels++;
+		if (nummodels == MAX_SOUNDS) {
+			while (str[0])
+				str = MSG_ReadString (msg);
+			MSG_ReadByte (msg);
+			return;
+		}
+		// save sound name
+	}
+	n = MSG_ReadByte (msg);
+	if (n) {
+		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+		MSG_WriteString (&sv->netchan.message,
+						 va ("modellist %d %d", sv->spawncount, n));
+	} else {
+		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+		MSG_WriteString (&sv->netchan.message,
+						 va ("prespawn %d 0 0", sv->spawncount));
+	}
+	sv->next_run = realtime;
+}
+
+static void
+qtv_cmd_f (server_t *sv)
+{
+	if (Cmd_Argc () > 1) {
+		MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+		SZ_Print (&sv->netchan.message, Cmd_Args (1));
+	}
+	sv->next_run = realtime;
+}
+
+static void
+qtv_skins_f (server_t *sv)
+{
+	// we don't actually bother checking skins here, but this is a good way
+	// to get everything ready at the last miniute before we start getting
+	// actual in-game update messages
+	MSG_WriteByte (&sv->netchan.message, qtv_stringcmd);
+	MSG_WriteString (&sv->netchan.message, va ("begin %d", sv->spawncount));
+	sv->next_run = realtime;
+	sv->connected = 2;
+	sv->delta = -1;
+}
+
+typedef struct {
+	const char *name;
+	void      (*func) (server_t *sv);
+} svcmd_t;
+
+svcmd_t svcmds[] = {
+	{"cmd",			qtv_cmd_f},
+	{"skins",		qtv_skins_f},
+
+	{0,				0},
+};
+
+void
+sv_stringcmd (server_t *sv, qmsg_t *msg)
+{
+	svcmd_t    *c;
+	const char *name;
+
+	COM_TokenizeString (MSG_ReadString (msg), qtv_args);
+	cmd_args = qtv_args;
+	name = Cmd_Argv (0);
+
+	for (c = svcmds; c->name; c++)
+		if (strcmp (c->name, name) == 0)
+			break;
+	if (!c->name) {
+		qtv_printf ("Bad QTV command: %s\n", name);
+		return;
+	}
+	c->func (sv);
+}
+
+static void
+qtv_parse_delta (server_t *sv, qmsg_t *msg, int bits)
+{
+	bits &= ~511;
+
+	if (bits & U_MOREBITS)
+		bits |= MSG_ReadByte (msg);
+	if (bits & U_EXTEND1) {
+		bits |= MSG_ReadByte (msg) << 16;
+		if (bits & U_EXTEND2)
+			bits |= MSG_ReadByte (msg) << 24;
+	}
+	if (bits & U_MODEL)
+		MSG_ReadByte (msg);
+	if (bits & U_FRAME)
+		MSG_ReadByte (msg);
+	if (bits & U_COLORMAP)
+		MSG_ReadByte (msg);
+	if (bits & U_SKIN)
+		MSG_ReadByte (msg);
+	if (bits & U_EFFECTS)
+		MSG_ReadByte (msg);
+	if (bits & U_ORIGIN1)
+		MSG_ReadCoord (msg);
+	if (bits & U_ANGLE1)
+		MSG_ReadAngle (msg);
+	if (bits & U_ORIGIN2)
+		MSG_ReadCoord (msg);
+	if (bits & U_ANGLE2)
+		MSG_ReadAngle (msg);
+	if (bits & U_ORIGIN3)
+		MSG_ReadCoord (msg);
+	if (bits & U_ANGLE3)
+		MSG_ReadAngle (msg);
+	if (bits & U_SOLID) {
+		// FIXME
+	}
+	if (!(bits & U_EXTEND1))
+		return;
+	if (bits & U_ALPHA)
+		MSG_ReadByte (msg);
+	if (bits & U_SCALE)
+		MSG_ReadByte (msg);
+	if (bits & U_EFFECTS2)
+		MSG_ReadByte (msg);
+	if (bits & U_GLOWSIZE)
+		MSG_ReadByte (msg);
+	if (bits & U_GLOWCOLOR)
+		MSG_ReadByte (msg);
+	if (bits & U_COLORMOD)
+		MSG_ReadByte (msg);
+	if (!(bits & U_EXTEND1))
+		return;
+	if (bits & U_FRAME2)
+		MSG_ReadByte (msg);
+}
+
+static void
+qtv_packetentities (server_t *sv, qmsg_t *msg, int delta)
+{
+	unsigned short word;
+	int         newnum, oldnum;
+
+	newnum = oldnum = 0;
+	if (delta) {
+		/*from =*/ MSG_ReadByte (msg);
+	} else {
+	}
+	sv->delta = sv->netchan.incoming_sequence;
+	while (1) {
+		word = (unsigned short) MSG_ReadShort (msg);
+		if (msg->badread) {     // something didn't parse right...
+			qtv_printf ("msg_badread in packetentities\n");
+			return;
+		}
+		if (!word) {
+			// copy rest of ents from old packet
+			break;
+		}
+		if (newnum < oldnum) {
+			if (word & U_REMOVE) {
+				continue;
+			}
+			qtv_parse_delta (sv, msg, word);
+			continue;
+		}
+		if (newnum == oldnum) {
+			if (word & U_REMOVE) {
+				continue;
+			}
+			qtv_parse_delta (sv, msg, word);
+			continue;
+		}
+	}
+}
+
+void
+sv_parse (server_t *sv, qmsg_t *msg, int reliable)
+{
+	int         c;
+	vec3_t      v, a;
+
+	while (1) {
+		c = MSG_ReadByte (msg);
+		if (c == -1)
+			break;
+		//qtv_printf ("sv_parse: svc: %d\n", c);
+		switch (c) {
+			default:
+				qtv_printf ("sv_parse: unknown svc: %d\n", c);
+				return;
+			case svc_nop:
+				break;
+			case svc_updatestat:
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				break;
+			case svc_setview:
+				break;
+			case svc_sound:
+				c = MSG_ReadShort (msg);
+				if (c & SND_VOLUME)
+					MSG_ReadByte (msg);
+				if (c & SND_ATTENUATION)
+					MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				MSG_ReadCoordV (msg, v);
+				break;
+			case svc_print:
+				MSG_ReadByte (msg);
+				MSG_ReadString (msg);
+				break;
+			case svc_setangle:
+				MSG_ReadByte (msg);
+				MSG_ReadAngleV(msg, v);
+				break;
+			case svc_updatefrags:
+				MSG_ReadByte (msg);
+				MSG_ReadShort (msg);
+				break;
+			case svc_stopsound:
+				MSG_ReadShort (msg);
+				break;
+			case svc_damage:
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				MSG_ReadCoordV (msg, v);
+				break;
+			case svc_temp_entity:
+				//XXX
+				break;
+			case svc_setpause:
+				MSG_ReadByte (msg);
+				break;
+			case svc_centerprint:
+				MSG_ReadString (msg);
+				break;
+			case svc_killedmonster:
+				//XXX
+				break;
+			case svc_foundsecret:
+				//XXX
+				break;
+			case svc_intermission:
+				MSG_ReadCoordV (msg, v);
+				MSG_ReadAngleV (msg, v);
+				break;
+			case svc_finale:
+				MSG_ReadString (msg);
+				break;
+			case svc_cdtrack:
+				MSG_ReadByte (msg);
+				break;
+			case svc_sellscreen:
+				//XXX
+				break;
+			case svc_smallkick:
+				//XXX
+				break;
+			case svc_bigkick:
+				//XXX
+				break;
+			case svc_updateping:
+				MSG_ReadByte (msg);
+				MSG_ReadShort (msg);
+				break;
+			case svc_updateentertime:
+				MSG_ReadByte (msg);
+				MSG_ReadFloat (msg);
+				break;
+			case svc_updatestatlong:
+				MSG_ReadByte (msg);
+				MSG_ReadLong (msg);
+				break;
+			case svc_muzzleflash:
+				MSG_ReadShort (msg);
+				break;
+			case svc_updateuserinfo:
+				MSG_ReadByte (msg);
+				MSG_ReadLong (msg);
+				MSG_ReadString (msg);
+				break;
+			case svc_playerinfo:
+				//XXX
+				break;
+			case svc_nails:
+				//XXX
+				break;
+			case svc_packetentities:
+				qtv_packetentities (sv, msg, 0);
+				break;
+			case svc_deltapacketentities:
+				qtv_packetentities (sv, msg, 1);
+				break;
+			case svc_maxspeed:
+				MSG_ReadFloat (msg);
+				break;
+			case svc_entgravity:
+				MSG_ReadFloat (msg);
+				break;
+			case svc_setinfo:
+				MSG_ReadByte (msg);
+				MSG_ReadString (msg);
+				MSG_ReadString (msg);
+				break;
+			case svc_serverinfo:
+				MSG_ReadString (msg);
+				MSG_ReadString (msg);
+				break;
+			case svc_updatepl:
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				break;
+			case svc_nails2:
+				//XXX
+				break;
+			case svc_serverdata:
+				qtv_serverdata (sv, msg);
+				break;
+			case svc_stufftext:
+				sv_stringcmd (sv, msg);
+				break;
+			case svc_soundlist:
+				qtv_soundlist (sv, msg);
+				break;
+			case svc_modellist:
+				qtv_modellist (sv, msg);
+				break;
+			case svc_spawnstaticsound:
+				MSG_ReadCoordV (msg, v);
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				break;
+			case svc_spawnbaseline:
+				MSG_ReadShort (msg);
+			case svc_spawnstatic:
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				MSG_ReadByte (msg);
+				MSG_ReadCoordAngleV (msg, v, a);
+				break;
+			case svc_lightstyle:
+				MSG_ReadByte (msg);
+				MSG_ReadString (msg);
+				break;
+		}
+	}
+}