diff --git a/DemoInfo/DP/DemoPacketParser.cs b/DemoInfo/DP/DemoPacketParser.cs index 83138aa..7414572 100644 --- a/DemoInfo/DP/DemoPacketParser.cs +++ b/DemoInfo/DP/DemoPacketParser.cs @@ -45,9 +45,11 @@ public static void ParsePacket(IBitStream bitstream, DemoParser demo) } else if (cmd == (int)SVC_Messages.svc_UpdateStringTable) { new UpdateStringTable().Parse(bitstream, demo); } else if (cmd == (int)NET_Messages.net_Tick) { //and all this other stuff - new NETTick().Parse(bitstream, demo); + new NETTick().Parse(bitstream, demo); } else if (cmd == (int)SVC_Messages.svc_UserMessage) { new UserMessage().Parse(bitstream, demo); + } else if (cmd == (int)SVC_Messages.svc_ServerInfo) { + new ServerInfo().Parse(bitstream, demo); } else { //You can use this flag to see what information the other packets contain, //if you want. Then you can look into the objects. Has some advnatages, and some disdavantages (mostly speed), diff --git a/DemoInfo/DP/FastNetmessages/ServerInfo.cs b/DemoInfo/DP/FastNetmessages/ServerInfo.cs new file mode 100644 index 0000000..e388741 --- /dev/null +++ b/DemoInfo/DP/FastNetmessages/ServerInfo.cs @@ -0,0 +1,137 @@ +using System; +using System.IO; + +namespace DemoInfo +{ + public struct ServerInfo + { + public Int32 Protocol; + public Int32 ServerCount; + public bool IsDedicated; + public bool IsOfficialValveServer; + public bool IsHltv; + public bool IsReplay; + public bool IsRedirectingToProxyRelay; + public Int32 COs; + public UInt32 MapCrc; + public UInt32 ClientCrc; + public UInt32 StringTableCrc; + public Int32 MaxClients; + public Int32 MaxClasses; + public Int32 PlayerSlot; + public float TickInterval; + public string GameDir; + public string MapName; + public string MapGroupName; + public string SkyName; + public string HostName; + public UInt32 PublicIp; + public UInt64 UgcMapId; + + + public void Parse(IBitStream bitstream, DemoParser parser) + { + while (!bitstream.ChunkFinished) + { + var desc = bitstream.ReadProtobufVarInt(); + var wireType = desc & 7; + var fieldnum = desc >> 3; + + if (wireType == 5) + { + if (fieldnum == 14) + { + parser.TickInterval = bitstream.ReadFloat(); + } + else + { + var val = bitstream.ReadInt(32); + switch (fieldnum) + { + case 8: + MapCrc = val; + break; + case 9: + ClientCrc = val; + break; + case 10: + StringTableCrc = val; + break; + } + } + } + else if (wireType == 2) + { + var val = bitstream.ReadProtobufString(); + + switch (fieldnum) + { + case 15: + GameDir = val; + break; + case 16: + MapName = val; + break; + case 17: + MapGroupName = val; + break; + case 18: + SkyName = val; + break; + case 19: + HostName = val; + break; + } + } + else if (wireType == 0) + { + var val = bitstream.ReadProtobufVarInt(); + var boolval = (val == 0) ? false : true; + + switch (fieldnum) + { + case 1: + Protocol = val; + break; + case 2: + ServerCount = val; + break; + case 3: + IsDedicated = boolval; + break; + case 4: + IsOfficialValveServer = boolval; + break; + case 5: + IsHltv = boolval; + break; + case 6: + IsReplay = boolval; + break; + case 7: + COs = val; + break; + case 11: + MaxClients = val; + break; + case 12: + MaxClasses = val; + break; + case 13: + PlayerSlot = val; + break; + case 20: + PublicIp = (uint)val; + break; + case 21: + IsRedirectingToProxyRelay = boolval; + break; + case 22: + UgcMapId = (uint)val; + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/DemoInfo/DemoParser.cs b/DemoInfo/DemoParser.cs index f24c425..58ea907 100644 --- a/DemoInfo/DemoParser.cs +++ b/DemoInfo/DemoParser.cs @@ -34,6 +34,16 @@ public class DemoParser : IDisposable /// public event EventHandler HeaderParsed; + /// + /// Raised when header data is corrupted and timings are zero and null. + /// + public event EventHandler HeaderCorrupted; + + /// + /// Raised when the time variables have been fixed for a demo with a corrupted header + /// + public event EventHandler TimeFixed; + /// /// Occurs when the match started, so when the "begin_new_match"-GameEvent is dropped. /// This usually right before the freezetime of the 1st round. Be careful, since the players @@ -259,6 +269,11 @@ public string Map { /// The header. public DemoHeader Header { get; private set; } + /// + /// True when header is corrupted + /// + public bool IsHeaderCorrupted { get; private set; } + /// /// Gets the participants of this game /// @@ -448,7 +463,7 @@ public string TFlag /// /// The tick rate. public float TickRate { - get { return this.Header.PlaybackFrames / this.Header.PlaybackTime; } + get { return IsHeaderCorrupted ? 1/_ticktime : this.Header.PlaybackFrames / this.Header.PlaybackTime; } } /// @@ -456,8 +471,10 @@ public float TickRate { /// /// The tick time. public float TickTime { - get { return this.Header.PlaybackTime / this.Header.PlaybackFrames; } + get { return IsHeaderCorrupted ? _ticktime : this.Header.PlaybackTime / this.Header.PlaybackFrames; } } + private List tickGaps = new List(); + private float _ticktime; /// /// Gets the parsing progess. 0 = beginning, ~1 = finished (it can actually be > 1, so be careful!) @@ -474,6 +491,11 @@ public float ParsingProgess { /// The current tick. public int CurrentTick { get; private set; } + /// + /// The tickrate *of the server* + /// + public float TickInterval { get; internal set; } + /// /// The current ingame-tick as reported by the demo-file. /// @@ -525,7 +547,18 @@ public void ParseHeader() throw new InvalidDataException("Invalid Demo-Protocol"); Header = header; + IsHeaderCorrupted = (header.PlaybackTime == 0); + + if (IsHeaderCorrupted) + { + Console.WriteLine("WARNING: The header for this demo file is corrupted. TickRate, TickTime, CurrentTime will be 0 for ticks at the start of the demo. ParsingProgress, PlaybackFrames, PlaybackTicks, PlaybackTime will always be 0."); + Console.WriteLine("HeaderCorrupted event raised, TimeFixed event will be raised when time variables are repaired."); + if (HeaderCorrupted != null) + { + HeaderCorrupted(this, new HeaderParsedEventArgs(Header)); + } + } if (HeaderParsed != null) HeaderParsed(this, new HeaderParsedEventArgs(Header)); @@ -552,6 +585,28 @@ public void ParseToEnd(CancellationToken token) } } + private void FixTickTime() + { + // at the beginning of demos the tickgap can be erratic, so make sure we have 10 consecutive that are the same + int gap = tickGaps[1] - tickGaps[0]; + bool isConsecutive = true; + for (int i = 1; i < tickGaps.Count - 1; i++) { + if (tickGaps[i + 1] - tickGaps[i] != gap) + { + tickGaps.Clear(); + isConsecutive = false; + break; + } + } + + if (isConsecutive) { + _ticktime = gap * TickInterval; + + if (TimeFixed != null) + TimeFixed(this, new TimeFixedEventArgs()); + } + } + /// /// Parses the next tick of the demo. /// @@ -561,6 +616,15 @@ public bool ParseNextTick() if (Header == null) throw new InvalidOperationException ("You need to call ParseHeader first before you call ParseToEnd or ParseNextTick!"); + if (IsHeaderCorrupted && _ticktime == 0 && IngameTick > 20) { + int consecutiveGaps = 10; + if (tickGaps.Count < consecutiveGaps) + tickGaps.Add(IngameTick); + else if (tickGaps.Count == consecutiveGaps) { + FixTickTime(); + } + } + bool b = ParseTick(); for (int i = 0; i < RawPlayers.Length; i++) { @@ -1462,6 +1526,8 @@ public void Dispose () this.FireNadeWithOwnerStarted = null; this.FlashNadeExploded = null; this.HeaderParsed = null; + this.HeaderCorrupted = null; + this.TimeFixed = null; this.MatchStarted = null; this.NadeReachedTarget = null; this.PlayerKilled = null; diff --git a/DemoInfo/Events.cs b/DemoInfo/Events.cs index 7d70521..cb5604e 100644 --- a/DemoInfo/Events.cs +++ b/DemoInfo/Events.cs @@ -21,6 +21,10 @@ public class TickDoneEventArgs : EventArgs { } + public class TimeFixedEventArgs : EventArgs + { + } + public class MatchStartedEventArgs : EventArgs { }