Skip to content

Commit

Permalink
WIP: parse Source 2 protos
Browse files Browse the repository at this point in the history
  • Loading branch information
markus-wa committed Mar 23, 2023
1 parent 1c11b45 commit 30cc604
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 16 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module github.com/markus-wa/demoinfocs-golang/v3

require (
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551
github.com/golang/snappy v0.0.4
github.com/llgcode/draw2d v0.0.0-20210904075650-80aa0a2a901d
github.com/markus-wa/go-heatmap/v2 v2.0.0
github.com/markus-wa/go-unassert v0.1.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/golang/geo v0.0.0-20180826223333-635502111454/go.mod h1:vgWZ7cu0fq0KY
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
Expand Down
25 changes: 25 additions & 0 deletions pkg/demoinfocs/demoinfocs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
csDemosPath = testDataPath + "/cs-demos"
demSetPath = csDemosPath + "/set"
defaultDemPath = csDemosPath + "/default.dem"
s2DemPath = csDemosPath + "/s2-gotv.dem"
retakeDemPath = csDemosPath + "/retake_unknwon_bombsite_index.dem"
unexpectedEndOfDemoPath = csDemosPath + "/unexpected_end_of_demo.dem"
)
Expand Down Expand Up @@ -194,6 +195,30 @@ func TestDemoInfoCs(t *testing.T) {
assertGolden(t, assertions, "default", actual.Bytes())
}

func TestS2(t *testing.T) {
t.Parallel()

if testing.Short() {
t.Skip("skipping test due to -short flag")
}

f, err := os.Open(s2DemPath)
assertions := assert.New(t)
assertions.NoError(err, "error opening demo %q", s2DemPath)

defer mustClose(t, f)

p := demoinfocs.NewParser(f)

t.Log("Parsing header")
_, err = p.ParseHeader()
assertions.NoError(err, "error returned by Parser.ParseHeader()")

t.Log("Parsing to end")
err = p.ParseToEnd()
assertions.NoError(err, "error occurred in ParseToEnd()")
}

