Skip to content

Commit 5125a5a

Browse files
committed
add ParserConfig.IgnorePacketEntitiesPanic flag for POV demo workaround
1 parent e2337d9 commit 5125a5a

File tree

5 files changed

+61
-28
lines changed

5 files changed

+61
-28
lines changed

pkg/demoinfocs/events/events.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ const (
605605
WarnTypeUnknownEquipmentIndex
606606
WarnTypeMissingItemDefinitionIndex
607607
WarnTypeStringTableParsingFailure // Should happen only with CS2 POV demos
608+
WarnTypePacketEntitiesPanic
608609
)
609610

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

pkg/demoinfocs/parser.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type parser struct {
8484
errLock sync.Mutex // Used to sync up error mutations between parsing & handling go-routines
8585
decryptionKey []byte // Stored in `match730_*.dem.info` see MatchInfoDecryptionKey().
8686
source2FallbackGameEventListBin []byte // sv_hibernate_when_empty bug workaround
87+
ignorePacketEntitiesPanic bool // Used to ignore PacketEntities parsing panics (some POV demos seem to have broken rare broken PacketEntities)
8788
/**
8889
* Set to the client slot of the recording player.
8990
* Always -1 for GOTV demos.
@@ -361,6 +362,10 @@ type ParserConfig struct {
361362
// It's used when the game event list is not found in the demo file.
362363
// This can happen due to a CS2 bug with sv_hibernate_when_empty.
363364
Source2FallbackGameEventListBin []byte
365+
366+
// IgnorePacketEntitiesPanic tells the parser to ignore PacketEntities parsing panics.
367+
// This is required as a workaround for some POV demos that seem to contain rare PacketEntities parsing issues.
368+
IgnorePacketEntitiesPanic bool
364369
}
365370

366371
// DefaultParserConfig is the default Parser configuration used by NewParser().
@@ -399,6 +404,8 @@ func NewParserWithConfig(demostream io.Reader, config ParserConfig) Parser {
399404
p.source2FallbackGameEventListBin = defaultSource2FallbackGameEventListBin
400405
}
401406

407+
p.ignorePacketEntitiesPanic = config.IgnorePacketEntitiesPanic
408+
402409
dispatcherCfg := dp.Config{
403410
PanicHandler: func(v any) {
404411
p.setError(fmt.Errorf("%v\nstacktrace:\n%s", v, debug.Stack()))

pkg/demoinfocs/parsing.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,18 @@ func (p *parser) ParseHeader() (common.DemoHeader, error) {
7171
case "PBDEMS2":
7272
p.bitReader.Skip(8 << 3) // skip 8 bytes
7373

74-
p.stParser = sendtables2.NewParser()
74+
var warnFunc func(error)
75+
76+
if p.ignorePacketEntitiesPanic {
77+
warnFunc = func(err error) {
78+
p.eventDispatcher.Dispatch(events.ParserWarn{
79+
Type: events.WarnTypePacketEntitiesPanic,
80+
Message: fmt.Sprintf("encountered PacketEntities panic: %v", err),
81+
})
82+
}
83+
}
84+
85+
p.stParser = sendtables2.NewParser(warnFunc)
7586

7687
p.stParser.OnEntity(p.onEntity)
7788

pkg/demoinfocs/sendtables2/entity.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sendtables2
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67

78
"github.com/golang/geo/r3"
@@ -252,6 +253,8 @@ func (e *Entity) OnDestroy(delegate func()) {
252253
}
253254

254255
func (e *Entity) Destroy() {
256+
e.active = false
257+
255258
for _, delegate := range e.onDestroy {
256259
delegate()
257260
}
@@ -467,6 +470,17 @@ func (e *Entity) readFields(r *reader, paths *[]*fieldPath) {
467470

468471
// Internal Callback for OnCSVCMsg_PacketEntities.
469472
func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error {
473+
defer func() {
474+
if p.packetEntitiesPanicWarnFunc == nil {
475+
return
476+
}
477+
478+
r := recover()
479+
if r != nil {
480+
fmt.Fprintf(os.Stderr, "error in OnPacketEntities: %v\n", r)
481+
}
482+
}()
483+
470484
r := newReader(m.GetEntityData())
471485

472486
var (
@@ -481,6 +495,7 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error {
481495
if p.entityFullPackets > 0 {
482496
return nil
483497
}
498+
484499
p.entityFullPackets++
485500
}
486501

@@ -496,12 +511,7 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error {
496511
index = next
497512

498513
cmd = r.readBits(2)
499-
if cmd == 0 && m.GetHasPvsVisBits() > 0 {
500-
cmd = r.readBits(2) << 3
501-
if cmd&0x08 == 8 {
502-
continue
503-
}
504-
}
514+
505515
if cmd&0x01 == 0 {
506516
if cmd&0x02 != 0 {
507517
classID = int32(r.readBits(p.classIdSize))
@@ -537,7 +547,12 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error {
537547

538548
op = st.EntityOpCreated | st.EntityOpEntered
539549
} else {
540-
if e = p.entities[index]; e == nil {
550+
if m.GetHasPvsVisBits() > 0 && r.readBits(2)&0x01 != 0 {
551+
continue
552+
}
553+
554+
e = p.entities[index]
555+
if e == nil {
541556
_panicf("unable to find existing entity %d", index)
542557
}
543558

@@ -552,21 +567,18 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error {
552567
} else {
553568
e = p.entities[index]
554569
if e == nil {
555-
continue
556570
_panicf("unable to find existing entity %d", index)
557571
}
558572

559573
if !e.active {
560-
_panicf("entity %d (%s) ordered to leave, already inactive", e.class.classId, e.class.name)
574+
continue // entity has already been destroyed
561575
}
562576

563577
op = st.EntityOpLeft
564578
if cmd&0x02 != 0 {
565579
op |= st.EntityOpDeleted
566580

567581
e.Destroy()
568-
569-
delete(p.entities, index)
570582
}
571583
}
572584

pkg/demoinfocs/sendtables2/parser.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,17 @@ type tuple struct {
5757
}
5858

5959
type Parser struct {
60-
serializers map[string]*serializer
61-
classIdSize uint32
62-
classBaselines map[int32][]byte
63-
classesById map[int32]*class
64-
classesByName map[string]*class
65-
entityFullPackets int
66-
entities map[int32]*Entity
67-
entityHandlers []st.EntityHandler
68-
pathCache []*fieldPath
69-
tuplesCache []tuple
60+
serializers map[string]*serializer
61+
classIdSize uint32
62+
classBaselines map[int32][]byte
63+
classesById map[int32]*class
64+
classesByName map[string]*class
65+
entityFullPackets int
66+
entities map[int32]*Entity
67+
entityHandlers []st.EntityHandler
68+
pathCache []*fieldPath
69+
tuplesCache []tuple
70+
packetEntitiesPanicWarnFunc func(error)
7071
}
7172

7273
func (p *Parser) ReadEnterPVS(r *bit.BitReader, index int, entities map[int]st.Entity, slot int) st.Entity {
@@ -106,13 +107,14 @@ func (p *Parser) ServerClasses() st.ServerClasses {
106107
return (*serverClasses)(p)
107108
}
108109

109-
func NewParser() *Parser {
110+
func NewParser(packetEntitiesPanicWarnFunc func(error)) *Parser {
110111
return &Parser{
111-
serializers: make(map[string]*serializer),
112-
classBaselines: make(map[int32][]byte),
113-
classesById: make(map[int32]*class),
114-
classesByName: make(map[string]*class),
115-
entities: make(map[int32]*Entity),
112+
serializers: make(map[string]*serializer),
113+
classBaselines: make(map[int32][]byte),
114+
classesById: make(map[int32]*class),
115+
classesByName: make(map[string]*class),
116+
entities: make(map[int32]*Entity),
117+
packetEntitiesPanicWarnFunc: packetEntitiesPanicWarnFunc,
116118
}
117119
}
118120

0 commit comments

Comments
 (0)