diff --git a/demboyz/demboyz.cpp b/demboyz/demboyz.cpp index d585f70..c7680de 100644 --- a/demboyz/demboyz.cpp +++ b/demboyz/demboyz.cpp @@ -1,6 +1,7 @@ #include "demofile.h" #include "demofilebitbuf.h" +#include "netmessages.h" #include #include #include @@ -11,28 +12,32 @@ void ParseGameEvent(const std::string& eventBuf) { CBitRead bitBuf(eventBuf.data(), eventBuf.size()); uint32 eventId = bitBuf.ReadUBitLong(9); - printf("%s\n", eventNames[eventId-1].c_str()); + printf("%s\n", eventNames[eventId].c_str()); } -void ParsePacket(const std::string& packetBuf) +void ParsePacket(const char* packetBuf, const int numBytes) { - auto data = packetBuf.data(); - CBitRead bitBuf(packetBuf.data(), packetBuf.size()); - uint32 typeId = bitBuf.ReadUBitLong(5); - printf("%i\n", typeId); - if (typeId != 25) + assert(numBytes <= NET_MAX_PAYLOAD); + CBitRead bitBuf(packetBuf, numBytes); + while (bitBuf.GetNumBitsLeft() >= NETMSG_TYPE_BITS) { - return; + uint32 typeId = bitBuf.ReadUBitLong(NETMSG_TYPE_BITS); + printf("%i\n", typeId); + ProcessNetMsg(typeId, bitBuf); + /*if (typeId != 25) + { + break; + } + + uint32 length = bitBuf.ReadUBitLong(11); + int numBytes = (length / 8) + (length % 8 > 0); + + std::string subpacket; + subpacket.resize(numBytes); + + bitBuf.ReadBits(&subpacket[0], length); + ParseGameEvent(subpacket);*/ } - - uint32 length = bitBuf.ReadUBitLong(11); - int numBytes = (length / 8) + (length % 8 > 0); - - std::string subpacket; - subpacket.resize(numBytes); - - bitBuf.ReadBits(&subpacket[0], length); - ParseGameEvent(subpacket); } void ParseEventNames(const char* eventfile, std::vector& eventNames) @@ -45,6 +50,24 @@ void ParseEventNames(const char* eventfile, std::vector& eventNames } } +void ParseSignonData(const std::string& signonData) +{ + CBitRead bitbuf(signonData.data(), signonData.length()); + const char cmd = bitbuf.ReadChar(); + assert(cmd == dem_signon); + const int32 tick = bitbuf.ReadLong(); + bitbuf.SeekRelative(sizeof(democmdinfo_t) * 8); + const int32 seq1 = bitbuf.ReadLong(); + const int32 seq2 = bitbuf.ReadLong(); + assert(seq1 == seq2); + + const int32 numBytes = bitbuf.ReadLong(); + std::string packet; + packet.resize(numBytes); + bitbuf.ReadBytes(&packet[0], numBytes); + ParsePacket(packet.data(), numBytes); +} + int main(const int argc, const char* argv[]) { if (argc < 3) @@ -62,12 +85,14 @@ int main(const int argc, const char* argv[]) auto demoHeader = demoFile.GetDemoHeader(); + ParseSignonData(demoFile.GetSignOnData()); + unsigned char cmd; int32 tick; int32 sequenceInfo1; int32 sequenceInfo2; democmdinfo_t cmdInfo; - std::string packetBuf; + char* packetBuf = (char*)malloc(NET_MAX_PAYLOAD); demoFile.ReadCmdHeader(cmd, tick); assert(cmd == dem_synctick && tick == 0); @@ -81,11 +106,13 @@ int main(const int argc, const char* argv[]) switch (cmd) { case dem_packet: - demoFile.ReadCmdInfo(cmdInfo); - demoFile.ReadSequenceInfo(sequenceInfo1, sequenceInfo2); - assert(sequenceInfo1 == sequenceInfo2); - demoFile.ReadRawData(packetBuf); - ParsePacket(packetBuf); + { + demoFile.ReadCmdInfo(cmdInfo); + demoFile.ReadSequenceInfo(sequenceInfo1, sequenceInfo2); + assert(sequenceInfo1 == sequenceInfo2); + const int32 length = demoFile.ReadRawData(packetBuf, NET_MAX_PAYLOAD); + ParsePacket(packetBuf, length); + } break; case dem_stop: assert(i == numFrames && tick == numTicks); @@ -95,10 +122,12 @@ int main(const int argc, const char* argv[]) case dem_usercmd: case dem_datatables: default: + assert(false); break; } } + free(packetBuf); demoFile.Close(); return 0; diff --git a/demboyz/demofilebitbuf.h b/demboyz/demofilebitbuf.h index 14b46fd..14749b5 100644 --- a/demboyz/demofilebitbuf.h +++ b/demboyz/demofilebitbuf.h @@ -233,6 +233,29 @@ public: int64 ReadSignedVarInt64() { return bitbuf::ZigZagDecode64( ReadVarInt64() ); } }; +inline unsigned int CBitRead::PeekUBitLong(int numbits) +{ + int nSaveBA = m_nBitsAvail; + int nSaveW = m_nInBufWord; + uint32 const *pSaveP = m_pDataIn; + unsigned int nRet = ReadUBitLong(numbits); + m_nBitsAvail = nSaveBA; + m_nInBufWord = nSaveW; + m_pDataIn = pSaveP; + return nRet; +} + +inline int CBitRead::ReadLong(void) +{ + return (int)ReadUBitLong(sizeof(long) << 3); +} + +inline float CBitRead::ReadFloat(void) +{ + uint32 nUval = ReadUBitLong(sizeof(long) << 3); + return *((float *)&nUval); +} + #ifndef MIN #define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) ) #endif diff --git a/demboyz/netmessages.cpp b/demboyz/netmessages.cpp new file mode 100644 index 0000000..401525a --- /dev/null +++ b/demboyz/netmessages.cpp @@ -0,0 +1,414 @@ + +#include "netmessages.h" +#include "demofilebitbuf.h" +#include + +namespace math +{ + unsigned int log2(unsigned int value) + { + unsigned int res = 0; + while (value >>= 1) + ++res; + return res; + } + + unsigned int Bits2Bytes(unsigned int bits) + { + return ((bits + 7) >> 3); + } +} + +void Net_NOP(CBitRead& bitbuf) +{ + // nothing +} + +void Net_Disconnect(CBitRead& bitbuf) +{ + char message[1024]; + bitbuf.ReadString(message, sizeof(message)); +} + +void Net_File(CBitRead& bitbuf) +{ + const unsigned int transferID = bitbuf.ReadUBitLong(32); + char filename[1024]; + bitbuf.ReadString(filename, sizeof(filename)); + const bool isRequest = bitbuf.ReadOneBit() != 0; +} + +void Net_Tick(CBitRead& bitbuf) +{ + static const float NET_TICK_SCALEUP = 100000.0f; + + const int tick = bitbuf.ReadLong(); + const float hostFrameTime = (float)bitbuf.ReadUBitLong(16) / NET_TICK_SCALEUP; + const float hostFrameTimeStdDev = (float)bitbuf.ReadUBitLong(16) / NET_TICK_SCALEUP; +} + +void Net_StringCmd(CBitRead& bitbuf) +{ + char commandBuffer[1024]; + bitbuf.ReadString(commandBuffer, sizeof(commandBuffer)); +} + +void Net_SetConVar(CBitRead& bitbuf) +{ + typedef struct cvar_s + { + char name[MAX_OSPATH]; + char value[MAX_OSPATH]; + } cvar_t; + + const int numVars = bitbuf.ReadByte(); + cvar_t cvar; + for (int i = 0; i < numVars; ++i) + { + bitbuf.ReadString(cvar.name, sizeof(cvar.name)); + bitbuf.ReadString(cvar.value, sizeof(cvar.value)); + } +} + +void Net_SignonState(CBitRead& bitbuf) +{ + const int signonState = bitbuf.ReadByte(); + //assert(signonState == SIGNONSTATE_PRESPAWN); + const int spawnCount = bitbuf.ReadLong(); +} + +void SVC_Print(CBitRead& bitbuf) +{ + char textBuffer[2048]; + bitbuf.ReadString(textBuffer, sizeof(textBuffer)); +} + +void SVC_ServerInfo(CBitRead& bitbuf) +{ + const int protocol = bitbuf.ReadShort(); // protocol version + const int serverCount = bitbuf.ReadLong(); // number of changelevels since server start + const bool isHLTV = bitbuf.ReadOneBit() != 0; // HLTV server ? + const bool isDedicated = bitbuf.ReadOneBit() != 0; // dedicated server ? + const int clientCRC = bitbuf.ReadLong(); // client.dll CRC server is using + const int maxClasses = bitbuf.ReadWord(); // max number of server classes + + if (protocol <= 17) + { + const int mapCRC = bitbuf.ReadLong(); // server map CRC + } + else + { + char unknown[16]; + bitbuf.ReadBytes(unknown, sizeof(unknown)); + } + const int playerSlot = bitbuf.ReadByte(); // our client slot number + const int maxClients = bitbuf.ReadByte(); // max number of clients on server + const float tickInterval = bitbuf.ReadFloat(); // server tick interval + const char os = bitbuf.ReadChar(); // 'l' = linux, 'w' = Win32 + + char gameDir[MAX_OSPATH]; // game directory eg "tf2" + char mapName[MAX_OSPATH]; // name of current map + char skyName[MAX_OSPATH]; // name of current skybox + char hostName[MAX_OSPATH]; // host name + bitbuf.ReadString(gameDir, sizeof(gameDir)); + bitbuf.ReadString(mapName, sizeof(mapName)); + bitbuf.ReadString(skyName, sizeof(skyName)); + bitbuf.ReadString(hostName, sizeof(hostName)); +} + +void SVC_SendTable(CBitRead& bitbuf) +{ + const bool needsDecoder = bitbuf.ReadOneBit() != 0; + const int dataLengthBits = bitbuf.ReadShort(); + bitbuf.SeekRelative(dataLengthBits); +} + +void SVC_ClassInfo(CBitRead& bitbuf) +{ + typedef struct class_s + { + int classID; + char datatablename[256]; + char classname[256]; + } class_t; + + const int numServerClasses = bitbuf.ReadShort(); + const int nServerClassBits = math::log2(numServerClasses) + 1; + // if true, client creates own SendTables & classinfos from game.dll + const bool createOnClient = bitbuf.ReadOneBit() != 0; + if (!createOnClient) + { + class_t serverclass; + for (int i = 0; i < numServerClasses; ++i) + { + serverclass.classID = bitbuf.ReadUBitLong(nServerClassBits); + bitbuf.ReadString(serverclass.classname, sizeof(serverclass.classname)); + bitbuf.ReadString(serverclass.datatablename, sizeof(serverclass.datatablename)); + } + } +} + +void SVC_SetPause(CBitRead& bitbuf) +{ + const bool paused = bitbuf.ReadOneBit() != 0; +} + +void SVC_CreateStringTable(CBitRead& bitbuf) +{ + if (bitbuf.PeekUBitLong(8) == ':') + { + bitbuf.ReadByte(); + } + + char tableName[256]; + bitbuf.ReadString(tableName, sizeof(tableName)); + const int maxEntries = bitbuf.ReadWord(); + const int encodeBits = math::log2(maxEntries); + const int numEntries = bitbuf.ReadUBitLong(encodeBits + 1); + const int lengthInBits = bitbuf.ReadUBitLong(NET_MAX_PALYLOAD_BITS + 3); + const bool userDataFixedSize = bitbuf.ReadOneBit() != 0; + if (userDataFixedSize) + { + const int userDataSize = bitbuf.ReadUBitLong(12); + const int userDataSizeBits = bitbuf.ReadUBitLong(4); + } + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_UpdateStringTable(CBitRead& bitbuf) +{ + const int tableId = bitbuf.ReadUBitLong(math::log2(MAX_TABLES)); + const int numChangedEntries = (bitbuf.ReadOneBit() != 0) ? bitbuf.ReadWord() : 1; + const int lengthInBits = bitbuf.ReadUBitLong(20); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_VoiceInit(CBitRead& bitbuf) +{ + char voiceCodec[MAX_OSPATH]; // used voice codec .dll + bitbuf.ReadString(voiceCodec, sizeof(voiceCodec)); + const int quality = bitbuf.ReadByte(); // custom quality setting +} + +void SVC_VoiceData(CBitRead& bitbuf) +{ + const int fromClientIndex = bitbuf.ReadByte(); + const bool proximity = !!bitbuf.ReadByte(); + const int lengthInBits = bitbuf.ReadWord(); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_HLTV(CBitRead& bitbuf) +{ + //const int state = bitbuf.ReadByte(); + assert(false); +} + +void SVC_Sounds(CBitRead& bitbuf) +{ + const bool reliableSound = bitbuf.ReadOneBit() != 0; + int numSounds; + int lengthInBits; + if (reliableSound) + { + numSounds = 1; + lengthInBits = bitbuf.ReadUBitLong(8); + } + else + { + numSounds = bitbuf.ReadUBitLong(8); + lengthInBits = bitbuf.ReadUBitLong(16); + } + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_SetView(CBitRead& bitbuf) +{ + const int entIndex = bitbuf.ReadUBitLong(MAX_EDICT_BITS); +} + +void SVC_FixAngle(CBitRead& bitbuf) +{ + const bool relative = bitbuf.ReadOneBit() != 0; + const float x = bitbuf.ReadBitAngle(16); + const float y = bitbuf.ReadBitAngle(16); + const float z = bitbuf.ReadBitAngle(16); +} + +void SVC_CrosshairAngle(CBitRead& bitbuf) +{ + const float x = bitbuf.ReadBitAngle(16); + const float y = bitbuf.ReadBitAngle(16); + const float z = bitbuf.ReadBitAngle(16); +} + +void SVC_BSPDecal(CBitRead& bitbuf) +{ + Vector pos; + bitbuf.ReadBitVec3Coord(pos); + const int decalTextureIndex = bitbuf.ReadUBitLong(MAX_DECAL_INDEX_BITS); + if (bitbuf.ReadOneBit() != 0) + { + const int entIndex = bitbuf.ReadUBitLong(MAX_EDICT_BITS); + const int modelIndex = bitbuf.ReadUBitLong(SP_MODEL_INDEX_BITS); + } + else + { + const int entIndex = 0; + const int modelIndex = 0; + } + const bool lowPriority = bitbuf.ReadOneBit() != 0; +} + +void SVC_TerrainMod(CBitRead& bitbuf) +{ + assert(false); +} + +void SVC_UserMessage(CBitRead& bitbuf) +{ + const int msgType = bitbuf.ReadByte(); + + // max 256 * 8 bits, see MAX_USER_MSG_DATA + const int lengthInBits = bitbuf.ReadUBitLong(11); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_EntityMessage(CBitRead& bitbuf) +{ + const int entIndex = bitbuf.ReadUBitLong(MAX_EDICT_BITS); + const int classID = bitbuf.ReadUBitLong(MAX_SERVER_CLASS_BITS); + + // max 256 * 8 bits + const int lengthInBits = bitbuf.ReadUBitLong(11); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_GameEvent(CBitRead& bitbuf) +{ + const int lengthInBits = bitbuf.ReadUBitLong(11); + //uint32 eventId = bitbuf.ReadUBitLong(9); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_PacketEntities(CBitRead& bitbuf) +{ + const int maxEntries = bitbuf.ReadUBitLong(MAX_EDICT_BITS); + const bool isDelta = bitbuf.ReadOneBit() != 0; + if (isDelta) + { + const int deltaFrom = bitbuf.ReadLong(); + } + else + { + const int deltaFrom = -1; + } + const int numBaseline = bitbuf.ReadUBitLong(1); + const int numUpdatedEntries = bitbuf.ReadUBitLong(MAX_EDICT_BITS); + const int lengthInBits = bitbuf.ReadUBitLong(DELTASIZE_BITS); + const bool updateBaseline = bitbuf.ReadOneBit() != 0; + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_TempEntities(CBitRead& bitbuf) +{ + const int numEntries = bitbuf.ReadUBitLong(EVENT_INDEX_BITS); + const int lengthInBits = bitbuf.ReadUBitLong(NET_MAX_PALYLOAD_BITS); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_Prefetch(CBitRead& bitbuf) +{ + enum + { + SOUND = 0, + }; + + const short type = SOUND; // bitbuf.ReadUBitLong(1); + const short soundIndex = bitbuf.ReadUBitLong(MAX_SOUND_INDEX_BITS); +} + +void SVC_Menu(CBitRead& bitbuf) +{ + typedef enum + { + DIALOG_MSG = 0, // just an on screen message + DIALOG_MENU, // an options menu + DIALOG_TEXT, // a richtext dialog + DIALOG_ENTRY, // an entry box + DIALOG_ASKCONNECT // Ask the client to connect to a specified IP address. Only the "time" and "title" keys are used. + } DIALOG_TYPE; + + DIALOG_TYPE type = (DIALOG_TYPE)bitbuf.ReadShort(); + const int lengthInBytes = bitbuf.ReadWord(); + bitbuf.SeekRelative(lengthInBytes * 8); +} + +void SVC_GameEventList(CBitRead& bitbuf) +{ + const int numEvents = bitbuf.ReadUBitLong(MAX_EVENT_BITS); + for (int i = 0; i < numEvents; ++i) + { + const int id = bitbuf.ReadUBitLong(MAX_EVENT_BITS); + char name[MAX_EVENT_NAME_LENGTH]; + bitbuf.ReadString(name, sizeof(name)); + printf("%s\n", name); + assert(false); + // gameeventmanager.cpp ParseEventList + } + const int lengthInBits = bitbuf.ReadUBitLong(20); + bitbuf.SeekRelative(lengthInBits); +} + +void SVC_GetCvarValue(CBitRead& bitbuf) +{ + typedef int QueryCvarCookie_t; + QueryCvarCookie_t cookie = bitbuf.ReadSBitLong(32); + char cvarName[256]; + bitbuf.ReadString(cvarName, sizeof(cvarName)); +} + +static const int NUM_MESSAGES = static_cast(NetMsg::SVC_LASTMSG) + 1; + +void ProcessNetMsg(const std::uint32_t msgType, CBitRead& bitbuf) +{ + static NetMsgFn netMsgHandler[NUM_MESSAGES] = + { + &Net_NOP, + &Net_Disconnect, + &Net_File, + &Net_Tick, + &Net_StringCmd, + &Net_SetConVar, + &Net_SignonState, + + &SVC_Print, + &SVC_ServerInfo, + &SVC_SendTable, + &SVC_ClassInfo, + &SVC_SetPause, + &SVC_CreateStringTable, + &SVC_UpdateStringTable, + &SVC_VoiceInit, + &SVC_VoiceData, + &SVC_HLTV, + &SVC_Sounds, + &SVC_SetView, + &SVC_FixAngle, + &SVC_CrosshairAngle, + &SVC_BSPDecal, + &SVC_TerrainMod, + &SVC_UserMessage, + &SVC_EntityMessage, + &SVC_GameEvent, + &SVC_PacketEntities, + &SVC_TempEntities, + &SVC_Prefetch, + &SVC_Menu, + &SVC_GameEventList, + &SVC_GetCvarValue + }; + + assert(msgType < NUM_MESSAGES); + netMsgHandler[msgType](bitbuf); +} diff --git a/demboyz/netmessages.h b/demboyz/netmessages.h new file mode 100644 index 0000000..fad69dd --- /dev/null +++ b/demboyz/netmessages.h @@ -0,0 +1,114 @@ + +#pragma once + +#include + +class CBitRead; + +typedef void(*NetMsgFn)(CBitRead& bitbuf); + +enum constants +{ + // was 5 + NETMSG_TYPE_BITS = 6, // 2^NETMSG_TYPE_BITS > SVC_LASTMSG + + // was 96000 + NET_MAX_PAYLOAD = 288000, // largest message size in bytes + + // was 17 + NET_MAX_PALYLOAD_BITS = 19, // 2^NET_MAX_PALYLOAD_BITS > NET_MAX_PAYLOAD + + // table index is sent in log2(MAX_TABLES) bits + MAX_TABLES = 32, // Table id is 4 bits + + // How many bits to use to encode an edict. + MAX_EDICT_BITS = 11, // # of bits needed to represent max edicts + + // Max # of edicts in a level + MAX_EDICTS = (1< (NET_MAX_PAYLOAD * 8) + EVENT_INDEX_BITS = 8, + MAX_SOUND_INDEX_BITS = 13, + + + SIGNONSTATE_NONE = 0, // no state yet, about to connect + SIGNONSTATE_CHALLENGE = 1, // client challenging server, all OOB packets + SIGNONSTATE_CONNECTED = 2, // client is connected to server, netchans ready + SIGNONSTATE_NEW = 3, // just got serverinfo and string tables + SIGNONSTATE_PRESPAWN = 4, // received signon buffers + SIGNONSTATE_SPAWN = 5, // ready to receive entity packets + SIGNONSTATE_FULL = 6, // we are fully connected, first non-delta packet received + SIGNONSTATE_CHANGELEVEL = 7 // server is changing level, please wait +}; + +enum class NetMsg: std::uint8_t +{ + net_NOP = 0, // nop command used for padding + net_Disconnect = 1, // disconnect, last message in connection + net_File = 2, // file transmission message request/deny + + net_Tick = 3, // send last world tick + net_StringCmd = 4, // a string command + net_SetConVar = 5, // sends one/multiple convar settings + net_SignonState = 6, // signals current signon state + + // + // server to client + // + + svc_Print = 7, // print text to console + svc_ServerInfo = 8, // first message from server about game, map etc + svc_SendTable = 9, // sends a sendtable description for a game class + svc_ClassInfo = 10, // Info about classes (first byte is a CLASSINFO_ define). + svc_SetPause = 11, // tells client if server paused or unpaused + + + svc_CreateStringTable = 12, // inits shared string tables + svc_UpdateStringTable = 13, // updates a string table + + svc_VoiceInit = 14, // inits used voice codecs & quality + svc_VoiceData = 15, // Voicestream data from the server + + //svc_HLTV = 16, // HLTV control messages + + svc_Sounds = 17, // starts playing sound + + svc_SetView = 18, // sets entity as point of view + svc_FixAngle = 19, // sets/corrects players viewangle + svc_CrosshairAngle = 20, // adjusts crosshair in auto aim mode to lock on traget + + svc_BSPDecal = 21, // add a static decal to the worl BSP + + // NOTE: This is now unused! + // svc_TerrainMod = 22, // modification to the terrain/displacement + + // Message from server side to client side entity + svc_UserMessage = 23, // a game specific message + svc_EntityMessage = 24, // a message for an entity + svc_GameEvent = 25, // global game event fired + + svc_PacketEntities = 26, // non-delta compressed entities + + svc_TempEntities = 27, // non-reliable event object + + svc_Prefetch = 28, // only sound indices for now + + svc_Menu = 29, // display a menu from a plugin + + svc_GameEventList = 30, // list of known games events and fields + + svc_GetCvarValue = 31, // Server wants to know the value of a cvar on the client. + + SVC_LASTMSG = 31 // last known server messages +}; + +void ProcessNetMsg(const std::uint32_t msgType, CBitRead& bitbuf);