/*
        net_packetlog.c

        packet logging/parsing - for debugging and educational purposes

        **EXPERIMENTAL**

        Copyright (C) 2000 Jukka Sorjonen <jukka.sorjonen@asikkala.fi>

	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

#ifdef HAVE_IO_H
# include <io.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#include <ctype.h>
#include <stdarg.h>

#include "QF/cmd.h"
#include "QF/cvar.h"
#include "QF/msg.h"
#include "QF/quakefs.h"
#include "QF/sys.h"
#include "QF/va.h"

#include "compat.h"
#include "netchan.h"
#include "qw/protocol.h"
#include "server.h"

cvar_t     *net_packetlog;
cvar_t     *net_loglevel;

// note: this is SUPPOSED to be duplicate, like many others
const char *svc_string[] = {
	"svc_bad",
	"svc_nop",
	"svc_disconnect",
	"svc_updatestat",
	"svc_version",						// [long] server version
	"svc_setview",						// [short] entity number
	"svc_sound",						// <see code>
	"svc_time",							// [float] server time
	"svc_print",						// [string] null terminated string
	"svc_stufftext",					// [string] stuffed into client's
										// console buffer the string
										// should be \n terminated
	"svc_setangle",						// [vec3] set the view angle to this
										// absolute value
	"svc_serverdata",					// [long] version ...
	"svc_lightstyle",					// [byte] [string]
	"svc_updatename",					// [byte] [string]
	"svc_updatefrags",					// [byte] [short]
	"svc_clientdata",					// <shortbits + data>
	"svc_stopsound",					// <see code>
	"svc_updatecolors",					// [byte] [byte]
	"svc_particle",						// [vec3] <variable>
	"svc_damage",						// [byte] impact [byte] blood [vec3]
										// from
	"svc_spawnstatic",
	"svc_spawnbinary",
	"svc_spawnbaseline",
	"svc_temp_entity",					// <variable>
	"svc_setpause",
	"svc_signonnum",
	"svc_centerprint",
	"svc_killedmonster",
	"svc_foundsecret",
	"svc_spawnstaticsound",
	"svc_intermission",
	"svc_finale",						// [string] music [string] text
	"svc_cdtrack",						// [byte] track [byte] looptrack
	"svc_sellscreen",
	"svc_smallkick",					// Quake svc_cutscene
	"svc_bigkick",
	"svc_updateping",
	"svc_updateentertime",
	"svc_updatestatlong",
	"svc_muzzleflash",
	"svc_updateuserinfo",
	"svc_download",
	"svc_playerinfo",
	"svc_nails",
	"svc_chokecount",
	"svc_modellist",
	"svc_soundlist",
	"svc_packetentities",
	"svc_deltapacketentities",
	"svc_maxspeed",
	"svc_entgravity",
	"svc_setinfo",
	"svc_serverinfo",
	"svc_updatepl",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL",
	"NEW PROTOCOL"
};

const char *clc_string[] = {
	"clc_bad",
	"clc_nop",
	"clc_disconnect",
	"clc_move",
	"clc_stringcmd",
	"clc_delta",
	"clc_tmove",
	"clc_upload"
};

#ifndef svc_spawnbinary
# define svc_spawnbinary 21
#endif

// Quake1, obsolete for QW
#define svc_time                7		// [float] server time
#define svc_updatename          13		// [byte] [string]
#define svc_version             4		// [long] server version
#define svc_clientdata          15		// <shortbits + data>
#define svc_updatecolors        17		// [byte] [byte]
#define svc_particle            18		// [vec3] <variable>
#define svc_signonnum           25		// [byte]  used for the signon
										// sequence
static QFile      *_stdout;
static QFile      *Net_PacketLog;
static const char **Net_sound_precache;
static sizebuf_t   _packet;
static qmsg_t      packet = {0, 0, &_packet};

static int
Net_LogStart (const char *fname)
{
	Sys_Printf ("Opening packet logfile: %s\n", fname);
	Net_PacketLog = QFS_Open (fname, "at");
	if (!Net_PacketLog)
		return -1;
	return 0;
}

void
Net_LogStop (void)
{
	if (Net_PacketLog)
		Qclose (Net_PacketLog);
	Net_PacketLog = NULL;
}

static void
hex_dump_buf (unsigned char *buf, int len)
{
	int         pos = 0, llen, i;

	while (pos < len) {
		llen = (len - pos < 16 ? len - pos : 16);
		Net_LogPrintf ("%08x: ", pos);
		for (i = 0; i < llen; i++)
			Net_LogPrintf ("%02x ", buf[pos + i]);
		for (i = 0; i < 16 - llen; i++)
			Net_LogPrintf ("   ");
		Net_LogPrintf (" | ");

		for (i = 0; i < llen; i++)
			Net_LogPrintf ("%c", isprint (buf[pos + i]) ? buf[pos + i] : '.');
		for (i = 0; i < 16 - llen; i++)
			Net_LogPrintf (" ");
		Net_LogPrintf ("\n");
		pos += llen;
	}
}
/*
static void
ascii_dump_buf (unsigned char *buf, int len)
{
	int         pos = 0, llen, i;

	while (pos < len) {
		llen = (len - pos < 60 ? len - pos : 60);
		Net_LogPrintf ("%08x: ", pos);
		for (i = 0; i < llen; i++)
			Net_LogPrintf ("%c", isprint (buf[pos + i]) ? buf[pos + i] : '.');
		Net_LogPrintf ("\n");
		pos += llen;
	}
}
*/
void
Log_Incoming_Packet (const byte *p, int len, int has_sequence, int is_server)
{
	if (!net_loglevel->int_val)
		return;

	if (is_server) {
		Net_LogPrintf ("\n<<<<<<<<<<<<<<<<<<<<< client to server %d bytes: "
					   "<<<<<<<<<<<<<<<<<<<<<<<<\n", len);
		if (net_loglevel->int_val != 3)
			hex_dump_buf ((unsigned char *) p, len);
		if (net_loglevel->int_val > 1)
			Analyze_Client_Packet (p, len, has_sequence);
	} else {
		Net_LogPrintf ("\n>>>>>>>>>>>>>>>>>>>>> server to client %d bytes: "
					   ">>>>>>>>>>>>>>>>>>>>>>>>\n", len);
		if (net_loglevel->int_val != 3)
			hex_dump_buf ((unsigned char *) p, len);
		if (net_loglevel->int_val > 1)
			Analyze_Server_Packet (p, len, has_sequence);
	}
	return;
}

