saul / demofile-net Goto Github PK
View Code? Open in Web Editor NEWBlazing fast cross-platform demo parser library for Counter-Strike 2, written in C#.
License: MIT License
Blazing fast cross-platform demo parser library for Counter-Strike 2, written in C#.
License: MIT License
Seeking is still failing, even after the recent fix.
The example below gives following error : System.Exception: Delta on non-existent entity 185
Console.WriteLine($"starting seek test, tick count {demo.TickCount.Value}");
for (int i = 50; i < demo.TickCount.Value; i += 75 * 64)
{
Console.WriteLine($"seeking to tick {i}");
await demo.SeekToTickAsync(new DemoTick(i), default);
int tickBackward = Math.Max(1, i - 75 * 64);
Console.WriteLine($"seeking backward to tick {tickBackward}");
await demo.SeekToTickAsync(new DemoTick(tickBackward), default);
int tickOnOppositeSide = demo.TickCount.Value - i;
Console.WriteLine($"seeking to opposite side at tick {tickOnOppositeSide}");
await demo.SeekToTickAsync(new DemoTick(tickOnOppositeSide), default);
}
Console.WriteLine($"seek test done");
https://www.hltv.org/matches/2372034/mouz-nxt-vs-space-cct-season-2-europe-series-3
map : Vertigo
Also happens on some recent Matchmaking demos.
CDemoFileInfo.GameInfo?.Cs?.RoundStartTicks
property is not available in any demo.
CDemoFileInfo.GameInfo
is always null.
var stream = new MemoryStream(File.ReadAllBytes(path));
var demo = new DemoParser();
demo.DemoEvents.DemoFileInfo += e =>
{
Console.WriteLine($"DemoFileInfo:\n");
var jsonSerializerOptions = new JsonSerializerOptions() { WriteIndented = true };
string jsonStr = JsonSerializer.Serialize(e, jsonSerializerOptions);
Console.WriteLine($"RoundStartTicks: {e.GameInfo?.Cs?.RoundStartTicks?.Count}\n");
Console.WriteLine(jsonStr);
Console.WriteLine($"\n");
};
await demo.ReadAllAsync(stream);
No response
Basically this:
public ServerClass?[] ServerClasses => _serverClasses;
inside of DemoParser.Entities.cs
.
Right now, you would need to manually process CDemoClassInfo
message to get Server Classes. So this would be more convenient.
e.Weapon on PlayerHurt returns hpk2000 instead of usp_silencer. Also the same problem appears in player's Weapons, usp_silencer returned as hpk2000. It is strange since on PlayerDeath everything works right.
Hey,
same feature request as the old S1 parser saul/demofile#373
The broadcast URLs for the current major are https://cstv.challengermode.com/major/primary and https://cstv.challengermode.com/major/secondary
I assume its almost identical to the CSGO one, convars are all the same at least
Pawn position is still jittering. Tested in one of latest demo files as of today (21 feb), and in older demos also.
To visualize:
demo_seek_tick 2242
No response
No response
Currently I am doing a lot of struggle to marshal data into Unity from external .NET process or external WebAssembly module.
It would be much better if this library would support Unity engine by default.
As you may know, Unity supports only up to .NET 4.8 or .NET Standard 2.1, see docs.
I managed to compile library to .NET Standard 2.1, which runs fine in .NET 7 executable, but gives some weird errors in Unity, like InvalidProtocolBufferException
. Then there is Unity's custom AOT solution (called IL2CPP) which I haven't even tried to run with.
What do you think about it ?
This file drags in many dependencies, maybe it's better to create smaller version that only has messages which are used in demos.
Messages used in demos:
CS_UM_WeaponSound 369
CS_UM_RadioText 322
CS_UM_PlayerStatsUpdate 336
CS_UM_VotePass 347
CS_UM_VoteFailed 348
CS_UM_EndOfMatchAllPlayersData 375
CS_UM_XpUpdate 365
CS_UM_ServerRankUpdate 352
CS_UM_MatchEndConditions 334
CS_UM_SendPlayerItemFound 363
Hi, it seems we have another error with parsing after the new cs2 update, its an error that dosnt happen often occoured in 4 out of 40 demos.
error is:
CSoundEventPathCornerEntity
from another project i have a stack trace too:
Logs: 27/05/2024 - 21.08.34 - Error at HandleAbove25Demos System.Exception: Attempted to create unknown entity: CSoundEventPathCornerEntity
at DemoFile.DemoParser.<>c__DisplayClass115_0.b__1(EntityContext context)
at DemoFile.DemoParser.OnPacketEntities(CSVCMsg_PacketEntities msg)
at DemoFile.PacketEvents.ParseNetMessage(Int32 msgType, ReadOnlySpan1 buf) at DemoFile.DemoParser.OnDemoPacket(CDemoPacket msg) at DemoFile.DemoEvents.ReadDemoCommandCore[T](Action
1 callback, MessageParser1 parser, ReadOnlySpan
1 buffer, Boolean isCompressed)
at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands msgType, ReadOnlySpan1 buffer, Boolean isCompressed) at DemoFile.DemoParser.MoveNextCoreAsync(EDemoCommands msgType, Boolean isCompressed, Int32 size, CancellationToken cancellationToken) at DemoFile.DemoParser.ReadAllAsync(Stream stream, CancellationToken cancellationToken) at PowerLigaStats.DemoParsing.DemoHandler.ProcessCS2DemoRewrite(String demoFilePath, Int32 season, String division, DateTime dateTime, Boolean playoff) in C:\Users\Patri\Documents\Kode\Full-stack\PowerLigaStats\PowerLigaStats\DemoParsing\DemoHandler.cs:line 915 at PowerLigaStats.DemoParsing.DemoHandler.ProcessCS2DemoRewrite(String demoFilePath, Int32 season, String division, DateTime dateTime, Boolean playoff) in C:\Users\Patri\Documents\Kode\Full-stack\PowerLigaStats\PowerLigaStats\DemoParsing\DemoHandler.cs:line 1046 at PowerLigaStats.DemoParsing.DemoHandler.ProcessDemoFileAbove24(String demoFilePath, Int32 season, String division, DateTime dateTime, Boolean playoff) in C:\Users\Patri\Documents\Kode\Full-stack\PowerLigaStats\PowerLigaStats\DemoParsing\DemoHandler.cs:line 57 at PowerLigaStats.DemoParsing.DemoManager.HandleDemosByDivision(Int32 season, String division, List
1 allResults) in C:\Users\Patri\Documents\Kode\Full-stack\PowerLigaStats\PowerLigaStats\DemoParsing\DemoManager.cs:line 271
at PowerLigaStats.DemoParsing.DemoManager.Start(Int32 season) in C:\Users\Patri\Documents\Kode\Full-stack\PowerLigaStats\PowerLigaStats\DemoParsing\DemoManager.cs:line 720
public static class Program
{
//public static string flashNoWork = "C:\\Users\\xguys\\Desktop\\Flash issues\\crystal_vulture.dem";
public static string flashWork = "C:\\Users\\xguys\\Desktop\\Demos To test\\661577c94c197423489bd882_0_1716836561580.dem";
public static async Task Main(string[] args)
{
try
{
Stopwatch stopwatch3 = new Stopwatch();
stopwatch3.Start();
MemoryStream _fileStream;
byte[] _demoBytes;
//_demoBytes = File.ReadAllBytes(demoFilePath);
_demoBytes = await File.ReadAllBytesAsync(flashWork);
_fileStream = new MemoryStream(_demoBytes);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var demo = new DemoParser();
demo.Source1GameEvents.BombExploded += e =>
{
Console.WriteLine("Bomb exploded");
};
demo.Source1GameEvents.PlayerBlind += e =>
{
Console.WriteLine("Player blind");
};
demo.Source1GameEvents.TrPlayerFlashbanged += e =>
{
Console.WriteLine("Player flashbanged");
};
//demo.Source1GameEvents.FlashbangDetonate += e =>
//{
// Console.WriteLine("Flashbang detonate");
//};
demo.Source1GameEvents.RoundStart += e =>
{
if (!demo.GameRules.HasMatchStarted)
{
//PowerLigaManager.Log("ROund end called but we have an issue, it seems match was not started yet");
return;
}
Console.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
Debug.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
};
demo.Source1GameEvents.CsWinPanelMatch += e =>
{
Console.WriteLine("WIn match has ended");
};
await demo.ReadAllAsync(_fileStream);
stopwatch3.Stop();
stopwatch.Stop();
Console.WriteLine("Time elapsed after file data: {0}", stopwatch.Elapsed);
Console.WriteLine("Time elapsed: {0}", stopwatch3.Elapsed);
Console.WriteLine("\nFinished!");
} catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message.ToString());
}
Console.ReadKey();
}
https://drive.google.com/file/d/1ks0sX_5EVK-Cy7BT66bpXorhmJUrVAqp/view?usp=sharing
Position of player's pawn is jittering (quickly snapping from one place to another). In only 1 tick, player is teleported to distant location, and then returned back. And this is not a round restart or death. This happens throughout entire match.
What is weird is that 2 of his position coordinates become a multiple of 1024.
The example shows position of pawn in 3 consecutive ticks. The result prints:
Position (tick 39984): Vector { X = -1102.1124, Y = -73.48895, Z = -167.96875 }
Position (tick 39985): Vector { X = -2048, Y = -1024, Z = -167.96875 }
Position (tick 39986): Vector { X = -1102.3584, Y = -71.92908, Z = -167.96875 }
2nd tick is the problematic one in this case, X and Y coordinates are multiple of 1024.
Example code uses navi-javelins-vs-9-pandas-fearless-m1-mirage.dem
file.
var demo = new DemoParser();
demo.CreateTimer(new DemoTick(39984), () =>
{
var player = demo.Players.Single(_ => _.PlayerName == "Angelka");
Console.WriteLine($"Position (tick 39984): {player?.Pawn?.Origin}");
});
demo.CreateTimer(new DemoTick(39985), () =>
{
var player = demo.Players.Single(_ => _.PlayerName == "Angelka");
Console.WriteLine($"Position (tick 39985): {player?.Pawn?.Origin}");
});
demo.CreateTimer(new DemoTick(39986), () =>
{
var player = demo.Players.Single(_ => _.PlayerName == "Angelka");
Console.WriteLine($"Position (tick 39986): {player?.Pawn?.Origin}");
});
await demo.Start(File.OpenRead(path));
Every demo, but example works only with navi-javelins-vs-9-pandas-fearless-m1-mirage.dem
CCSPlayerPawn.ActiveWeapon?.ServerClass?.Name
doesn't return silenced weapons (m4a1-s, usp-s), but returns their couterparts (m4a1, hkp2000).
No response
No response
It seems that there must be some issue with the "PlayerBlind" event not always being parsed correctly from a demo, as it gets called
in some demos and on some it dosnt, it seems that the "FlashbangDetonate" event gets called every time so just seems like a parsing issue on your side, but i could be wrong. below ive attached 1 that works and 1 that dosnt that i used in my test.
public static class Program
{
public static string flashNoWork = "C:\\Users\\xguys\\Desktop\\Flash issues\\crystal_vulture.dem";
public static string flashWork = "C:\\Users\\xguys\\Desktop\\Flash issues\\hedensted_ingearme.dem";
public static async Task Main(string[] args)
{
Stopwatch stopwatch3 = new Stopwatch();
stopwatch3.Start();
MemoryStream _fileStream;
byte[] _demoBytes;
//_demoBytes = File.ReadAllBytes(demoFilePath);
_demoBytes = await File.ReadAllBytesAsync(flashWork);
_fileStream = new MemoryStream(_demoBytes);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var demo = new DemoParser();
demo.Source1GameEvents.BombExploded += e =>
{
Console.WriteLine("Bomb exploded");
};
demo.Source1GameEvents.PlayerBlind += e =>
{
Console.WriteLine("Player blind");
};
demo.Source1GameEvents.FlashbangDetonate += e =>
{
Console.WriteLine("Flashbang detonate");
};
demo.Source1GameEvents.RoundStart += e =>
{
if (!demo.GameRules.HasMatchStarted)
{
//PowerLigaManager.Log("ROund end called but we have an issue, it seems match was not started yet");
return;
}
Console.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
Debug.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
};
demo.Source1GameEvents.CsWinPanelMatch += e =>
{
Console.WriteLine("WIn match has ended");
};
await demo.ReadAllAsync(_fileStream);
stopwatch3.Stop();
stopwatch.Stop();
Console.WriteLine("Time elapsed after file data: {0}", stopwatch.Elapsed);
Console.WriteLine("Time elapsed: {0}", stopwatch3.Elapsed);
Console.WriteLine("\nFinished!");
Console.ReadKey();
}
}
https://drive.google.com/drive/folders/1VdU7tr2s_4SV_EHUOViCJm4AUWxNzvDy?usp=sharing
Sometimes, during freeze time pawn is placed outside of buy-zone.
To reproduce:
Go to: https://csunity-5172f.web.app/
Click Open -> Ok -> Select navi-javelins-vs-9-pandas-fearless-m1-mirage.dem
-> Click Pause
Observe player "victoria" on the middle of screen. If you look around, you'll see his teammates are too far away from him.
Once the round starts, his position will be correct.
His position coordinates are again 1024. You can see that in Inspector (but converted to meters, it will be 32).
No response
navi-javelins-vs-9-pandas-fearless-m1-mirage.dem
Some demos are reporting wrong round results.
Both RoundEnd event
and GameRules.CSRoundResults
give the same result, but that result doesn't match the one from HLTV.
Example : (map Vertigo) https://www.hltv.org/matches/2369946/ence-vs-astralis-pgl-cs2-major-copenhagen-2024-europe-rmr-b
official : 13 - 6
parser : 10 - 9
var path = "test.dem";
var demo = new DemoParser();
var winnerCounts = new Dictionary<int, int>();
demo.Source1GameEvents.RoundEnd += e =>
{
Console.WriteLine($"Round end: winner {(CSTeamNumber)e.Winner}");
if (winnerCounts.TryGetValue(e.Winner, out int count))
winnerCounts[e.Winner] = count + 1;
else
winnerCounts[e.Winner] = 1;
};
await demo.StartReadingAsync(File.OpenRead(path), default);
while (await demo.MoveNextAsync(default))
{
}
Console.WriteLine();
foreach (var pair in winnerCounts)
{
Console.WriteLine($"winner {pair.Key}: {pair.Value}");
}
Console.WriteLine();
Console.WriteLine("Game rules round results:");
Console.WriteLine(string.Join("\n", demo.GameRules.CSRoundResults));
Console.WriteLine("\nFinished!");
No response
I was trying to get total duration (total tick count) of demo file, and found that CDemoFileInfo
demo command has this info.
However, this command is never invoked. I am not sure if this is a bug in the library, or the command is simply not in demo file.
Anyway, is there any way to find out total duration of the match (without parsing entire file) ?
var demo = new DemoParser();
demo.DemoEvents.DemoFileInfo += (info) => Console.WriteLine("got DemoFileInfo");
await demo.Start(stream, default);
No response
Title really says all, but here is a demo if you need it:
Code example, player death event will run as normal but no RoundStart, RoundEnd or GameStart will be called, events that is rather needed to display round changes etc.
public static async Task Main(string[] args)
{
var demo = new DemoParser();
demo.Source1GameEvents.PlayerDeath += e =>
{
Console.WriteLine($"{e.Attacker?.PlayerName} [{e.Weapon}] {e.Player?.PlayerName}");
};
demo.Source1GameEvents.RoundStart += e =>
{
Console.WriteLine($"Round start");
};
demo.Source1GameEvents.GameStart += e =>
{
Console.WriteLine($"Game start");
};
demo.Source1GameEvents.RoundEnd += e =>
{
Console.WriteLine($"Round end");
};
await demo.ReadAllAsync(File.OpenRead("D:\\Testing\\Sæson 25\\3divC\\goatgang_vs_UnsignedPuggers_map1_de_ancient_2024-02-07_20-01-15_6599f0ecca2465f94caa2467_1707336075213\\6599f0ecca2465f94caa2467_0_1707336075213.dem"));
Console.WriteLine("\nFinished!");
}
https://we.tl/t-3WP54hnNAc
Apologizes for 2 different link types, i realized google made alot more sense after uploading the first one 😓
https://drive.google.com/file/d/1QCmjuZsS2QkglWWbccO77I2Ag6R5bJJ8/view?usp=sharing
When attempting to use the example from readme.md
with a Dathost match API demo we receive the following error. The demo in question can be found here
Unhandled exception. System.NotSupportedException: Unrecognised serializer field: m_firePositions
at DemoFile.Sdk.CInferno.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at DemoFile.Sdk.CFireCrackerBlast.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at DemoFile.Sdk.DecoderSet.<>c__DisplayClass4_0`1.<CreateDecoder>b__1(SerializableField field)
at System.Linq.Enumerable.SelectArrayIterator`2.ToArray()
at DemoFile.Sdk.DecoderSet.CreateDecoder[T](SerializerKey serializerKey)
at DemoFile.Sdk.DecoderSet.GetDecoder[T](SerializerKey serializerKey)
at DemoFile.Sdk.DecoderSet.GetDecoder(String className)
at DemoFile.DemoParser.OnDemoClassInfo(CDemoClassInfo msg)
at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands msgType, ReadOnlySpan`1 buffer)
at DemoFile.DemoParser.Start(Stream stream, CancellationToken cancellationToken)
at Program.Main(String[] args) in D:\Users\syoung\Downloads\demoscrape3\demoscrape3\Program.cs:line 18
at Program.<Main>(String[] args)
For reproducing this issue you need parse special demo. I`m gonna send you demo file tomorrow. May be you can find some decisions about this. I am using latest version of DemoFile
Google.Protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
at Google.Protobuf.ParsingPrimitives.ParseTag(ReadOnlySpan`1& buffer, ParserInternalState& state)
at CNETMsg_Tick.pb::Google.Protobuf.IBufferMessage.InternalMergeFrom(ParseContext& input)
at DemoFile.PacketEvents.ParseNetMessage(Int32 msgType, ReadOnlySpan`1 buf)
at DemoFile.DemoParser.OnDemoPacket(CDemoPacket msg)
at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands msgType, ReadOnlySpan`1 buffer)
at DemoFile.DemoParser.Start(Stream stream, CancellationToken cancellationToken)
at Questera.CsGo.Parser.Parser.ParseAsync(String fileName, Int64 userSteamId) in /src/CsGoProvider/Questera.CsGo.Parser/Parser.cs:line 44
at Questera.CsGo.Flows.DownloadMatchStatistics.Steps.ParseDemoStep.RunAsync() in /src/CsGoProvider/Questera.CsGo.Flows/DownloadMatchStatistics/Steps/ParseDemoStep.cs:line 29
at CyberCube.Common.TinyFlow.Services.FlowExecutor.ExecuteStepAsync[TData](FlowStep step, TData data)
at CyberCube.Common.TinyFlow.Services.FlowExecutor.ExecuteStepsAsync[TData,TResult](IList`1 steps, TData data)
at CyberCube.Common.TinyFlow.Services.FlowExecutor.ExecuteAsync[TFlow,TData,TResult](TData data)
at Questera.CsGo.Handlers.Statistics.DownloadMatchStatisticsHandler.Handle(Request request, CancellationToken cancellationToken)
No response
No response
System.Exception: Attempted to create unknown entity: CSoundEventPathCornerEntity
at DemoFile.DemoParser.<>c__DisplayClass115_0.b__1(EntityContext context)
at DemoFile.DemoParser.OnPacketEntities(CSVCMsg_PacketEntities msg)
at DemoFile.PacketEvents.ParseNetMessage(Int32 msgType, ReadOnlySpan1 buf) at DemoFile.DemoParser.OnDemoPacket(CDemoPacket msg) at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands msgType, ReadOnlySpan
1 buffer, Boolean isCompressed)
at DemoFile.DemoParser.MoveNextCoreAsync(EDemoCommands msgType, Boolean isCompressed, Int32 size, CancellationToken cancellationToken)
at DemoFile.DemoParser.ReadAllAsync(Stream stream, CancellationToken cancellationToken)****
No response
http://replay186.valve.net/730/003688111663585689781_1784675935.dem.bz2
Performance issues, in the Git you state that parsing a whole 1 hour demo can be done in 1.5 seconds.
i have a pc with 64 gigs of ddr5
AMD Ryzen 7 7800X3D
RTX 3070ti
running on a nvme drive.
but i am getting 8-10 seconds to just run this demo that ended 13-4. like 20 minutes of gameplay. Am i doing something wrong here? it seems rather slow.
Test was done on a console application on .net 8 with the below code.
public static async Task Main(string[] args)
{
await using var fileStream = File.OpenRead(demoFilePath);
Stopwatch stopwatch3 = new Stopwatch();
stopwatch3.Start();
bool bombExploded = false;
var demo = new DemoParser();
demo.Source1GameEvents.BombExploded += e =>
{
Console.WriteLine("Bomb exploded");
bombExploded = true;
};
demo.Source1GameEvents.RoundStart += e =>
{
if (!demo.GameRules.HasMatchStarted)
{
//PowerLigaManager.Log("ROund end called but we have an issue, it seems match was not started yet");
return;
}
Console.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
};
demo.Source1GameEvents.CsWinPanelMatch += e =>
{
Console.WriteLine("WIn match has ended");
};
await demo.ReadAllAsync(fileStream);
stopwatch3.Stop();
Console.WriteLine("Time elapsed: {0}", stopwatch3.Elapsed);
Console.WriteLine("\nFinished!");
}
https://drive.google.com/file/d/1Oi80oKMHf2DvIYSI42UL4kxvICLMGj2G/view?usp=sharing
It would be great if NativeAOT compilation would be supported by the library.
This would give the benefits of faster execution, and possibility to build native library (DLL) which can then be linked by any other program (regardless of programming language).
Right now, I get this error for most of demos:
Unhandled Exception: Google.Protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
at Google.Protobuf.ParsingPrimitives.ParseTag(ReadOnlySpan`1&, ParserInternalState&) + 0x133
at CNETMsg_Tick.pb::Google.Protobuf.IBufferMessage.InternalMergeFrom(ParseContext&) + 0x1f
at Google.Protobuf.MessageExtensions.MergeFrom(IMessage, ReadOnlySpan`1, Boolean, ExtensionRegistry) + 0xdb
at Google.Protobuf.MessageParser`1.ParseFrom(ReadOnlySpan`1) + 0x2f
at DemoFile.PacketEvents.ParseNetMessage(Int32, ReadOnlySpan`1) + 0xd3
at DemoFile.DemoParser.OnDemoPacket(CDemoPacket msg) + 0x328
at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands, ReadOnlySpan`1) + 0x1cf
at DemoFile.DemoParser.<Start>d__46.MoveNext() + 0x824
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x20
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0xb6
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task) + 0x42
at Program.<Main>d__0.MoveNext() + 0x2b6
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x20
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0xb6
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task) + 0x42
at Program.<Main>(String[]) + 0x20
at DemoFile.Example.Basic!<BaseAddress>+0x44a35b
It could be a bug in Google.Protobuf
library, but I did not investigate further, for now.
I have 2 demos that are resulting in EOF. Demos works fine on 2D demo sites and in cs2 itself. It seems to be a contained error to this parser.
public static async Task Main(string[] args)
{
bool bombExploded = false;
var demo = new DemoParser();
demo.Source1GameEvents.BombExploded += e =>
{
Console.WriteLine("Bomb exploded");
bombExploded = true;
};
demo.Source1GameEvents.PlayerDeath += e =>
{
Console.WriteLine($"{e.Attacker?.PlayerName} from {e.Attacker?.Team} [{e.Weapon}] {e.Player?.PlayerName} from {e.Player?.Team}");
};
demo.EntityEvents.CCSGameRulesProxy.AddChangeCallback(proxy => proxy.GameRules?.RoundStartCount, (proxy, _, _) =>
{
var roundStartCountStuff = proxy.GameRules!.RoundStartCount;
var roundStartCountStuff2 = proxy.GameRules!.RoundStartRoundNumber;
bombExploded = false;
Console.WriteLine($"count 1{roundStartCountStuff} count 2 {roundStartCountStuff2} ct score: {demo.TeamCounterTerrorist.Score} t score: {demo.TeamTerrorist.Score}");
});
demo.Source1GameEvents.CsWinPanelMatch += e =>
{
Console.WriteLine("WIn match has ended");
};
await demo.ReadAllAsync(File.OpenRead(baseDirectory + "\\FatalUtility_vs_EgedalEsport_map1_de_nuke_2024-01-16_18-00-45_6599f0f1ca2465f94caa2607_1705428045421.dem"));
//await demo.
Console.WriteLine("\nFinished!");
}
https://drive.google.com/drive/folders/1dgPmwyjy5UlEsDW-eF8rdyRNFxAmEzCa?usp=sharing
This error pops up when trying to load a CS2 match from a .dem
file:
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
Example code used:
using DemoFile;
internal class Program
{
public static async Task Main(string[] args)
{
var path = args.SingleOrDefault() ?? throw new Exception("Expected a single argument: <path to .dem>");
var demo = new DemoParser();
demo.Source1GameEvents.PlayerDeath += e =>
{
Console.WriteLine($"{e.Attacker?.PlayerName} [{e.Weapon}] {e.Player?.PlayerName}");
};
await demo.Start(File.OpenRead(path));
Console.WriteLine("\nFinished!");
}
}
The error seems to appear when trying to access the e.Player
or e.Attacker
properties.
I'm in WinForms using version NET version .NET 7.0
.
I'm leaving a link to the file used here.
I'm trying to run the KillFeed sample with a demo from 2023-12-04 but it throws the error:
'Unrecognised serializer field: m_ePlayerFireEvent'
I've tried to run the SdkGen, but it also throws the same error.
No response
http://replay201.valve.net/730/003654429170693308593_1898149149.dem.bz2
CBaseCSGrenade.GrenadeCount
is always 1, meaning you can't detect if player has 2 flashbangs.
I tried reading Clip1, Clip2, other entries in ReserveAmmo array, and checking if there are multiple CFlashbang entities.
It happens in all demos that I tried (~ 15 of them).
var path = ...;
var demo = new DemoParser();
var stream = new MemoryStream(File.ReadAllBytes(path));
await demo.StartReadingAsync(stream, default);
while (await demo.MoveNextAsync(default))
{
var pawns = demo.Players
.Where(p => p.Pawn != null && p.Pawn.IsAlive)
.Select(p => (CCSPlayerPawn)p.Pawn!);
var weapons = pawns
.SelectMany(p => p.Weapons)
.Where(w => w.ReserveAmmo.Length > 1)
.Where(w => w.ReserveAmmo[1] > 1)
.OfType<CBaseCSGrenade>()
.ToArray();
if (weapons.Length == 0)
continue;
Console.WriteLine($"[{demo.CurrentDemoTick.Value}]\n{string.Join("\n", weapons.Select(w => $"{w.OwnerEntityHandle.Value}, {w.AttributeManager?.Item?.Name}, clip {w.Clip1} {w.Clip2}, ammo {w.ReserveAmmo.ElementAtOrDefault(0)} {w.ReserveAmmo.ElementAtOrDefault(1)} {w.ReserveAmmo.ElementAtOrDefault(2)}"))}\n\n");
Console.Out.Flush();
}
Console.WriteLine("\nFinished!");
No response
There should be seeking support in the library.
It seems that demos already have enough data for this to be possible, eg. snapshots on every 60 sec, round-start ticks, etc...
Backward seeking through time should also be possible.
For best usability, I think library should have an API like this:
DemoParser.SaveToSnapshot(Snapshot s);
DemoParser.RestoreFromSnapshot(Snapshot s);
class Snapshot
{
List<Entity> entities;
List<StringTable> stringTables;
int tick;
long streamPosition;
CDemoFileHeader header;
...
}
Looks like there is new field which is not supported by decoders. Please, look at the attached demo
No response
CS2 updated recently, and none of the demos from HLTV (after 7th February) are working.
Currently they give the following error:
System.NotSupportedException: Unrecognised serializer field: m_hSequence
at DemoFile.Sdk.ISkeletonAnimationController.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0xb4
at DemoFile.Sdk.CBaseAnimGraphController.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x64b
at DemoFile.Sdk.CBodyComponentBaseAnimGraph.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x175
at System.Linq.Enumerable.SelectArrayIterator`2.Fill(ReadOnlySpan`1, Span`1, Func`2) + 0x45
at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() + 0x8d
at DemoFile.Sdk.DecoderSet.CreateDecoder[T](SerializerKey) + 0xe2
at DemoFile.Sdk.DecoderSet.GetDecoder[T](SerializerKey) + 0x77
at DemoFile.Sdk.CBodyComponent.CreateDowncastDecoder(SerializerKey, DecoderSet, Func`1&) + 0x2fe
at DemoFile.Sdk.CBaseEntity.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0xd8
at DemoFile.Sdk.CBaseModelEntity.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x12a6
at DemoFile.Sdk.CBaseAnimGraph.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x47c
at DemoFile.Sdk.CBaseFlex.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x214
at DemoFile.Sdk.CEconEntity.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x4e6
at DemoFile.Sdk.CBasePlayerWeapon.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x40a
at DemoFile.Sdk.CCSWeaponBase.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0xf18
at DemoFile.Sdk.CCSWeaponBaseGun.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet) + 0x1d4
at System.Linq.Enumerable.SelectArrayIterator`2.Fill(ReadOnlySpan`1, Span`1, Func`2) + 0x45
at System.Linq.Enumerable.SelectArrayIterator`2.ToArray() + 0x8d
at DemoFile.Sdk.DecoderSet.CreateDecoder[T](SerializerKey) + 0xe2
at DemoFile.Sdk.DecoderSet.GetDecoder[T](SerializerKey) + 0x77
at DemoFile.Sdk.DecoderSet.TryGetDecoder(String, Type&, SendNodeDecoder`1&) + 0x3d20
at DemoFile.Sdk.DecoderSet.GetDecoder(String) + 0x27
at DemoFile.DemoParser.OnDemoClassInfo(CDemoClassInfo msg) + 0x2c9
at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands, ReadOnlySpan`1) + 0x198
at DemoFile.DemoParser.<MoveNextAsync>d__61.MoveNext() + 0x496
No response
No response
How would I use the library from real-time application which has it's own Message Loop (eg. GUI application) ?
Right now, the only way to parse demo file would be to call async method which would run in a different thread. It would be much better if the control of parsing is done by user of the library. That way, he can pause the parser, move to next tick, seek through history, etc.
Basically, a non-async version which allows the user to advance through demo file tick by tick.
I already implemented this myself (with additional API, no breaking changes), and would like to open PR, but I wanted to ask you what you think about it ?
Hi, decided to ask this via an issue.
I've been looking through the API but have not found a way to retrieve a UTC for the match start, I don't know if this is a part of the demo file though. I assumed since csstats and leetify for example do know when matches start (but this could also be because they receive the demos from Valve and in that delivery, an UTC start timestamp might get included? Don't know).
Does anyone know if there is a way to get the UTC timestamp of a match via the demo?
Thanks in advance, this parser has already helped me a lot! ❤️
In the specified demo, seeking fails when trying to seek past the tick 192001, with error:
Unhandled exception. DemoFile.InvalidDemoException: Could not find DemFullPacket at tick 192001
at DemoFile.DemoParser.SkipToFullPacketTickAsync(DemoTick targetTick, CancellationToken cancellationToken) in demofile-net\src\DemoFile\DemoParser.FullPacket.cs:line 145
at DemoFile.DemoParser.SeekToTickAsync(DemoTick targetTick, CancellationToken cancellationToken) in demofile-net\src\DemoFile\DemoParser.FullPacket.cs:line 97
at Program.Main(String[] args) in demofile-net\examples\DemoFile.Example.Basic\Program.cs:line 155
at Program.<Main>(String[] args)
var stream = new MemoryStream(File.ReadAllBytes(path));
await demo.StartReadingAsync(stream, default);
await demo.SeekToTickAsync(new DemoTick(192000), default); // works for anything below 192001
Console.WriteLine($"seeked to tick {demo.CurrentDemoTick.Value}");
await demo.SeekToTickAsync(new DemoTick(193000), default); // fails
Console.WriteLine($"seeked to tick {demo.CurrentDemoTick.Value}");
https://www.hltv.org/matches/2372034/mouz-nxt-vs-space-cct-season-2-europe-series-3
map : Vertigo
Also happens on some recent Matchmaking demos.
System.NotSupportedException: Unrecognised serializer field: m_nBloodType
at DemoFile.Sdk.CEntityInstance.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at DemoFile.Sdk.CBaseEntity.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at DemoFile.Sdk.CBaseModelEntity.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at DemoFile.Sdk.CBaseAnimGraph.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at DemoFile.Sdk.CEconEntity.CreateFieldDecoder(SerializableField field, DecoderSet decoderSet)
at System.Linq.Enumerable.SelectArrayIterator2.Fill(ReadOnlySpan
1 source, Span1 destination, Func
2 func)
at System.Linq.Enumerable.SelectArrayIterator2.ToArray() at DemoFile.Sdk.DecoderSet.CreateDecoder[T](SerializerKey serializerKey) at DemoFile.Sdk.DecoderSet.GetDecoder[T](SerializerKey serializerKey) at DemoFile.Sdk.DecoderSet.TryGetDecoder(String className, Type& classType, SendNodeDecoder
1& decoder)
at DemoFile.DemoParser.OnDemoClassInfo(CDemoClassInfo msg)
at DemoFile.DemoEvents.ReadDemoCommandCore[T](Action1 callback, MessageParser
1 parser, ReadOnlySpan1 buffer, Boolean isCompressed) at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands msgType, ReadOnlySpan
1 buffer, Boolean isCompressed)
at DemoFile.DemoParser.MoveNextCoreAsync(EDemoCommands msgType, Boolean isCompressed, Int32 size, CancellationToken cancellationToken)
at DemoFile.DemoParser.StartReadingAsync(Stream stream, CancellationToken cancellationToken)
at DemoFile.DemoParser.ReadAllAsync(Stream stream, CancellationToken cancellationToken)
No response
http://replay189.valve.net/730/003686249481730261055_0831476125.dem.bz2
I'm attempting to load a demo file depicting a scenario where a player is initially disconnected. Prior to the start of the first round, the previously missing player eventually connects.
However, when attempting to display the names of the spawning players, the player who was initially absent does not seem to be included in the recorded events of the game demo.
Example code used:
demo.Source1GameEvents.PlayerSpawn += e =>
{
if (e.Player != null)
{
Console.WriteLine($"Player spawn: {e.Player.SteamID} | {e.Player.PlayerName}");
}
};
demo.Source1GameEvents.RoundEnd += e =>
{
Console.Write("\n");
};
The expected output from the previous code should be of a list containing 9 players for each round played. This list should reflect the absence of the player who was initially disconnected.
I'm in WinForms using version NET version .NET 7.0
.
I'm leaving a link to the file used here.
EDIT:
Player death events don't seem to be working for the initially disconnected player either:
demo.Source1GameEvents.PlayerDeath += e =>
{
if (e.Player != null)
{
Console.WriteLine($"Player death: {e.Player.SteamID} | {e.Player.PlayerName}");
}
};
Maybe all events are not working for this player?
existing dota2 demo parsing library is java
does not apply well to c# development
The new update broke demos again.
Here are some of the parsing errors:
Exception: Exception of type 'System.ArgumentOutOfRangeException' was thrown. (Parameter 'msgType')
Actual value was DemMax.
DemoFile.InvalidDemoException: Unexpected demo command: 81
public static class Program
{
//public static string flashNoWork = "C:\\Users\\xguys\\Desktop\\Flash issues\\crystal_vulture.dem";
public static string flashWork = "C:\\Users\\xguys\\Desktop\\demoinfo\\661577d64c197423489bdd0f_0_1714410167560.dem";
public static async Task Main(string[] args)
{
try
{
Stopwatch stopwatch3 = new Stopwatch();
stopwatch3.Start();
MemoryStream _fileStream;
byte[] _demoBytes;
//_demoBytes = File.ReadAllBytes(demoFilePath);
_demoBytes = await File.ReadAllBytesAsync(flashWork);
_fileStream = new MemoryStream(_demoBytes);
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var demo = new DemoParser();
demo.Source1GameEvents.BombExploded += e =>
{
Console.WriteLine("Bomb exploded");
};
demo.Source1GameEvents.PlayerBlind += e =>
{
Console.WriteLine("Player blind");
};
demo.Source1GameEvents.TrPlayerFlashbanged += e =>
{
Console.WriteLine("Player flashbanged");
};
//demo.Source1GameEvents.FlashbangDetonate += e =>
//{
// Console.WriteLine("Flashbang detonate");
//};
demo.Source1GameEvents.RoundStart += e =>
{
if (!demo.GameRules.HasMatchStarted)
{
//PowerLigaManager.Log("ROund end called but we have an issue, it seems match was not started yet");
return;
}
Console.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
Debug.WriteLine("Round start called at round ct:" + demo.TeamCounterTerrorist.Score + " and t:" + demo.TeamTerrorist.Score);
};
demo.Source1GameEvents.CsWinPanelMatch += e =>
{
Console.WriteLine("WIn match has ended");
};
await demo.ReadAllAsync(_fileStream);
stopwatch3.Stop();
stopwatch.Stop();
Console.WriteLine("Time elapsed after file data: {0}", stopwatch.Elapsed);
Console.WriteLine("Time elapsed: {0}", stopwatch3.Elapsed);
Console.WriteLine("\nFinished!");
} catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message.ToString());
}
Console.ReadKey();
}
https://drive.google.com/file/d/1xPpl-ZU_IljuPnEW3X-jKrm4-XRhiGk9/view?usp=sharing
Library should be able to parse 1 demo using multiple threads at the same time.
This would bring huge performance improvement, which scales linearly with number of CPUs.
The way I imagined it, is to split demo into multiple parts (eg. based on round-start ticks), then send each part to a different thread to process it.
The prerequisite for this is to have seeking support (#47), where each thread would get a snapshot of demo at the tick where it's starting to process.
I think it would reduce the time to process 1 demo file to as low as 0.3 seconds.
I realy like the library. Great work
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.