protected override void Parse(ref BitStreamReader bsr) { Cmd = bsr.ReadUInt(); uint byteSize = bsr.ReadUInt(); int indexBeforeData = bsr.CurrentBitIndex; CommandNumber = bsr.ReadUIntIfExists(); TickCount = bsr.ReadUIntIfExists(); ViewAngleX = bsr.ReadFloatIfExists(); ViewAngleY = bsr.ReadFloatIfExists(); ViewAngleZ = bsr.ReadFloatIfExists(); SidewaysMovement = bsr.ReadFloatIfExists(); ForwardMovement = bsr.ReadFloatIfExists(); VerticalMovement = bsr.ReadFloatIfExists(); Buttons = (Buttons?)bsr.ReadUIntIfExists(); Impulse = bsr.ReadByteIfExists(); if (bsr.ReadBool()) { WeaponSelect = bsr.ReadUInt(11); WeaponSubtype = bsr.ReadUIntIfExists(6); } MouseDx = (short?)bsr.ReadUShortIfExists(); MouseDy = (short?)bsr.ReadUShortIfExists(); bsr.CurrentBitIndex = indexBeforeData + (int)(byteSize << 3); }
protected override void Parse(ref BitStreamReader bsr) { PacketInfo = new CmdInfo[DemoInfo.MaxSplitscreenPlayers]; for (int i = 0; i < PacketInfo.Length; i++) { PacketInfo[i] = new CmdInfo(DemoRef); PacketInfo[i].ParseStream(ref bsr); } InSequence = bsr.ReadUInt(); OutSequence = bsr.ReadUInt(); MessageStream = new MessageStream(DemoRef); MessageStream.ParseStream(ref bsr); // After we're doing with the packet, we can process all the messages. // Most things should be processed during parsing, but any additional checks should be done here. var netTickMessages = MessageStream.Where(tuple => tuple.messageType == MessageType.NetTick).ToList(); if (netTickMessages.Count > 1) { DemoRef.LogError("there's more than 2 net tick messages in this packet"); } NetTick?tickInfo = (NetTick?)netTickMessages.FirstOrDefault().message; if (tickInfo != null) { if (DemoRef.EntitySnapshot != null) { DemoRef.EntitySnapshot.EngineTick = tickInfo.EngineTick; } } // todo fill prop handles with data here TimingAdjustment.AdjustFromPacket(this); }
// src_main/common/netmessages.cpp SVC_ServerInfo::WriteToBuffer protected override void Parse(ref BitStreamReader bsr) { NetworkProtocol = bsr.ReadUShort(); ServerCount = bsr.ReadUInt(); IsHltv = bsr.ReadBool(); IsDedicated = bsr.ReadBool(); if (DemoInfo.IsLeft4Dead() && DemoInfo.Game >= SourceGame.L4D2_2147) { UnknownBit = bsr.ReadBool(); } ClientCrc = bsr.ReadSInt(); if (DemoInfo.NewDemoProtocol) // unknown field, could be before ClientCrc { Unknown = bsr.ReadUInt(); } MaxServerClasses = bsr.ReadUShort(); if (NetworkProtocol == 24) { MapMD5 = bsr.ReadBytes(16); } else { MapCrc = bsr.ReadUInt(); // network protocol < 18 according to p2 leak, but doesn't add up for l4d2 and p2 } PlayerCount = bsr.ReadByte(); MaxClients = bsr.ReadByte(); TickInterval = bsr.ReadFloat(); Platform = (char)bsr.ReadByte(); GameDirBitIndex = bsr.AbsoluteBitIndex; GameDir = bsr.ReadNullTerminatedString(); MapName = bsr.ReadNullTerminatedString(); SkyName = bsr.ReadNullTerminatedString(); HostName = bsr.ReadNullTerminatedString(); if (DemoInfo.IsLeft4Dead() && DemoInfo.Game >= SourceGame.L4D2_2147) { MissionName = bsr.ReadNullTerminatedString(); MutationName = bsr.ReadNullTerminatedString(); } if (NetworkProtocol == 24) { HasReplay = bsr.ReadBool(); // protocol version >= 16 } // there's a good change that the first SvcServerInfo parsed is parsed correctly // prevent the interval from being overwritten by subsequent, incorrectly detected, SvcServerInfo messages // TODO: check if changing tickrate mid game sends another SvcServerInfo if (!DemoInfo.HasParsedTickInterval) { DemoInfo.TickInterval = TickInterval; DemoInfo.HasParsedTickInterval = true; } // this packet always(?) appears before the creation of any tables DemoRef.StringTablesManager.ClearCurrentTables(); // init baselines here DemoRef.EntBaseLines = new EntityBaseLines(DemoRef, MaxServerClasses); }
protected override void Parse(ref BitStreamReader bsr) { TableId = (byte)bsr.ReadUInt(5); TableName = DemoRef.StringTablesManager.TableById(TableId).Name; ChangedEntriesCount = bsr.ReadUShortIfExists() ?? 1; uint dataLen = bsr.ReadUInt(20); TableUpdates = new StringTableUpdates(DemoRef, TableName, ChangedEntriesCount, false); TableUpdates.ParseStream(bsr.SplitAndSkip(dataLen)); }
protected override void Parse(ref BitStreamReader bsr) { bsr.ReadVectorCoord(out Pos); DecalTextureIndex = (int)bsr.ReadUInt(9); if (bsr.ReadBool()) { EntityIndex = bsr.ReadUInt(DemoInfo.MaxEdictBits); ModelIndex = bsr.ReadUInt(DemoInfo.MaxEdictBits); } LowPriority = bsr.ReadBool(); }
protected override void Parse(ref BitStreamReader bsr) { EventCount = (int)bsr.ReadUInt(9); int dataLen = (int)bsr.ReadUInt(20); int indexBeforeData = bsr.CurrentBitIndex; Descriptors = new List <GameEventDescription>(EventCount); for (int i = 0; i < EventCount; i++) { Descriptors.Add(new GameEventDescription(DemoRef)); Descriptors[^ 1].ParseStream(ref bsr);
protected override void Parse(ref BitStreamReader bsr) { FileStamp = bsr.ReadStringOfLength(8); DemoProtocol = bsr.ReadUInt(); NetworkProtocol = bsr.ReadUInt(); ServerName = bsr.ReadStringOfLength(260); ClientName = bsr.ReadStringOfLength(260); MapName = bsr.ReadStringOfLength(260); GameDirectory = bsr.ReadStringOfLength(260); PlaybackTime = bsr.ReadFloat(); TickCount = bsr.ReadSInt(); FrameCount = bsr.ReadSInt(); SignOnLength = bsr.ReadUInt(); }
protected override void Parse(ref BitStreamReader bsr) { MenuType = bsr.ReadUShort(); uint dataLen = bsr.ReadUInt(); _data = bsr.SplitAndSkip(dataLen); }
public static unsafe byte[] Decompress(ref BitStreamReader bsr, int compSize) { uint type = bsr.ReadUInt(); int decompSize = bsr.ReadSInt(); switch (type) { case LZSS_ID: Span <byte> dataIn = compSize < 100000 ? stackalloc byte[compSize] : new byte[compSize]; bsr.ReadToSpan(dataIn); byte[] arrOut = new byte[decompSize]; fixed(byte *pDataIn = dataIn) fixed(byte *pDataOut = arrOut) DecompressLZSS(pDataIn, pDataOut); return(arrOut); case SNAPPY_ID: Debug.Assert(false); throw new NotImplementedException("snappy decompression not implemented"); default: throw new InvalidOperationException("unknown compression method"); } }
protected override void Parse(ref BitStreamReader bsr) { RemoveUser = bsr.ReadBool(); uint dataLen = bsr.ReadUInt(11); Data = bsr.SplitAndSkip(dataLen); }
protected override void Parse(ref BitStreamReader bsr) { EngineTick = bsr.ReadUInt(); if (DemoInfo.Game != SourceGame.HL2_OE) { HostFrameTime = bsr.ReadUShort() / NetTickScaleUp; HostFrameTimeStdDev = bsr.ReadUShort() / NetTickScaleUp; } }
protected override void Parse(ref BitStreamReader bsr) { Flags = (InterpFlags)bsr.ReadUInt(); _floats = new Vector3[6]; for (int i = 0; i < _floats.Length; i++) { bsr.ReadVector3(out _floats[i]); } }
protected override void Parse(ref BitStreamReader bsr) { DataTableId = _classInfoRef == null ? bsr.ReadUShort() : (int)bsr.ReadUInt(DemoRef.DataTableParser.ServerClassBits); ClassName = bsr.ReadNullTerminatedString(); DataTableName = bsr.ReadNullTerminatedString(); }
/* Okay, this is pretty wacky. First I read a byte, and based off of that I try to determine the type of * user message. If I don't have a lookup list for whatever game this is or the type seems bogus, I log an * error. Otherwise, create the message instance, and if it's not empty, try to parse it. If parsing fails, * log an error. Finally, if not all bits of the message are parsed, then it's likely that I did something * wrong, (since it seems like the user messages use up all the bits in the message) so log an error. */ protected override void Parse(ref BitStreamReader bsr) { byte typeVal = bsr.ReadByte(); MessageType = UserMessage.ByteToUserMessageType(DemoInfo, typeVal); uint messageLength = bsr.ReadUInt(DemoInfo.UserMessageLengthBits); string?errorStr = null; var uMessageReader = bsr.SplitAndSkip(messageLength); switch (MessageType) { case UserMessageType.Unknown: errorStr = $"There is no SvcUserMessage list for this game, type {typeVal} was found"; break; case UserMessageType.Invalid: errorStr = $"SvcUserMessage with value {typeVal} is invalid"; break; default: UserMessage = SvcUserMessageFactory.CreateUserMessage(DemoRef, MessageType) !; if (UserMessage == null) { errorStr = $"Unimplemented SvcUserMessage: {MessageType}"; _unimplemented = true; } else { try { // empty messages might still have 1-2 bytes, might need to do something 'bout that if (UserMessage.ParseStream(uMessageReader) != 0) { errorStr = $"{GetType().Name} - {MessageType} ({typeVal}) didn't parse all bits"; } } catch (Exception e) { errorStr = $"{GetType().Name} - {MessageType} ({typeVal}) " + $"threw exception during parsing, message: {e.Message}"; } } break; } #region error logging // if parsing fails, just convert to an unknown type - the byte array that it will print is still useful if (errorStr != null) { int rem = uMessageReader.BitsRemaining; DemoRef.LogError($"{errorStr}, ({rem} bit{(rem == 1 ? "" : "s")}) - " + $"{uMessageReader.FromBeginning().ToHexString()}"); UserMessage = new UnknownUserMessage(DemoRef); UserMessage.ParseStream(uMessageReader); } #endregion }
protected override void Parse(ref BitStreamReader bsr) { TableName = bsr.ReadNullTerminatedString(); MaxEntries = (short)bsr.ReadUShort(); NumEntries = (int)bsr.ReadUInt(BitUtils.HighestBitIndex(MaxEntries) + 1); uint dataLen = bsr.ReadUInt(DemoInfo.IsLeft4Dead2() ? 21 : 20); UserDataFixedSize = bsr.ReadBool(); UserDataSize = (int)(UserDataFixedSize ? bsr.ReadUInt(12) : 0); UserDataSizeBits = (int)(UserDataFixedSize ? bsr.ReadUInt(4) : 0); if (DemoRef.Header.NetworkProtocol >= 15) { Flags = (StringTableFlags)bsr.ReadUInt(DemoInfo.NewDemoProtocol ? 2 : 1); } DemoRef.StringTablesManager.CreateStringTable(this); TableUpdates = new StringTableUpdates(DemoRef, TableName, NumEntries, true); TableUpdates.ParseStream(bsr.SplitAndSkip(dataLen)); }
// we're reading a player_info_t, so take alignment into account protected override void Parse(ref BitStreamReader bsr) { if (DemoInfo.NewDemoProtocol && !DemoInfo.IsLeft4Dead1()) { SteamId = (CSteamId)bsr.ReadULong(); } Name = bsr.ReadStringOfLength(MaxPlayerNameLength); UserId = bsr.ReadSInt(); Guid = bsr.ReadStringOfLength(SignedGuidLen + 1); bsr.SkipBytes(3); FriendsId = bsr.ReadUInt(); FriendsName = bsr.ReadStringOfLength(MaxPlayerNameLength); FakePlayer = bsr.ReadByte() != 0; IsHlTv = bsr.ReadByte() != 0; bsr.SkipBytes(2); CustomFiles = new[] { bsr.ReadUInt(), bsr.ReadUInt(), bsr.ReadUInt(), bsr.ReadUInt() }; FilesDownloaded = bsr.ReadByte(); bsr.SkipBytes(3); // for demo protocol 4 there's 4 additional bytes somewhere for some reason }
public static void ChangeDemoDir(SourceDemo demo, Stream s, string newDir) { void Write(byte[] buf) => s.Write(buf, 0, buf.Length); string old = demo.Header.GameDirectory; if (old == newDir) { // no difference Write(demo.Reader.Data); return; } int lenDiff = newDir.Length - old.Length; BitStreamReader bsr = demo.Reader; byte[] dirBytes = Encoding.ASCII.GetBytes(newDir); Write(bsr.ReadBytes(796)); Write(dirBytes); // header doesn't matter but I change it anyway Write(new byte[260 - newDir.Length]); bsr.SkipBytes(260); Write(bsr.ReadBytes(12)); byte[] tmp = BitConverter.GetBytes((uint)(bsr.ReadUInt() + lenDiff)); if (!BitConverter.IsLittleEndian) { tmp = tmp.Reverse().ToArray(); } Write(tmp); foreach (SignOn signOn in demo.FilterForPacket <SignOn>().Where(signOn => signOn.FilterForMessage <SvcServerInfo>().Any())) { // catch up to signOn packet int byteCount = (signOn.Reader.AbsoluteBitIndex - bsr.AbsoluteBitIndex) / 8; Write(bsr.ReadBytes(byteCount)); bsr.SkipBits(signOn.Reader.BitLength); BitStreamWriter bsw = new BitStreamWriter(); BitStreamReader signOnReader = signOn.Reader; bsw.WriteBits(signOnReader.ReadRemainingBits()); signOnReader = signOnReader.FromBeginning(); int bytesToMessageStreamSize = demo.DemoInfo.MaxSplitscreenPlayers * 76 + 8; signOnReader.SkipBytes(bytesToMessageStreamSize); // edit the message stream length - read uint, and edit at index before the reading of said uint bsw.EditIntAtIndex((int)(signOnReader.ReadUInt() + lenDiff), signOnReader.CurrentBitIndex - 32, 32); // actually change the game dir SvcServerInfo serverInfo = signOn.FilterForMessage <SvcServerInfo>().Single(); int editIndex = serverInfo.GameDirBitIndex - signOn.Reader.AbsoluteBitIndex; bsw.RemoveBitsAtIndex(editIndex, old.Length * 8); bsw.InsertBitsAtIndex(dirBytes, editIndex, newDir.Length * 8); Write(bsw.AsArray); } Write(bsr.ReadRemainingBits().bytes); }
protected override void Parse(ref BitStreamReader bsr) { EntryCount = bsr.ReadByte(); uint bitLen = DemoInfo.Game == SourceGame.PORTAL_1_1910503 ? bsr.ReadVarUInt32() : bsr.ReadUInt(DemoInfo.IsLeft4Dead2() ? 18 : 17); _data = bsr.SplitAndSkip(bitLen); // todo }
protected override void Parse(ref BitStreamReader bsr) { NeedsDecoder = bsr.ReadBool(); Name = bsr.ReadNullTerminatedString(); ExpectedPropCount = (int)bsr.ReadUInt(DemoInfo.Game == SourceGame.HL2_OE ? 9 : 10); SendProps = new List <SendTableProp>(ExpectedPropCount); for (int i = 0; i < ExpectedPropCount; i++) { var sendProp = new SendTableProp(DemoRef, this); SendProps.Add(sendProp); sendProp.ParseStream(ref bsr); } }
protected override void Parse(ref BitStreamReader bsr) { // first, we read the main message info here MaxEntries = (ushort)bsr.ReadUInt(11); IsDelta = bsr.ReadBool(); DeltaFrom = IsDelta ? bsr.ReadSInt() : -1; BaseLine = bsr.ReadUInt(1); UpdatedEntries = (ushort)bsr.ReadUInt(11); uint dataLen = bsr.ReadUInt(20); UpdateBaseline = bsr.ReadBool(); _entBsr = bsr.SplitAndSkip(dataLen); #if !FORCE_PROCESS_ENTS if ((DemoRef.DemoParseResult & DemoParseResult.EntParsingEnabled) == 0 || (DemoRef.DemoParseResult & DemoParseResult.EntParsingFailed) != 0) { return; } #endif // now, we do some setup for ent parsing ref EntitySnapshot?snapshot = ref DemoRef.EntitySnapshot;
protected override void Parse(ref BitStreamReader bsr) { DataType = (CustomDataType)bsr.ReadSInt(); uint size = bsr.ReadUInt(); DataMessage = CustomDataFactory.CreateCustomDataMessage(DemoRef, DataType); try { DataMessage.ParseStream(bsr.SplitAndSkip(size * 8)); } catch (Exception e) { DemoRef.LogError($"error while parsing custom data of type: {DataType}... {e.Message}"); DataMessage = new UnknownCustomDataMessage(DemoRef); } }
protected override void Parse(ref BitStreamReader bsr) { SignOnState = (SignOnState)bsr.ReadByte(); SpawnCount = bsr.ReadSInt(); if (DemoInfo.NewDemoProtocol) { NumServerPlayers = bsr.ReadUInt(); int length = (int)bsr.ReadUInt(); if (length > 0) { PlayerNetworkIds = bsr.ReadBytes(length); } length = (int)bsr.ReadUInt(); if (length > 0) // the string still seams to be null terminated (sometimes?) { MapName = bsr.ReadStringOfLength(length).Split(new char[] { '\0' }, 2)[0]; } } if (SignOnState == SignOnState.PreSpawn) { DemoRef.ClientSoundSequence = 1; // reset sound sequence number after receiving SignOn sounds } }
protected override void Parse(ref BitStreamReader bsr) { SendPropType = DemoInfo.SendPropTypes[(int)bsr.ReadUInt(5)]; Name = bsr.ReadNullTerminatedString(); Flags = (int)bsr.ReadUInt(DemoInfo.SendPropFlagBits); if (DemoInfo.NewDemoProtocol && !DemoInfo.IsLeft4Dead1()) { Priority = bsr.ReadByte(); } if (SendPropType == SendPropType.DataTable || DemoInfo.PropFlagChecker.HasFlag(Flags, PropFlag.Exclude)) { ExcludeDtName = bsr.ReadNullTerminatedString(); } else { switch (SendPropType) { case SendPropType.String: case SendPropType.Int: case SendPropType.Float: case SendPropType.Vector3: case SendPropType.Vector2: LowValue = bsr.ReadFloat(); HighValue = bsr.ReadFloat(); NumBits = bsr.ReadUInt(DemoInfo.SendPropNumBitsToGetNumBits); break; case SendPropType.Array: NumElements = bsr.ReadUInt(10); break; default: throw new ArgumentOutOfRangeException(nameof(SendPropType), $"Invalid prop type: {SendPropType}"); } } }
protected override void Parse(ref BitStreamReader bsr) { int byteSize = (int)bsr.ReadUInt(); int indexBeforeData = bsr.CurrentBitIndex; try { Tables = new List <SendTable>(); while (bsr.ReadBool()) { var table = new SendTable(DemoRef); Tables.Add(table); table.ParseStream(ref bsr); } ushort classCount = bsr.ReadUShort(); ServerClasses = new List <ServerClass>(classCount); for (int i = 0; i < classCount; i++) { var @class = new ServerClass(DemoRef, null); ServerClasses.Add(@class); @class.ParseStream(ref bsr); // I assume in many places that the ID of the table matches its index if (i != ServerClasses[i].DataTableId) { throw new ConstraintException("server class ID does not match its index in the list"); } } // in case SvcServerInfo parsing fails DemoRef.EntBaseLines ??= new EntityBaseLines(DemoRef, ServerClasses.Count); // re-init the baselines if the count doesn't match (maybe I should just init them from here?) if (DemoRef.EntBaseLines !.Baselines.Length != classCount) { DemoRef.EntBaseLines.ClearBaseLineState(classCount); } // create the prop list for each class DemoRef.DataTableParser = new DataTableParser(DemoRef, this); DemoRef.DataTableParser.FlattenClasses(true); } catch (Exception e) { DemoRef.LogError($"exception while parsing datatables\n\texception: {e.Message}"); Debug.WriteLine(e); } bsr.CurrentBitIndex = indexBeforeData + (byteSize << 3); }
protected override void Parse(ref BitStreamReader bsr) { Reliable = bsr.ReadBool(); int soundCount = Reliable ? 1 : bsr.ReadByte(); int dataBitLen = (int)bsr.ReadUInt(Reliable ? 8 : 16); BitStreamReader soundBsr = bsr.SplitAndSkip(dataBitLen); SoundInfo sound = new SoundInfo(DemoRef); SoundInfo delta = new SoundInfo(DemoRef); delta.SetDefault(); Exception?e = null; try { Sounds = new SoundInfo[soundCount]; for (int i = 0; i < soundCount; i++) { sound.ParseDelta(ref soundBsr, delta); delta = sound; if (Reliable) // client is incrementing the reliable sequence numbers itself { DemoRef.ClientSoundSequence = ++DemoRef.ClientSoundSequence & SndSeqNumMask; if (sound.SequenceNumber != 0) { throw new ArgumentException($"expected sequence number 0, got: {sound.SequenceNumber}"); } sound.SequenceNumber = DemoRef.ClientSoundSequence; } Sounds[i] = new SoundInfo(sound); } } catch (Exception exp) { e = exp; } if (e != null) { Sounds = null; DemoRef.LogError($"exception while parsing {nameof(SoundInfo)}: {e.Message}"); } else if (soundBsr.BitsRemaining != 0) { Sounds = null; DemoRef.LogError($"exception while parsing {nameof(SoundInfo)}: {soundBsr.BitsRemaining} bits left to read"); } }
protected override void Parse(ref BitStreamReader bsr) { var soundIndexBits = DemoInfo.IsLeft4Dead2() ? DemoInfo.Game >= SourceGame.L4D2_2091 ? 15 : 14 : 13; SoundIndex = (int)bsr.ReadUInt(soundIndexBits); var mgr = DemoRef.StringTablesManager; if (mgr.TableReadable.GetValueOrDefault(TableNames.SoundPreCache)) { if (SoundIndex >= mgr.Tables[TableNames.SoundPreCache].Entries.Count) { DemoRef.LogError($"{GetType().Name} - sound index out of range: {SoundIndex}"); } else if (SoundIndex != 0) { SoundName = mgr.Tables[TableNames.SoundPreCache].Entries[SoundIndex].EntryName; } } }
public void WriteUInts() { var random = new Random(0); BitStreamWriter bsw = new BitStreamWriter(); List <(uint val, int size)> bits = new List <(uint val, int size)>(); for (int _ = 0; _ < Iterations; _++) { (uint val, int size)r = ((uint)random.Next(), random.Next(1, 33)); r.val &= uint.MaxValue >> (32 - r.size); bits.Add(r); bsw.WriteBitsFromUInt(r.val, r.size); } BitStreamReader bsr = new BitStreamReader(bsw); for (int i = 0; i < Iterations; i++) { Assert.AreEqual(bits[i].val, bsr.ReadUInt(bits[i].size), $"index: {i}"); } }
protected override void Parse(ref BitStreamReader bsr) { byte typeVal = bsr.ReadByte(); Type = DemoPacket.ByteToPacketType(DemoInfo, typeVal); if (Type == PacketType.Unknown) { throw new ArgumentException("no byte->packet mapper found for this game!"); } else if (Type == PacketType.Invalid) { throw new ArgumentException($"Illegal packet type: {typeVal}"); } // very last byte is cut off in (all?) demos, copy data from the previous frame if this is the case Tick = bsr.BitsRemaining >= 32 ? bsr.ReadSInt() : (int)bsr.ReadUInt(24) | (DemoRef.Frames[^ 2].Tick & (0xff << 24));
public void SkipBits() { var random = new Random(0); BitStreamWriter bsw = new BitStreamWriter(); List <(int skipCount, uint val)> data = new List <(int skipCount, uint val)>(); byte[] randArr = new byte[100 / 8 + 1]; random.NextBytes(randArr); for (int _ = 0; _ < Iterations; _++) { (int skip, uint val)r = (random.Next(0, 100), (uint)random.Next()); data.Add(r); bsw.WriteBits(randArr, r.skip); bsw.WriteUInt(r.val); } BitStreamReader bsr = new BitStreamReader(bsw); for (int i = 0; i < Iterations; i++) { bsr.SkipBits(data[i].skipCount); Assert.AreEqual(data[i].val, bsr.ReadUInt(), $"index: {i}"); } }
protected override void Parse(ref BitStreamReader bsr) { EntityIndex = bsr.ReadUInt(DemoInfo.MaxEdictBits); }