void
Log_Outgoing_Packet (const byte *p, int len, int has_sequence, int is_server)
{
	if (!net_loglevel->int_val)
		return;

	if (is_server) {
		Net_LogPrintf ("\n>>>>>>>>>>>>>>>>>>>>> server to client %d bytes: "
					   ">>>>>>>>>>>>>>>>>>>>>>>>\n", len);
		if (net_loglevel->int_val != 3)
			hex_dump_buf ((unsigned char *) p, len);
		if (net_loglevel->int_val > 1)
			Analyze_Server_Packet (p, len, has_sequence);
	} else {
		Net_LogPrintf ("\n<<<<<<<<<<<<<<<<<<<<< client to server %d bytes: "
					   "<<<<<<<<<<<<<<<<<<<<<<<<\n", len);
		if (net_loglevel->int_val != 3)
			hex_dump_buf ((unsigned char *) p, len);
		if (net_loglevel->int_val > 1)
			Analyze_Client_Packet (p, len, has_sequence);
	}
	return;
}

static void
Log_Delta (int bits)
{
	entity_state_t to;
	int i;

	// set everything to the state we are delta'ing from

	to.number = bits & 511;
	to.frame = 0;
	to.effects = 0;
	bits &= ~511;

	if (bits & U_MOREBITS) {			// read in the low order bits
		i = MSG_ReadByte (&packet);
		bits |= i;
	}

	// LordHavoc: Endy neglected to mark this as being part of the QSG
	// version 2 stuff...
	if (bits & U_EXTEND1) {
		bits |= MSG_ReadByte (&packet) << 16;
		if (bits & U_EXTEND2)
			bits |= MSG_ReadByte (&packet) << 24;
	}

	to.flags = bits;

	if (bits & U_MODEL)
		Net_LogPrintf (" MdlIdx: %d", MSG_ReadByte (&packet));
	if (bits & U_FRAME) {
		to.frame = MSG_ReadByte (&packet);
		Net_LogPrintf (" Frame: %d", to.frame);
	}
	if (bits & U_COLORMAP)
		Net_LogPrintf (" Colormap: %d", MSG_ReadByte (&packet));
	if (bits & U_SKIN)
		Net_LogPrintf (" Skinnum: %d", MSG_ReadByte (&packet));
	if (bits & U_EFFECTS) {
		to.effects = MSG_ReadByte (&packet);
		Net_LogPrintf (" Effects: %d", to.effects);
	}
	if (bits & U_ORIGIN1)
		Net_LogPrintf (" X: %f", MSG_ReadCoord (&packet));
	if (bits & U_ANGLE1)
		Net_LogPrintf (" Pitch: %f", MSG_ReadAngle (&packet));
	if (bits & U_ORIGIN2)
		Net_LogPrintf (" Y: %f", MSG_ReadCoord (&packet));
	if (bits & U_ANGLE2)
		Net_LogPrintf (" Yaw: %f", MSG_ReadAngle (&packet));
	if (bits & U_ORIGIN3)
		Net_LogPrintf (" Z: %f", MSG_ReadCoord (&packet));
	if (bits & U_ANGLE3)
		Net_LogPrintf (" Roll: %f", MSG_ReadAngle (&packet));

// Ender (QSG - Begin)
	if (bits & U_ALPHA)
		Net_LogPrintf (" Alpha: %d", MSG_ReadByte (&packet));
	if (bits & U_SCALE)
		Net_LogPrintf (" Scale: %d", MSG_ReadByte (&packet));
	if (bits & U_EFFECTS2)
		Net_LogPrintf (" U_EFFECTS2: %d", (to.effects & 0xFF) |
					   (MSG_ReadByte (&packet) << 8));
	if (bits & U_GLOWSIZE)
		Net_LogPrintf (" GlowSize: %d", MSG_ReadByte (&packet));
	if (bits & U_GLOWCOLOR)
		Net_LogPrintf (" ColorGlow: %d", MSG_ReadByte (&packet));
	if (bits & U_COLORMOD)
		Net_LogPrintf (" Colormod: %d", MSG_ReadByte (&packet));
	if (bits & U_FRAME2)
		Net_LogPrintf (" Uframe2: %d", ((to.frame & 0xFF) |
										(MSG_ReadByte (&packet) << 8)));
// Ender (QSG - End)

	return;
}


