Skip to content

Commit 5f3b9e7

Browse files
committed
CS2 POV demo support
1 parent 28f108a commit 5f3b9e7

File tree

7 files changed

+85
-45
lines changed

7 files changed

+85
-45
lines changed

pkg/demoinfocs/datatables.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,18 @@ func (p *parser) nadeProjectileDestroyed(proj *common.GrenadeProjectile) {
10031003

10041004
func (p *parser) bindWeaponS2(entity st.Entity) {
10051005
entityID := entity.ID()
1006-
itemIndex := entity.PropertyValueMust("m_iItemDefinitionIndex").S2UInt64()
1006+
itemIndexVal := entity.PropertyValueMust("m_iItemDefinitionIndex")
1007+
1008+
if itemIndexVal.Any == nil {
1009+
p.eventDispatcher.Dispatch(events.ParserWarn{
1010+
Type: events.WarnTypeMissingItemDefinitionIndex,
1011+
Message: "missing m_iItemDefinitionIndex property in weapon entity",
1012+
})
1013+
1014+
return
1015+
}
1016+
1017+
itemIndex := itemIndexVal.S2UInt64()
10071018
wepType := common.EquipmentIndexMapping[itemIndex]
10081019

10091020
if wepType == common.EqUnknown {

pkg/demoinfocs/demoinfocs_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
retakeDemPath = csDemosPath + "/retake_unknwon_bombsite_index.dem"
3939
unexpectedEndOfDemoPath = csDemosPath + "/unexpected_end_of_demo.dem"
4040
s2DemPath = demSetPathS2 + "/s2.dem"
41+
s2POVDemPath = demSetPathS2 + "/pov.dem"
4142
)
4243

4344
var concurrentDemos = flag.Int("concurrentdemos", 2, "The `number` of current demos")
@@ -231,6 +232,26 @@ func TestS2(t *testing.T) {
231232
assertions.NoError(err, "error occurred in ParseToEnd()")
232233
}
233234

235+
func TestS2POV(t *testing.T) {
236+
t.Parallel()
237+
238+
if testing.Short() {
239+
t.Skip("skipping test due to -short flag")
240+
}
241+
242+
f, err := os.Open(s2POVDemPath)
243+
assertions := assert.New(t)
244+
assertions.NoError(err, "error opening demo %q", s2POVDemPath)
245+
246+
defer mustClose(t, f)
247+
248+
p := demoinfocs.NewParser(f)
249+
250+
t.Log("Parsing to end")
251+
err = p.ParseToEnd()
252+
assertions.NoError(err, "error occurred in ParseToEnd()")
253+
}
254+
234255
func TestEncryptedNetMessages(t *testing.T) {
235256
t.Parallel()
236257

@@ -524,6 +545,10 @@ func testDemoSet(t *testing.T, path string) {
524545
t.Log("expected known issue with team swaps occurred:", warn.Message)
525546
return
526547

548+
case events.WarnTypeMissingItemDefinitionIndex:
549+
t.Log("expected known issue with missing item definition index occurred:", warn.Message)
550+
return
551+
527552
case events.WarnTypeGameEventBeforeDescriptors:
528553
if strings.Contains(name, "POV-orbit-skytten-vs-cloud9-gfinity15sm1-nuke.dem") {
529554
t.Log("expected known issue for POV demos occurred:", warn.Message)

pkg/demoinfocs/events/events.go

+1
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ const (
581581
WarnTypeCantReadEncryptedNetMessage
582582

583583
WarnTypeUnknownEquipmentIndex
584+
WarnTypeMissingItemDefinitionIndex
584585
)
585586

586587
// ParserWarn signals that a non-fatal problem occurred during parsing.

pkg/demoinfocs/game_events.go

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ func newGameEventHandler(parser *parser, ignoreBombsiteIndexNotFound bool) gameE
240240
"item_remove": geh.itemRemove, // Dropped?
241241
"jointeam_failed": nil, // Dunno, only in locally recorded (POV) demos
242242
"other_death": geh.otherDeath, // Other deaths, like chickens.
243+
"player_activate": nil, // CS2 POV demos
243244
"player_blind": delay(geh.playerBlind), // Player got blinded by a flash. Delayed because Player.FlashDuration hasn't been updated yet
244245
"player_changename": nil, // Name change
245246
"player_connect": geh.playerConnect, // Bot connected or player reconnected, players normally come in via string tables & data tables

pkg/demoinfocs/sendtables2/entity.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -519,16 +519,16 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error {
519519
_panicf("unable to find new class %d", classID)
520520
}
521521

522-
baseline := p.classBaselines[classID]
523-
if baseline == nil {
524-
_panicf("unable to find new baseline %d", classID)
525-
}
526-
527522
e = newEntity(index, serial, class)
528523
p.entities[index] = e
529524

530-
e.readFields(newReader(baseline), &paths)
531-
paths = paths[:0]
525+
baseline := p.classBaselines[classID]
526+
527+
if baseline != nil {
528+
// POV demos are missing some baselines?
529+
e.readFields(newReader(baseline), &paths)
530+
paths = paths[:0]
531+
}
532532

533533
e.readFields(r, &paths)
534534
paths = paths[:0]

pkg/demoinfocs/stringtables.go

+38-36
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,10 @@ func parseStringTable(
303303
flags int32,
304304
variantBitCount bool) (items []*stringTableItem) {
305305
items = make([]*stringTableItem, 0)
306+
// Some tables have no data
307+
if len(buf) == 0 {
308+
return items
309+
}
306310

307311
// Create a reader for the buffer
308312
r := bit.NewSmallBitReader(bytes.NewReader(buf))
@@ -312,14 +316,9 @@ func parseStringTable(
312316
index := int32(-1)
313317
keys := make([]string, 0, stringtableKeyHistorySize+1)
314318

315-
// Some tables have no data
316-
if len(buf) == 0 {
317-
return items
318-
}
319-
320319
// Loop through entries in the data structure
321320
//
322-
// Each entry is a tuple consisting of {index, key, value}
321+
// Each entry is a tuple consisting of {index, missing m_iItemDefinitionIndex property key, value}
323322
//
324323
// Index can either be incremented from the previous position or
325324
// overwritten with a given entry.
@@ -373,46 +372,49 @@ func parseStringTable(
373372
if len(keys) > stringtableKeyHistorySize {
374373
keys = keys[1:]
375374
}
376-
}
377375

378-
// Some entries have a value.
379-
hasValue := r.ReadBit()
380-
if hasValue {
381-
bitSize := uint(0)
382-
isCompressed := false
383-
if userDataFixed {
384-
bitSize = uint(userDataSize)
385-
} else {
386-
if (flags & 0x1) != 0 {
387-
isCompressed = r.ReadBit()
388-
}
376+
// Some entries have a value.
377+
hasValue := r.ReadBit()
378+
if hasValue {
379+
bitSize := uint(0)
380+
isCompressed := false
389381

390-
if variantBitCount {
391-
bitSize = r.ReadUBitInt() * 8
382+
if userDataFixed {
383+
bitSize = uint(userDataSize)
392384
} else {
393-
bitSize = r.ReadInt(17) * 8
385+
if (flags & 0x1) != 0 {
386+
isCompressed = r.ReadBit()
387+
}
388+
389+
if variantBitCount {
390+
bitSize = r.ReadUBitInt() * 8
391+
} else {
392+
bitSize = r.ReadInt(17) * 8
393+
}
394394
}
395-
}
396-
value = r.ReadBits(int(bitSize))
397395

398-
if isCompressed {
399-
tmp, err := snappy.Decode(nil, value)
400-
if err != nil {
401-
panic(fmt.Sprintf("unable to decode snappy compressed stringtable item (%s, %d, %s): %s", name, index, key, err))
396+
value = r.ReadBits(int(bitSize))
397+
398+
if isCompressed {
399+
tmp, err := snappy.Decode(nil, value)
400+
if err != nil {
401+
panic(fmt.Sprintf("unable to decode snappy compressed stringtable item (%s, %d, %s): %s", name, index, key, err))
402+
}
403+
404+
value = tmp
402405
}
403-
value = tmp
404406
}
405-
}
406407

407-
items = append(items, &stringTableItem{index, key, value})
408+
items = append(items, &stringTableItem{index, key, value})
409+
}
408410
}
409411

410412
return items
411413
}
412414

413415
var instanceBaselineKeyRegex = regexp.MustCompile(`^\d+:\d+$`)
414416

415-
func (p *parser) processStringTableS2(tab createStringTable, br *bit.BitReader) {
417+
func (p *parser) processStringTableS2(tab createStringTable) {
416418
items := parseStringTable(tab.StringData, tab.GetNumEntries(), tab.GetName(), tab.GetUserDataFixedSize(), tab.GetUserDataSize(), tab.GetFlags(), tab.GetUsingVarintBitcounts())
417419

418420
for _, item := range items {
@@ -452,23 +454,23 @@ func (p *parser) processStringTable(tab createStringTable) {
452454
tab.StringData = b
453455
}
454456

455-
br := bit.NewSmallBitReader(bytes.NewReader(tab.StringData))
456-
457457
if tab.isS2 {
458-
p.processStringTableS2(tab, br)
458+
p.processStringTableS2(tab)
459459
} else {
460+
br := bit.NewSmallBitReader(bytes.NewReader(tab.StringData))
461+
460462
if br.ReadBit() {
461463
panic("unknown stringtable format")
462464
}
463465

464466
p.processStringTableS1(tab, br)
467+
468+
p.poolBitReader(br)
465469
}
466470

467471
if tab.GetName() == stNameModelPreCache {
468472
p.processModelPreCacheUpdate()
469473
}
470-
471-
p.poolBitReader(br)
472474
}
473475

474476
func parsePlayerInfo(reader io.Reader) common.PlayerInfo {

test/cs-demos

Submodule cs-demos updated from d07eb4c to 0b0f78a

0 commit comments

Comments
 (0)