/* net_packetlog.c packet logging/parsing - for debugging and educational purposes Copyright (C) 2000 Jukka Sorjonen 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 $Id$ */ // fixme: we did support Quake1 protocol too... #define QUAKEWORLD #ifdef HAVE_CONFIG_H # include #endif #include #include #include "qtypes.h" #include "quakeio.h" #include "net.h" #include "protocol.h" #include "msg.h" #include "server.h" cvar_t *netlogger; cvar_t *netloglevel; extern server_t sv; extern qboolean is_server(void); //extern sizebuf_t net_message; extern byte net_message_buffer[MAX_MSGLEN*2]; void Analyze_Server_Packet(byte *data,int len); void Analyze_Client_Packet(byte *data,int len); void Parse_Server_Packet(void); void Parse_Client_Packet(void); int Net_LogStart(char *fname); void Net_LogStop(void); // note: this is SUPPOSED to be duplicate, like many others char *svc_string[] = { "svc_bad", "svc_nop", "svc_disconnect", "svc_updatestat", "svc_version", // [long] server version "svc_setview", // [short] entity number "svc_sound", // "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", // "svc_stopsound", // "svc_updatecolors", // [byte] [byte] "svc_particle", // [vec3] "svc_damage", // [byte] impact [byte] blood [vec3] from "svc_spawnstatic", "svc_spawnbinary", "svc_spawnbaseline", "svc_temp_entity", // "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" }; 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 // #define svc_updatecolors 17 // [byte] [byte] #define svc_particle 18 // [vec3] #define svc_signonnum 25 // [byte] used for the signon sequence QFile *Net_PacketLog; /* ================= NET_LogPrintf Prints packet to logfile, adds time stamp etc. ================= */ void Net_LogPrintf (char *fmt, ...) { va_list argptr; char text[2048]; 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); } int Net_LogStart(char *fname) { Con_Printf("Opening packet logfile: %s\n",fname); Net_PacketLog=Qopen(fname,"wt+"); if(!Net_PacketLog) return -1; return 0; } void Net_LogStop(void) { if (Net_PacketLog) Qclose(Net_PacketLog); Net_PacketLog=NULL; } 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; } } 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(char *p, int len) { if (!netloglevel->int_val) return; if (is_server) { Net_LogPrintf("\n<<<<<<<<<<<<<<<<<<<<< client to server %d bytes: <<<<<<<<<<<<<<<<<<<<<<<<\n", len); if (netloglevel->int_val!=3) hex_dump_buf((unsigned char *)p,len); if (netloglevel->int_val>1) Analyze_Client_Packet(p,len); } else { Net_LogPrintf("\n>>>>>>>>>>>>>>>>>>>>> server to client %d bytes: >>>>>>>>>>>>>>>>>>>>>>>>\n", len); if (netloglevel->int_val!=3) hex_dump_buf((unsigned char *)p,len);; if (netloglevel->int_val>1) Analyze_Server_Packet(p,len); } return; } void Log_Outgoing_Packet(char *p, int len) { if (!netloglevel->int_val) return; if (is_server) { Net_LogPrintf("\n>>>>>>>>>>>>>>>>>>>>> server to client %d bytes: >>>>>>>>>>>>>>>>>>>>>>>>\n", len); if (netloglevel->int_val!=3) hex_dump_buf((unsigned char *)p,len);; if (netloglevel->int_val>1) Analyze_Server_Packet(p,len); } else { Net_LogPrintf("\n<<<<<<<<<<<<<<<<<<<<< client to server %d bytes: <<<<<<<<<<<<<<<<<<<<<<<<\n", len); if (netloglevel->int_val!=3) hex_dump_buf((unsigned char *)p,len);; if (netloglevel->int_val>1) Analyze_Client_Packet(p,len); } return; } void Analyze_Server_Packet(byte *data,int len) { // Fixme: quick-hack net_message.data=data; net_message.cursize=len; MSG_BeginReading(); Parse_Server_Packet(); net_message.data=net_message_buffer; } void Parse_Server_Packet() { long seq1,seq2; int c,i,ii,iii,mask1,mask2; char *s; seq1 = MSG_ReadLong(); if (msg_badread) return; if(seq1==-1) { Net_LogPrintf("Special Packet"); } else { seq2=MSG_ReadLong(); //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 (msg_badread) break; c = MSG_ReadByte(); if (c == -1) break; // Net_LogPrintf("\n<%ld,%ld> ",seq1 & 0x7FFFFFFF,seq2 & 0x7FFFFFFF); Net_LogPrintf("<%06x> [0x%02x] ",MSG_GetReadCount(),c); if (c<53) Net_LogPrintf("%s: ",svc_string[c]); // else Net_LogPrintf("(UNK: %d): ",c); if (MSG_GetReadCount()>net_message.cursize) return; switch (c) { case svc_bad: Net_LogPrintf(" - should not happen"); case svc_nop: Net_LogPrintf(" No operation"); break; case svc_disconnect: Net_LogPrintf(" "); break; case svc_updatestat: i=MSG_ReadByte(); Net_LogPrintf(" index: %d value: %d",i,MSG_ReadByte ()); break; case svc_version: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #endif break; case svc_setview: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #else MSG_ReadShort(); #endif break; case svc_sound: i = MSG_ReadShort (); Net_LogPrintf(": (%d) ",i); if (i & SND_VOLUME) Net_LogPrintf("Volume %d ", MSG_ReadByte()); if (i & SND_ATTENUATION) Net_LogPrintf("Ann: %f", (float)MSG_ReadByte () / 64.0); ii = MSG_ReadByte (); // fixme: well, cl. for client :-) Net_LogPrintf("%d (%s) ",ii,sv.sound_precache[ii]); Net_LogPrintf("Pos: "); for (ii = 0; ii < 3; ii++) Net_LogPrintf("%f ",MSG_ReadCoord()); Net_LogPrintf("Ent: %d ",(i >> 3) & 1023); Net_LogPrintf("Channel %d ",i & 7); break; case svc_time: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**\n"); #else MSG_ReadFloat(); #endif break; case svc_print: // fixme i==PRINT_CHAT Net_LogPrintf(" %d",MSG_ReadByte()); Net_LogPrintf(" %s",MSG_ReadString()); break; case svc_stufftext: Net_LogPrintf("%s",MSG_ReadString()); break; case svc_setangle: for (i=0;i<3;i++) Net_LogPrintf("%f ",MSG_ReadAngle()); break; #ifdef QUAKEWORLD case svc_serverdata: Net_LogPrintf("Ver: %ld",MSG_ReadLong()); Net_LogPrintf(" Client ID: %ld",MSG_ReadLong()); Net_LogPrintf(" Dir: %s",MSG_ReadString()); Net_LogPrintf(" User ID: %d",MSG_ReadByte()); Net_LogPrintf(" Map: %s",MSG_ReadString()); for(i=0;i<10;i++) MSG_ReadFloat(); break; #endif case svc_lightstyle: i=MSG_ReadByte(); if (i >= MAX_LIGHTSTYLES) return; Net_LogPrintf("%d %s",i,MSG_ReadString()); break; case svc_updatename: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #else Net_LogPrintf("%d %s",MSG_ReadByte(),MSG_ReadString()); #endif break; case svc_updatefrags: Net_LogPrintf("player: %d frags: %d",MSG_ReadByte(),MSG_ReadShort()); break; case svc_clientdata: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #endif break; case svc_stopsound: Net_LogPrintf("%d",MSG_ReadShort()); break; case svc_updatecolors: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #else Net_LogPrintf("%d %d",MSG_ReadByte(),MSG_ReadByte()); #endif break; case svc_particle: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #else for (i=0;i<3;i++) Net_LogPrintf(" %f",MSG_ReadCoord()); for (i=0;i<3;i++) Net_LogPrintf(" %d",MSG_ReadChar()); Net_LogPrintf(" Count: %d",MSG_ReadByte()); Net_LogPrintf(" Color: %d",MSG_ReadByte()); #endif break; case svc_damage: // fixme parse damage Net_LogPrintf("armor: %d health: %d",MSG_ReadByte(),MSG_ReadByte()); Net_LogPrintf(" from %f,%f,%f",MSG_ReadCoord(),MSG_ReadCoord(),MSG_ReadCoord()); break; case svc_spawnstatic: Net_LogPrintf("%d",MSG_ReadByte()); Net_LogPrintf(" Frame: %d Color: %d Skin: %",MSG_ReadByte(),MSG_ReadByte(),MSG_ReadByte()); for(i = 0; i < 3; i++) Net_LogPrintf("%d: %f %f", i+1, MSG_ReadCoord(), MSG_ReadAngle()); break; case svc_spawnbinary: Net_LogPrintf("**OBSOLETE**"); break; case svc_spawnbaseline: Net_LogPrintf("%d",MSG_ReadShort()); Net_LogPrintf(" idx: %d",MSG_ReadByte()); Net_LogPrintf(" Frame: %d",MSG_ReadByte()); Net_LogPrintf(" Colormap: %d",MSG_ReadByte()); Net_LogPrintf(" Skin: %d",MSG_ReadByte()); for (i=0;i<3;i++) { Net_LogPrintf(" %f",MSG_ReadCoord()); Net_LogPrintf(" %d",MSG_ReadAngle()); }; break; case svc_temp_entity: i=MSG_ReadByte(); switch(i) { case 0: case 1: case 3: case 4: case 7: case 8: case 10: case 11: case 13: Net_LogPrintf(" origin %f %f %f",MSG_ReadCoord(),MSG_ReadCoord(),MSG_ReadCoord()); break; case 5: case 6: case 9: Net_LogPrintf(" created by %d",MSG_ReadShort()); Net_LogPrintf(" origin: %f,%f,%f",MSG_ReadCoord(),MSG_ReadCoord(),MSG_ReadCoord()); Net_LogPrintf(" trace endpos: %f,%f,%f", MSG_ReadCoord(), MSG_ReadCoord(), MSG_ReadCoord()); break; case 2: case 12: Net_LogPrintf(" count: %d",MSG_ReadByte()); printf(" origin: %f,%f,%f",MSG_ReadCoord(),MSG_ReadCoord(),MSG_ReadCoord()); break; default: Net_LogPrintf(" unknown value %d for tempentity",i); break; } break; case svc_setpause: Net_LogPrintf(" %d",MSG_ReadByte ()); break; case svc_signonnum: #ifdef QUAKEWORLD Net_LogPrintf("**QW OBSOLETE**"); #else Net_LogPrintf("%d",MSG_ReadByte()); #endif break; case svc_centerprint: Net_LogPrintf(MSG_ReadString()); break; case svc_killedmonster: break; case svc_foundsecret: break; case svc_spawnstaticsound: Net_LogPrintf("pos %f,%f,%f",MSG_ReadCoord(),MSG_ReadCoord(),MSG_ReadCoord()); Net_LogPrintf("%d %d %d",MSG_ReadByte(),MSG_ReadByte(),MSG_ReadByte()); break; case svc_intermission: for (i=0 ; i<3 ; i++) Net_LogPrintf("%f ",MSG_ReadCoord()); Net_LogPrintf("\n"); for (i=0 ; i<3 ; i++) Net_LogPrintf("%f ",MSG_ReadAngle ()); break; case svc_finale: Net_LogPrintf("%s",MSG_ReadString()); break; case svc_cdtrack: Net_LogPrintf("%d",MSG_ReadByte()); break; case svc_sellscreen: break; case svc_smallkick: break; case svc_bigkick: break; case svc_updateping: Net_LogPrintf("Player: %d ",MSG_ReadByte()); Net_LogPrintf("Ping: %d",MSG_ReadShort()); break; case svc_updateentertime: Net_LogPrintf("Player: %d ",MSG_ReadByte()); Net_LogPrintf("Time: %f",MSG_ReadFloat()); break; case svc_updatestatlong: i=MSG_ReadByte(); Net_LogPrintf("%d value: %ld",i,MSG_ReadLong()); break; case svc_muzzleflash: Net_LogPrintf("%d",MSG_ReadShort()); break; case svc_updateuserinfo: Net_LogPrintf("Player: %d ",MSG_ReadByte()); Net_LogPrintf("ID: %ld ",MSG_ReadLong()); Net_LogPrintf("Info: %s",MSG_ReadString()); break; case svc_download: ii=MSG_ReadShort(); Net_LogPrintf("%d bytes at %d",ii,MSG_ReadByte()); for (i=0;i>31) &0x01) Net_LogPrintf("CL_REL "); if ((seq2 >>31) &0x01) Net_LogPrintf("CL_RELACK "); */ Net_LogPrintf("QP: %u\n",MSG_ReadShort()); while (1) { if (msg_badread) break; c = MSG_ReadByte(); if (c == -1) break; // Net_LogPrintf("<%ld,%ld> ",seq1 & 0x7FFFFFFF,seq2 & 0x7FFFFFFF); Net_LogPrintf("\n<%06x> [0x%02x] ",MSG_GetReadCount(),c); if (c<8) Net_LogPrintf("%s: ",clc_string[c]); switch(c) { case clc_nop: break; case clc_delta: Net_LogPrintf ("%d",MSG_ReadByte()); break; case clc_move: Net_LogPrintf("checksum = %02x ",MSG_ReadByte()); Net_LogPrintf("PacketLoss: %d",MSG_ReadByte()); for (i=0;i<3;i++) { mask=MSG_ReadByte(); Net_LogPrintf("\n\t(%d) mask = %02x",i,mask); if(mask & 0x01) Net_LogPrintf(" Tilt: %f",MSG_ReadAngle16()); if(mask & 0x80) Net_LogPrintf(" Yaw: %f",MSG_ReadAngle16()); if(mask & 0x02) Net_LogPrintf(" Roll: %f",MSG_ReadAngle16()); if(mask & 0x04) Net_LogPrintf(" Fwd: %d",MSG_ReadShort()); if(mask & 0x08) Net_LogPrintf(" Right: %d",MSG_ReadShort()); if(mask & 0x10) Net_LogPrintf(" Up: %d",MSG_ReadShort()); if(mask & 0x20) Net_LogPrintf(" Flags: %d",MSG_ReadByte()); if(mask & 0x40) Net_LogPrintf(" Impulse: %d",MSG_ReadByte()); Net_LogPrintf(" Msec: %d",MSG_ReadByte()); } break; case clc_stringcmd: Net_LogPrintf("%s",MSG_ReadString()); break; case clc_tmove: for (i=0;i<3;i++) Net_LogPrintf("%f ",MSG_ReadCoord()); break; case clc_upload: ii=MSG_ReadShort(); Net_LogPrintf("%d bytes at %d",ii,MSG_ReadByte()); for (i=0;i