static void
Parse_Server_Packet (int has_sequence)
{
	const char *s;
	int         c = 0, i, ii, iii, mask1, mask2;
	long        seq1 = 0, seq2;

	if (has_sequence) {
		seq1 = MSG_ReadLong (&packet);
		if (packet.badread)
			return;
	}

	if (seq1 == -1) {
		Net_LogPrintf ("Special Packet");
	} else {
		if (has_sequence) {
			seq2 = MSG_ReadLong (&packet);
			// FIXME: display seqs right when reliable
			Net_LogPrintf ("\nSeq: %ld Ack: %ld ", seq1 & 0x7FFFFFFF,
						   seq2 & 0x7FFFFFFF);

			if ((seq1 >> 31) & 0x01)
				Net_LogPrintf ("SV_REL ");
			if ((seq2 >> 31) & 0x01)
				Net_LogPrintf ("SV_RELACK");
			Net_LogPrintf ("\n");
		}

		while (1) {
			if (packet.badread) {
				Net_LogPrintf ("*** BAD READ *** %s", svc_string[c]);
				break;
			}
			c = MSG_ReadByte (&packet);
			if (c == -1)
				break;
			Net_LogPrintf ("<%06x> [0x%02x] ", MSG_GetReadCount (&packet), c);

			if (c < 53)
				Net_LogPrintf ("%s: ", svc_string[c]);

			if (MSG_GetReadCount (&packet) > packet.message->cursize)
				return;

			switch (c) {
				case svc_bad:
					Net_LogPrintf (" - should not happen");
					break;
				case svc_nop:
					Net_LogPrintf (" No operation");
					break;
				case svc_disconnect:
					Net_LogPrintf (" <Quit>");
					break;
				case svc_updatestat:
					i = MSG_ReadByte (&packet);
					Net_LogPrintf (" index: %d value: %d", i, MSG_ReadByte
								   (&packet));
					break;
				case svc_version:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;
				case svc_setview:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;
				case svc_sound:
					i = MSG_ReadShort (&packet);
					Net_LogPrintf (": (%d) ", i);
					if (i & SND_VOLUME)
						Net_LogPrintf ("Volume %d ", MSG_ReadByte (&packet));
					if (i & SND_ATTENUATION)
						Net_LogPrintf ("Ann: %f",
									   (float) MSG_ReadByte (&packet) / 64.0);
					ii = MSG_ReadByte (&packet);

					Net_LogPrintf ("%d (%s) ", ii, Net_sound_precache[ii]);
					Net_LogPrintf ("Pos: ");
					for (ii = 0; ii < 3; ii++)
						Net_LogPrintf ("%f ", MSG_ReadCoord (&packet));
					Net_LogPrintf ("Ent: %d ", (i >> 3) & 1023);
					Net_LogPrintf ("Channel %d ", i & 7);
					break;
				case svc_time:
					Net_LogPrintf ("**QW OBSOLETE**\n");
					break;
				case svc_print:
					// FIXME: i==PRINT_CHAT
					Net_LogPrintf ("[%d] ", MSG_ReadByte (&packet));
					Net_LogPrintf ("%s", MSG_ReadString (&packet));
					break;
				case svc_stufftext:
					Net_LogPrintf ("%s", MSG_ReadString (&packet));
					break;
				case svc_setangle:
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%f ", MSG_ReadAngle (&packet));
					break;
				case svc_serverdata:
					Net_LogPrintf ("Ver: %d", MSG_ReadLong (&packet));
					Net_LogPrintf (" Client ID: %d", MSG_ReadLong (&packet));
					Net_LogPrintf (" Dir: %s", MSG_ReadString (&packet));
					Net_LogPrintf (" User ID: %d", MSG_ReadByte (&packet));
					Net_LogPrintf (" Map: %s", MSG_ReadString (&packet));
					for (i = 0; i < 10; i++)
						MSG_ReadFloat (&packet);
					break;

				case svc_lightstyle:
					i = MSG_ReadByte (&packet);
					if (i >= MAX_LIGHTSTYLES)
						return;
					Net_LogPrintf ("%d %s", i, MSG_ReadString (&packet));
					break;
				case svc_updatename:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;
				case svc_updatefrags:
					Net_LogPrintf ("player: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("frags: %d",
								   (short) MSG_ReadShort (&packet));
					break;
				case svc_clientdata:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;
				case svc_stopsound:
					Net_LogPrintf ("%d", MSG_ReadShort (&packet));
					break;

				case svc_updatecolors:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;
				case svc_particle:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;

				case svc_damage:
					// FIXME: parse damage
					Net_LogPrintf ("armor: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("health: %d from", MSG_ReadByte (&packet));
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%f%s", MSG_ReadCoord (&packet),
									   i < 2 ? "," : "");
					break;
				case svc_spawnstatic:
					Net_LogPrintf ("Model: %d", MSG_ReadByte (&packet));
					Net_LogPrintf (" Frame: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("Color: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("Skin: %d", MSG_ReadByte (&packet));
					for (i = 0; i < 3; i++) {
						Net_LogPrintf ("%d: %f ", i + 1,
									   MSG_ReadCoord (&packet));
						Net_LogPrintf ("%f", MSG_ReadAngle (&packet));
					}
					break;
				case svc_spawnbinary:
					Net_LogPrintf ("**OBSOLETE**");
					break;
				case svc_spawnbaseline:
					Net_LogPrintf ("%d", MSG_ReadShort (&packet));
					Net_LogPrintf (" idx: %d", MSG_ReadByte (&packet));
					Net_LogPrintf (" Frame: %d", MSG_ReadByte (&packet));
					Net_LogPrintf (" Colormap: %d", MSG_ReadByte (&packet));
					Net_LogPrintf (" Skin: %d", MSG_ReadByte (&packet));
					for (i = 0; i < 3; i++) {
						Net_LogPrintf (" %f", MSG_ReadCoord (&packet));
						Net_LogPrintf (" %f", MSG_ReadAngle (&packet));
					};
					break;
				case svc_temp_entity:
					i = MSG_ReadByte (&packet);
					switch (i) {
						case 0:
						case 1:
						case 3:
						case 4:
						case 7:
						case 8:
						case 10:
						case 11:
						case 13:
							Net_LogPrintf (" origin");
							for (i = 0; i < 3; i++)
								Net_LogPrintf (" %f", MSG_ReadCoord (&packet));
							break;
						case 5:
						case 6:
						case 9:
							Net_LogPrintf (" created by %d",
										   MSG_ReadShort (&packet));
							Net_LogPrintf (" origin:");
							for (i = 0; i < 3; i++)
								Net_LogPrintf ("%c%f", i ? ',' : ' ',
											   MSG_ReadCoord (&packet));
							Net_LogPrintf (" trace endpos:");
							for (i = 0; i < 3; i++)
								Net_LogPrintf ("%c%f", i ? ',' : ' ',
											   MSG_ReadCoord (&packet));
							break;
						case 2:
						case 12:
							Net_LogPrintf (" count: %d", MSG_ReadByte
										   (&packet));
							Net_LogPrintf (" origin:");
							for (i = 0; i < 3; i++)
								Net_LogPrintf ("%c%f", i ? ',' : ' ',
											   MSG_ReadCoord (&packet));
							break;
						default:
							Net_LogPrintf (" unknown value %d for tempentity",
										   i);
							break;
					}
					break;

				case svc_setpause:
					Net_LogPrintf (" %d", MSG_ReadByte (&packet));
					break;
				case svc_signonnum:
					Net_LogPrintf ("**QW OBSOLETE**");
					break;
				case svc_centerprint:
					Net_LogPrintf ("%s\n", MSG_ReadString (&packet));
					break;
				case svc_killedmonster:
					break;
				case svc_foundsecret:
					break;
				case svc_spawnstaticsound:
					Net_LogPrintf ("pos");
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%c%f", i ? ',' : ' ',
									   MSG_ReadCoord (&packet));
					for (i = 0; i < 3; i++)
						Net_LogPrintf (" %d", MSG_ReadByte (&packet));
					break;
				case svc_intermission:
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%f ", MSG_ReadCoord (&packet));
					Net_LogPrintf ("\n");
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%f ", MSG_ReadAngle (&packet));
					break;
				case svc_finale:
					Net_LogPrintf ("%s", MSG_ReadString (&packet));
					break;
				case svc_cdtrack:
					Net_LogPrintf ("%d", MSG_ReadByte (&packet));
					break;
				case svc_sellscreen:
					break;
				case svc_smallkick:
					break;
				case svc_bigkick:
					break;
				case svc_updateping:
					Net_LogPrintf ("Player: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("Ping: %d", MSG_ReadShort (&packet));
					break;
				case svc_updateentertime:
					Net_LogPrintf ("Player: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("Time: %f", MSG_ReadFloat (&packet));
					break;

				case svc_updatestatlong:
					i = MSG_ReadByte (&packet);
					Net_LogPrintf ("%d value: %d", i, MSG_ReadLong (&packet));
					break;

				case svc_muzzleflash:
					Net_LogPrintf ("%d", MSG_ReadShort (&packet));
					break;

				case svc_updateuserinfo:
					Net_LogPrintf ("Player: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("ID: %d ", MSG_ReadLong (&packet));
					Net_LogPrintf ("Info: %s", MSG_ReadString (&packet));
					break;
				case svc_download:
					ii = MSG_ReadShort (&packet);
					Net_LogPrintf ("%d bytes at %d", ii, MSG_ReadByte
								   (&packet));
					for (i = 0; i < ii; i++)
						MSG_ReadByte (&packet);
					break;
				case svc_playerinfo:
					Net_LogPrintf ("\n\tPlayer: %d", MSG_ReadByte (&packet));
					mask2 = mask1 = MSG_ReadShort (&packet);
					Net_LogPrintf (" Mask1: %d", mask1);
#if 1
					Net_LogPrintf (" Origin:");
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%c%f", i ? ',' : ' ',
									   MSG_ReadCoord (&packet));
#endif
					Net_LogPrintf (" Frame: %d", MSG_ReadByte (&packet));

#if 1
					if (mask1 & PF_MSEC)
						Net_LogPrintf (" Ping: %d", MSG_ReadByte (&packet));

					if (mask1 & PF_COMMAND) {
						mask2 = MSG_ReadByte (&packet);	// command
						if (mask2 & 0x01)
							Net_LogPrintf (" Pitch: %f", MSG_ReadAngle16
										   (&packet));
						if (mask2 & 0x80)
							Net_LogPrintf (" Yaw: %f", MSG_ReadAngle16
										   (&packet));
						if (mask2 & 0x02)
							Net_LogPrintf (" Roll: %f", MSG_ReadAngle16
										   (&packet));
						if (mask2 & 0x04)
							Net_LogPrintf (" Speed1: %d",
										   MSG_ReadShort (&packet));
						if (mask2 & 0x08)
							Net_LogPrintf (" Speed2: %d",
										   MSG_ReadShort (&packet));
						if (mask2 & 0x10)
							Net_LogPrintf (" Speed3: %d",
										   MSG_ReadShort (&packet));
						if (mask2 & 0x20)
							Net_LogPrintf (" Flag: %d", MSG_ReadByte
										   (&packet));
						if (mask2 & 0x40)
							Net_LogPrintf (" Impulse: %d", MSG_ReadByte
										   (&packet));
						Net_LogPrintf (" Msec: %d", MSG_ReadByte (&packet));
					}
					if (mask1 & PF_VELOCITY1)
						Net_LogPrintf (" Xspd: %f", MSG_ReadCoord (&packet));
					if (mask1 & PF_VELOCITY2)
						Net_LogPrintf (" Yspd: %f", MSG_ReadCoord (&packet));
					if (mask1 & PF_VELOCITY3)
						Net_LogPrintf (" ZSpd: %f", MSG_ReadCoord (&packet));
					if (mask1 & PF_MODEL)
						Net_LogPrintf (" Model: %d", MSG_ReadByte (&packet));
					if (mask1 & PF_SKINNUM)
						Net_LogPrintf (" Skin: %d", MSG_ReadByte (&packet));
					if (mask1 & PF_EFFECTS)
						Net_LogPrintf (" Effects: %d", MSG_ReadByte (&packet));

					if (mask1 & PF_WEAPONFRAME)
						Net_LogPrintf (" Weapon frame: %d", MSG_ReadByte
									   (&packet));

#else
					if (mask1 & (DF_ORIGIN | (DF_ORIGIN << 1)
								 | (DF_ORIGIN << 2))) {
						Net_LogPrintf (" Origin:");
						for (i = 0; i < 3; i++)
							if (mask1 & (DF_ORIGIN << i))
								Net_LogPrintf ("%c%f", i ? ',' : ' ',
											   MSG_ReadCoord (&packet));
							else
								Net_LogPrintf ("%c", i ? ',' : ' ');
					}
					if (mask1 & (DF_ANGLES | (DF_ANGLES << 1)
								 | (DF_ANGLES << 2))) {
						Net_LogPrintf (" Angles:");
						for (i = 0; i < 3; i++)
							if (mask1 & (DF_ANGLES << i))
								Net_LogPrintf ("%c%f", i ? ',' : ' ',
											   MSG_ReadAngle16 (&packet));
							else
								Net_LogPrintf ("%c", i ? ',' : ' ');
					}
					if (mask1 & DF_MODEL)
						Net_LogPrintf (" Model: %d", MSG_ReadByte (&packet));
					if (mask1 & DF_SKINNUM)
						Net_LogPrintf (" Skin: %d", MSG_ReadByte (&packet));
					if (mask1 & DF_EFFECTS)
						Net_LogPrintf (" Effects: %d", MSG_ReadByte (&packet));

					if (mask1 & DF_WEAPONFRAME)
						Net_LogPrintf (" Weapon frame: %d", MSG_ReadByte
									   (&packet));
#endif
					break;
				case svc_nails:
					ii = MSG_ReadByte (&packet);
					Net_LogPrintf (" %d (bits not parsed)", ii);
					for (i = 0; i < ii; i++) {
						for (iii = 0; iii < 6; iii++)
							MSG_ReadByte (&packet);
					}
					break;
				case svc_chokecount:
					Net_LogPrintf ("%d", MSG_ReadByte (&packet));
					break;
				case svc_modellist:
					ii = MSG_ReadByte (&packet);
					Net_LogPrintf ("start %d", ii);
					for (i = ii; i < 256; i++) {
						s = MSG_ReadString (&packet);
						if (packet.badread)
							break;
						if (!s)
							break;
						if (strlen (s) == 0)
							break;
						Net_LogPrintf ("\n\tModel %d: %s", i, s);
					}
					i = MSG_ReadByte (&packet);
					if (i)
						Net_LogPrintf ("\n\tnext at %d", i);
					else
						Net_LogPrintf ("\n\t*End of modellist*");
					break;
				case svc_soundlist:
					ii = MSG_ReadByte (&packet);
					Net_LogPrintf ("start %d", ii);
					for (i = ii; i < 256; i++) {
						s = MSG_ReadString (&packet);
						if (packet.badread)
							break;
						if (!s)
							break;
						if (strlen (s) == 0)
							break;
						Net_LogPrintf ("\n\tSound %d: %s", i, s);
					}
					i = MSG_ReadByte (&packet);

					if (i)
						Net_LogPrintf ("\n\tnext at %d", i);
					else
						Net_LogPrintf ("\n\t*End of sound list*");
					break;
				case svc_packetentities:
packetentities:
					while (1) {
						mask1 = MSG_ReadShort (&packet);
						if (packet.badread) {
							Net_LogPrintf ("Badread\n");
							return;
						}
						if (!mask1)
							break;
						Net_LogPrintf ("%d", mask1 & 511);
						if (mask1 & U_REMOVE)
							Net_LogPrintf (" UREMOVE");
						Log_Delta (mask1);
						Net_LogPrintf ("\n");
					}
					break;
				case svc_deltapacketentities:
					Net_LogPrintf ("idx: %d\n", MSG_ReadByte (&packet));
					goto packetentities;
					break;
				case svc_maxspeed:
					Net_LogPrintf ("%f", MSG_ReadFloat (&packet));
					break;
				case svc_entgravity:
					Net_LogPrintf ("%f", MSG_ReadFloat (&packet));
					break;
				case svc_setinfo:
					Net_LogPrintf ("Player: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("Keyname: %s ", MSG_ReadString (&packet));
					Net_LogPrintf ("Value: %s", MSG_ReadString (&packet));
					break;
				case svc_serverinfo:
					Net_LogPrintf ("Name: %s ", MSG_ReadString (&packet));
					Net_LogPrintf ("Value: %s", MSG_ReadString (&packet));
					break;
				case svc_updatepl:
					Net_LogPrintf ("Player: %d ", MSG_ReadByte (&packet));
					Net_LogPrintf ("Ploss: %d", MSG_ReadByte (&packet));
					break;
				default:
					Net_LogPrintf ("**UNKNOWN**: [%d]", c);
					break;
			}
			Net_LogPrintf ("\n");
		}
	}
}

void
Analyze_Server_Packet (const byte * data, int len, int has_sequence)
{
	if (!Net_PacketLog)
		Net_PacketLog = _stdout;
	packet.message->data = (byte*)data;
	packet.message->cursize = len;
	MSG_BeginReading (&packet);
	Parse_Server_Packet (has_sequence);
	if (Net_PacketLog == _stdout)
		Net_PacketLog = NULL;
}

static void
Parse_Client_Packet (int has_sequence)
{
	int         mask, i, c, ii;
	long        seq1 = 0, seq2;

	if (has_sequence)
		seq1 = MSG_ReadLong (&packet);
	if (seq1 == -1) {
		Net_LogPrintf ("Special: %s\n", MSG_ReadString (&packet));
		return;
	} else {
		if (has_sequence) {
			// FIXME: display seqs right when reliable
			seq2 = MSG_ReadLong (&packet);

			Net_LogPrintf ("\nSeq: %ld Ack: %ld ", seq1 & 0x7FFFFFFF,
						   seq2 & 0x7FFFFFFF);

			Net_LogPrintf ("QP: %u\n", MSG_ReadShort (&packet) & 0xFFFF);
		}

		while (1) {
			if (packet.badread)
				break;
			c = MSG_ReadByte (&packet);
			if (c == -1)
				break;
			Net_LogPrintf ("\n<%06x> [0x%02x] ", MSG_GetReadCount (&packet),
						   c);
			if (c < 8)
				Net_LogPrintf ("%s: ", clc_string[c]);

			switch (c) {
				case clc_nop:
					break;
				case clc_delta:
					Net_LogPrintf ("%d", MSG_ReadByte (&packet));
					break;
				case clc_move:
					Net_LogPrintf ("checksum = %02x ", MSG_ReadByte (&packet));
					Net_LogPrintf ("PacketLoss: %d", MSG_ReadByte (&packet));

					for (i = 0; i < 3; i++) {
						mask = MSG_ReadByte (&packet);
						Net_LogPrintf ("\n\t(%d) mask = %02x", i, mask);
						if (mask & 0x01)
							Net_LogPrintf (" Tilt: %f", MSG_ReadAngle16
										   (&packet));
						if (mask & 0x80)
							Net_LogPrintf (" Yaw: %f", MSG_ReadAngle16
										   (&packet));
						if (mask & 0x02)
							Net_LogPrintf (" Roll: %f", MSG_ReadAngle16
										   (&packet));
						if (mask & 0x04)
							Net_LogPrintf (" Fwd: %d",
										   MSG_ReadShort (&packet));
						if (mask & 0x08)
							Net_LogPrintf (" Right: %d",
										   MSG_ReadShort (&packet));
						if (mask & 0x10)
							Net_LogPrintf (" Up: %d",
										   MSG_ReadShort (&packet));
						if (mask & 0x20)
							Net_LogPrintf (" Flags: %d", MSG_ReadByte
										   (&packet));
						if (mask & 0x40)
							Net_LogPrintf (" Impulse: %d", MSG_ReadByte
										   (&packet));
						Net_LogPrintf (" Msec: %d", MSG_ReadByte (&packet));
					}
					break;
				case clc_stringcmd:
					Net_LogPrintf ("%s", MSG_ReadString (&packet));
					break;
				case clc_tmove:
					for (i = 0; i < 3; i++)
						Net_LogPrintf ("%f ", MSG_ReadCoord (&packet));
					break;
				case clc_upload:
					ii = MSG_ReadShort (&packet);
					Net_LogPrintf ("%d bytes at %d", ii, MSG_ReadByte
								   (&packet));
					for (i = 0; i < ii; i++)
						MSG_ReadByte (&packet);
					break;
				default:
					Net_LogPrintf ("**UNKNOWN**: [%d]", c);
					break;
			}
			Net_LogPrintf ("\n");
		}
	}
}

void
Analyze_Client_Packet (const byte * data, int len, int has_sequence)
{
	if (!Net_PacketLog)
		Net_PacketLog = _stdout;
	packet.message->data = (byte*)data;
	packet.message->cursize = len;
	MSG_BeginReading (&packet);
	Parse_Client_Packet (has_sequence);
	if (Net_PacketLog == _stdout)
		Net_PacketLog = NULL;
}

static void
Net_PacketLog_f (cvar_t *var)
{
	if (var->int_val) {
		Net_LogStart ("qfpacket.log");
	} else {
		Net_LogStop ();
	}
}

static void
Net_PacketLog_Zap_f (void)
{
	if (Net_PacketLog && Net_PacketLog != _stdout) {
		Sys_Printf ("truncating packet logfile: %s\n", "qfpacket.log");
		Qseek (Net_PacketLog, 0, 0);
		Qwrite (Net_PacketLog, 0, 0);
	} else {
		Sys_Printf ("Deleting packet logfile: %s\n", "qfpacket.log");
		QFS_Remove ("qfpacket.log");
	}
}

int
Net_Log_Init (const char **sound_precache)
{
	Net_sound_precache = sound_precache;

	_stdout = Qdopen (1, "wt");	// create a QFile of stdout

	net_packetlog = Cvar_Get ("net_packetlog", "0", CVAR_NONE, Net_PacketLog_f,
							 "enable/disable packet logging");

// 0 = no logging
// 1 = hex dump only
// 2 = parse/hexdump
// 3 = just parse
// 4 = parse/hexdump, skip movement/empty messages

	net_loglevel = Cvar_Get ("net_loglevel", "2", CVAR_NONE, NULL,
							 "Packet logging/parsing");

	Cmd_AddCommand ("net_packetlog_zap", Net_PacketLog_Zap_f,
					"clear the packet log file");
	return 0;
}

/*
	NET_LogPrintf

	Prints packet to logfile, adds time stamp etc.
*/
void
Net_LogPrintf (const char *fmt, ...)
{
	char        text[2048];
	va_list     argptr;

	va_start (argptr, fmt);
	vsnprintf (text, sizeof (text), fmt, argptr);
	va_end (argptr);
	if (!Net_PacketLog)
		return;

	Qprintf (Net_PacketLog, "%s", text);
	Qflush (Net_PacketLog);
}
