Skip to content

Commit b12aa13

Browse files
authored
Merge branch 'master' into master
2 parents 414eb7a + 3cd3eb4 commit b12aa13

File tree

9 files changed

+102
-50
lines changed

9 files changed

+102
-50
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
A blazing fast, feature complete and production ready Go library for parsing and analysing of Counter-Strike 2 and Counter-Strike: Global Offensive (CS:GO) demos (aka replays).
44

5-
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine)
6-
75
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs?tab=doc)
86
[![Build Status](https://img.shields.io/github/actions/workflow/status/markus-wa/demoinfocs-golang/ci.yml?branch=master&style=flat-square)](https://github.com/markus-wa/demoinfocs-golang/actions)
97
[![codecov](https://img.shields.io/codecov/c/github/markus-wa/demoinfocs-golang?style=flat-square)](https://codecov.io/gh/markus-wa/demoinfocs-golang)

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/samber/lo v1.38.1
1616
github.com/stretchr/testify v1.8.4
1717
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
18-
google.golang.org/protobuf v1.31.0
18+
google.golang.org/protobuf v1.34.1
1919
)
2020

2121
require (

go.sum

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw
77
github.com/golang/geo v0.0.0-20180826223333-635502111454/go.mod h1:vgWZ7cu0fq0KY3PpEHsocXOWJpRtkcbKemU4IUw0M60=
88
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
99
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
10-
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
1110
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
1211
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
13-
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1412
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
1513
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1614
github.com/llgcode/draw2d v0.0.0-20230723155556-e595d7c7e75e h1:hqFckor7F0B63l6cV/PoAsuQUOmDji/1oVF0+24EMUI=
@@ -84,10 +82,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
8482
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
8583
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
8684
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
87-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
88-
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
89-
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
90-
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
85+
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
86+
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
9187
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9288
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9389
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

pkg/demoinfocs/common/hostage.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package common
33
import (
44
"github.com/golang/geo/r3"
55

6+
"github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/constants"
67
st "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/sendtables"
78
)
89

@@ -54,7 +55,12 @@ func (hostage *Hostage) Health() int {
5455
// Returns nil if the hostage is not following a player.
5556
func (hostage *Hostage) Leader() *Player {
5657
if hostage.demoInfoProvider.IsSource2() {
57-
return hostage.demoInfoProvider.FindPlayerByPawnHandle(getUInt64(hostage.Entity, "m_leader"))
58+
leaderHandle := getUInt64(hostage.Entity, "m_leader")
59+
if leaderHandle != constants.InvalidEntityHandleSource2 {
60+
return hostage.demoInfoProvider.FindPlayerByPawnHandle(leaderHandle)
61+
}
62+
63+
return hostage.demoInfoProvider.FindPlayerByPawnHandle(getUInt64(hostage.Entity, "m_hHostageGrabber"))
5864
}
5965

6066
return hostage.demoInfoProvider.FindPlayerByHandle(uint64(getInt(hostage.Entity, "m_leader")))

pkg/demoinfocs/common/hostage_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/stretchr/testify/assert"
77

8+
"github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/constants"
89
st "github.com/markus-wa/demoinfocs-golang/v4/pkg/demoinfocs/sendtables"
910
)
1011

@@ -19,6 +20,29 @@ func TestHostage_Leader(t *testing.T) {
1920
assert.Equal(t, player, hostage.Leader())
2021
}
2122

23+
func TestHostage_LeaderWithInvalidHandleS2(t *testing.T) {
24+
player := new(Player)
25+
player.EntityID = 10
26+
provider := demoInfoProviderMock{
27+
playersByHandle: map[uint64]*Player{10: player},
28+
isSource2: true,
29+
}
30+
hostage := hostageWithProperties([]fakeProp{
31+
{
32+
propName: "m_leader",
33+
value: st.PropertyValue{Any: uint64(constants.InvalidEntityHandleSource2)},
34+
isNil: false,
35+
},
36+
{
37+
propName: "m_hHostageGrabber",
38+
value: st.PropertyValue{Any: uint64(10)},
39+
isNil: false,
40+
},
41+
}, provider)
42+
43+
assert.Equal(t, player, hostage.Leader())
44+
}
45+
2246
func TestHostage_State(t *testing.T) {
2347
hostage := hostageWithProperty("m_nHostageState", st.PropertyValue{IntVal: int(HostageStateFollowingPlayer)}, demoInfoProviderMock{})
2448

@@ -34,3 +58,7 @@ func TestHostage_Health(t *testing.T) {
3458
func hostageWithProperty(propName string, value st.PropertyValue, provider demoInfoProviderMock) *Hostage {
3559
return &Hostage{Entity: entityWithProperty(propName, value), demoInfoProvider: provider}
3660
}
61+
62+
func hostageWithProperties(properties []fakeProp, provider demoInfoProviderMock) *Hostage {
63+
return &Hostage{Entity: entityWithProperties(properties), demoInfoProvider: provider}
64+
}

pkg/demoinfocs/demoinfocs_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,10 @@ func testDemoSet(t *testing.T, path string) {
549549
t.Log("expected known issue with missing item definition index occurred:", warn.Message)
550550
return
551551

552+
case events.WarnTypeStringTableParsingFailure:
553+
t.Log("expected known issue with stringtables parsing occurred:", warn.Message)
554+
return
555+
552556
case events.WarnTypeGameEventBeforeDescriptors:
553557
if strings.Contains(name, "POV-orbit-skytten-vs-cloud9-gfinity15sm1-nuke.dem") {
554558
t.Log("expected known issue for POV demos occurred:", warn.Message)

pkg/demoinfocs/events/events.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ type GrenadeEvent struct {
203203
GrenadeType common.EquipmentType
204204
Grenade *common.Equipment // Maybe nil for InfernoStart & InfernoExpired since we don't know the thrower (at least in old demos)
205205
Position r3.Vector
206-
Thrower *common.Player // May be nil if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
206+
Thrower *common.Player // May be nil with POV demos or if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
207207
GrenadeEntityID int
208208
}
209209

@@ -308,7 +308,7 @@ const (
308308
// BombEvent contains the common attributes of bomb events. Dont register
309309
// handlers on this tho, you want BombEventIf for that.
310310
type BombEvent struct {
311-
Player *common.Player
311+
Player *common.Player // Can be nil with POV demos
312312
Site Bombsite
313313
}
314314

@@ -421,7 +421,7 @@ const (
421421

422422
// PlayerHurt signals that a player has been damaged.
423423
type PlayerHurt struct {
424-
Player *common.Player // May be nil if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
424+
Player *common.Player // May be nil with POV demos or if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
425425
Attacker *common.Player // May be nil if the player is taking world damage (e.g. fall damage) or if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
426426
Health int
427427
Armor int
@@ -590,6 +590,7 @@ const (
590590

591591
WarnTypeUnknownEquipmentIndex
592592
WarnTypeMissingItemDefinitionIndex
593+
WarnTypeStringTableParsingFailure // Should happen only with CS2 POV demos
593594
)
594595

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

pkg/demoinfocs/game_events.go

+15-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package demoinfocs
22

33
import (
44
"fmt"
5+
"math"
56
"time"
67

78
"github.com/golang/geo/r3"
@@ -501,13 +502,12 @@ func (geh gameEventHandler) playerHurt(data map[string]*msg.CSVCMsg_GameEventKey
501502
armorDamageTaken = 100
502503
}
503504

504-
if player != nil && (!geh.parser.isSource2() || (player.PlayerPawnEntity() != nil)) {
505-
// m_iHealth & m_ArmorValue check for CS2 POV demos
506-
if health == 0 && (!geh.parser.isSource2() || player.PlayerPawnEntity().Property("m_iHealth") != nil) {
505+
if player != nil {
506+
if health == 0 {
507507
healthDamageTaken = player.Health()
508508
}
509509

510-
if armor == 0 && (!geh.parser.isSource2() || player.PlayerPawnEntity().Property("m_ArmorValue") != nil) {
510+
if armor == 0 {
511511
armorDamageTaken = player.Armor()
512512
}
513513
}
@@ -650,10 +650,6 @@ func (geh gameEventHandler) HostageRescuedAll(map[string]*msg.CSVCMsg_GameEventK
650650
}
651651

652652
func (geh gameEventHandler) playerConnect(data map[string]*msg.CSVCMsg_GameEventKeyT) {
653-
if geh.parser.isSource2() {
654-
return
655-
}
656-
657653
pl := common.PlayerInfo{
658654
UserID: int(data["userid"].GetValShort()),
659655
Name: data["name"].GetValString(),
@@ -671,7 +667,17 @@ func (geh gameEventHandler) playerConnect(data map[string]*msg.CSVCMsg_GameEvent
671667
}
672668
}
673669

674-
geh.parser.setRawPlayer(int(data["index"].GetValByte()), pl)
670+
var playerIndex int
671+
if geh.parser.isSource2() {
672+
playerIndex = pl.UserID
673+
if !pl.IsFakePlayer && !pl.IsHltv && pl.XUID > 0 && pl.UserID <= math.MaxUint8 {
674+
pl.UserID |= math.MaxUint8 << 8
675+
}
676+
} else {
677+
playerIndex = int(data["index"].GetValByte())
678+
}
679+
680+
geh.parser.setRawPlayer(playerIndex, pl)
675681
}
676682

677683
func (geh gameEventHandler) playerDisconnect(data map[string]*msg.CSVCMsg_GameEventKeyT) {

pkg/demoinfocs/stringtables.go

+41-28
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,9 @@ const (
294294
)
295295

296296
// Parse a string table data blob, returning a list of item updates.
297-
func parseStringTable(
297+
//
298+
//nolint:funlen,gocognit
299+
func (p *parser) parseStringTable(
298300
buf []byte,
299301
numUpdates int32,
300302
name string,
@@ -308,6 +310,16 @@ func parseStringTable(
308310
return items
309311
}
310312

313+
defer func() {
314+
err := recover()
315+
if err != nil {
316+
p.eventDispatcher.Dispatch(events.ParserWarn{
317+
Type: events.WarnTypeStringTableParsingFailure,
318+
Message: "failed to parse stringtable properly",
319+
})
320+
}
321+
}()
322+
311323
// Create a reader for the buffer
312324
r := bit.NewSmallBitReader(bytes.NewReader(buf))
313325

@@ -372,41 +384,42 @@ func parseStringTable(
372384
if len(keys) > stringtableKeyHistorySize {
373385
keys = keys[1:]
374386
}
387+
}
375388

376-
// Some entries have a value.
377-
hasValue := r.ReadBit()
378-
if hasValue {
379-
bitSize := uint(0)
380-
isCompressed := false
389+
// Some entries have a value.
390+
hasValue := r.ReadBit()
391+
//nolint:nestif
392+
if hasValue {
393+
bitSize := uint(0)
394+
isCompressed := false
381395

382-
if userDataFixed {
383-
bitSize = uint(userDataSize)
384-
} else {
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-
}
396+
if userDataFixed {
397+
bitSize = uint(userDataSize)
398+
} else {
399+
if (flags & 0x1) != 0 {
400+
isCompressed = r.ReadBit()
394401
}
395402

396-
value = r.ReadBits(int(bitSize))
403+
if variantBitCount {
404+
bitSize = r.ReadUBitInt() * 8
405+
} else {
406+
bitSize = r.ReadInt(17) * 8
407+
}
408+
}
397409

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-
}
410+
value = r.ReadBits(int(bitSize))
403411

404-
value = tmp
412+
if isCompressed {
413+
tmp, err := snappy.Decode(nil, value)
414+
if err != nil {
415+
panic(fmt.Sprintf("unable to decode snappy compressed stringtable item (%s, %d, %s): %s", name, index, key, err))
405416
}
406-
}
407417

408-
items = append(items, &stringTableItem{index, key, value})
418+
value = tmp
419+
}
409420
}
421+
422+
items = append(items, &stringTableItem{index, key, value})
410423
}
411424

412425
return items
@@ -415,7 +428,7 @@ func parseStringTable(
415428
var instanceBaselineKeyRegex = regexp.MustCompile(`^\d+:\d+$`)
416429

417430
func (p *parser) processStringTableS2(tab createStringTable) {
418-
items := parseStringTable(tab.StringData, tab.GetNumEntries(), tab.GetName(), tab.GetUserDataFixedSize(), tab.GetUserDataSize(), tab.GetFlags(), tab.GetUsingVarintBitcounts())
431+
items := p.parseStringTable(tab.StringData, tab.GetNumEntries(), tab.GetName(), tab.GetUserDataFixedSize(), tab.GetUserDataSize(), tab.GetFlags(), tab.GetUsingVarintBitcounts())
419432

420433
for _, item := range items {
421434
switch tab.GetName() {

0 commit comments

Comments
 (0)