Skip to content

Commit adb29b1

Browse files
committed
fix: incomplete stringtables parsing
fix #539 fix #532 fix #525
1 parent 11dd3de commit adb29b1

File tree

4 files changed

+52
-35
lines changed

4 files changed

+52
-35
lines changed

pkg/demoinfocs/demoinfocs_test.go

Lines changed: 4 additions & 0 deletions
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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ type GrenadeEvent struct {
196196
GrenadeType common.EquipmentType
197197
Grenade *common.Equipment // Maybe nil for InfernoStart & InfernoExpired since we don't know the thrower (at least in old demos)
198198
Position r3.Vector
199-
Thrower *common.Player // May be nil if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
199+
Thrower *common.Player // May be nil with POV demos or if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
200200
GrenadeEntityID int
201201
}
202202

@@ -301,7 +301,7 @@ const (
301301
// BombEvent contains the common attributes of bomb events. Dont register
302302
// handlers on this tho, you want BombEventIf for that.
303303
type BombEvent struct {
304-
Player *common.Player
304+
Player *common.Player // Can be nil with POV demos
305305
Site Bombsite
306306
}
307307

@@ -414,7 +414,7 @@ const (
414414

415415
// PlayerHurt signals that a player has been damaged.
416416
type PlayerHurt struct {
417-
Player *common.Player // May be nil if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
417+
Player *common.Player // May be nil with POV demos or if the demo is partially corrupt (player is 'unconnected', see #156 and #172).
418418
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).
419419
Health int
420420
Armor int
@@ -583,6 +583,7 @@ const (
583583

584584
WarnTypeUnknownEquipmentIndex
585585
WarnTypeMissingItemDefinitionIndex
586+
WarnTypeStringTableParsingFailure // Should happen only with CS2 POV demos
586587
)
587588

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

pkg/demoinfocs/game_events.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,13 +493,12 @@ func (geh gameEventHandler) playerHurt(data map[string]*msg.CSVCMsg_GameEventKey
493493
armorDamageTaken = 100
494494
}
495495

496-
if player != nil && (!geh.parser.isSource2() || (player.PlayerPawnEntity() != nil)) {
497-
// m_iHealth & m_ArmorValue check for CS2 POV demos
498-
if health == 0 && (!geh.parser.isSource2() || player.PlayerPawnEntity().Property("m_iHealth") != nil) {
496+
if player != nil {
497+
if health == 0 {
499498
healthDamageTaken = player.Health()
500499
}
501500

502-
if armor == 0 && (!geh.parser.isSource2() || player.PlayerPawnEntity().Property("m_ArmorValue") != nil) {
501+
if armor == 0 {
503502
armorDamageTaken = player.Armor()
504503
}
505504
}

pkg/demoinfocs/stringtables.go

Lines changed: 41 additions & 28 deletions
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)