func TestEncryptedNetMessages(t *testing.T) {
t.Parallel()

Expand Down
116 changes: 100 additions & 16 deletions pkg/demoinfocs/parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import (
"sync"
"time"

"github.com/golang/snappy"
"github.com/markus-wa/go-unassert"
dispatch "github.com/markus-wa/godispatch"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"

common "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/common"
events "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/events"
msg "github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/msg"
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/msgs2"

"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/common"
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/events"
"github.com/markus-wa/demoinfocs-golang/v3/pkg/demoinfocs/msg"
)

const maxOsPath = 260
Expand Down Expand Up @@ -45,18 +48,24 @@ var (
func (p *parser) ParseHeader() (common.DemoHeader, error) {
var h common.DemoHeader
h.Filestamp = p.bitReader.ReadCString(8)
h.Protocol = p.bitReader.ReadSignedInt(32)
h.NetworkProtocol = p.bitReader.ReadSignedInt(32)
h.ServerName = p.bitReader.ReadCString(maxOsPath)
h.ClientName = p.bitReader.ReadCString(maxOsPath)
h.MapName = p.bitReader.ReadCString(maxOsPath)
h.GameDirectory = p.bitReader.ReadCString(maxOsPath)
h.PlaybackTime = time.Duration(p.bitReader.ReadFloat() * float32(time.Second))
h.PlaybackTicks = p.bitReader.ReadSignedInt(32)
h.PlaybackFrames = p.bitReader.ReadSignedInt(32)
h.SignonLength = p.bitReader.ReadSignedInt(32)

if h.Filestamp != "HL2DEMO" {

switch h.Filestamp {
case "HL2DEMO":
h.Protocol = p.bitReader.ReadSignedInt(32)
h.NetworkProtocol = p.bitReader.ReadSignedInt(32)
h.ServerName = p.bitReader.ReadCString(maxOsPath)
h.ClientName = p.bitReader.ReadCString(maxOsPath)
h.MapName = p.bitReader.ReadCString(maxOsPath)
h.GameDirectory = p.bitReader.ReadCString(maxOsPath)
h.PlaybackTime = time.Duration(p.bitReader.ReadFloat() * float32(time.Second))
h.PlaybackTicks = p.bitReader.ReadSignedInt(32)
h.PlaybackFrames = p.bitReader.ReadSignedInt(32)
h.SignonLength = p.bitReader.ReadSignedInt(32)

case "PBDEMS2":
p.bitReader.Skip(8 << 3) // skip 8 bytes

default:
return h, ErrInvalidFileType
}

Expand Down Expand Up @@ -206,7 +215,7 @@ const (
)

//nolint:funlen,cyclop
func (p *parser) parseFrame() bool {
func (p *parser) parseFrameS1() bool {
cmd := demoCommand(p.bitReader.ReadSingleByte())

// Send ingame tick number update
Expand Down Expand Up @@ -272,6 +281,81 @@ func (p *parser) parseFrame() bool {
return true
}

var demoCommandMsgs = map[msgs2.EDemoCommands]proto.Message{
msgs2.EDemoCommands_DEM_Stop: &msgs2.CDemoStop{},
msgs2.EDemoCommands_DEM_FileHeader: &msgs2.CDemoFileHeader{},
msgs2.EDemoCommands_DEM_FileInfo: &msgs2.CDemoFileInfo{},
msgs2.EDemoCommands_DEM_SyncTick: &msgs2.CDemoSyncTick{},
msgs2.EDemoCommands_DEM_SendTables: &msgs2.CDemoSendTables{},
msgs2.EDemoCommands_DEM_ClassInfo: &msgs2.CDemoClassInfo{},
msgs2.EDemoCommands_DEM_StringTables: &msgs2.CDemoStringTables{},
msgs2.EDemoCommands_DEM_Packet: &msgs2.CDemoPacket{},
msgs2.EDemoCommands_DEM_SignonPacket: &msgs2.CDemoPacket{},
msgs2.EDemoCommands_DEM_ConsoleCmd: &msgs2.CDemoConsoleCmd{},
msgs2.EDemoCommands_DEM_CustomData: &msgs2.CDemoCustomData{},
msgs2.EDemoCommands_DEM_UserCmd: &msgs2.CDemoUserCmd{},
msgs2.EDemoCommands_DEM_FullPacket: &msgs2.CDemoFullPacket{},
msgs2.EDemoCommands_DEM_SaveGame: &msgs2.CDemoSaveGame{},
msgs2.EDemoCommands_DEM_SpawnGroups: &msgs2.CDemoSpawnGroups{}}

func (p *parser) parseFrameS2() bool {
cmd := msgs2.EDemoCommands(p.bitReader.ReadVarInt32())

msgType := cmd & ^msgs2.EDemoCommands_DEM_IsCompressed
msgCompressed := (cmd & msgs2.EDemoCommands_DEM_IsCompressed) != 0

tick := p.bitReader.ReadVarInt32()

// This appears to actually be an int32, where a -1 means pre-game.
if tick == 4294967295 {
tick = 0
}

size := p.bitReader.ReadVarInt32()

buf := p.bitReader.ReadBytes(int(size))

if msgCompressed {
var err error

buf, err = snappy.Decode(nil, buf)
if err != nil {
panic(err) // FIXME: avoid panic
}
}

msg := demoCommandMsgs[msgType]

if msg == nil {
panic(fmt.Sprintf("Unknown demo command: %d", msgType))
}

err := proto.Unmarshal(buf, msg)
if err != nil {
panic(err) // FIXME: avoid panic
}

p.msgQueue <- msg

p.msgQueue <- ingameTickNumber(int32(tick))

return true
}

// FIXME: refactor to interface instead of switch
func (p *parser) parseFrame() bool {
switch p.header.Filestamp {
case "HL2DEMO":
return p.parseFrameS1()

case "PBDEMS2":
return p.parseFrameS2()

default:
panic(fmt.Sprintf("Unknown demo version: %s", p.header.Filestamp))
}
}

var byteSlicePool = sync.Pool{
New: func() any {
s := make([]byte, 0, 256)
Expand Down

0 comments on commit 30cc604

Please sign in to comment.