diff --git a/datagram.go b/datagram.go new file mode 100644 index 0000000..2342616 --- /dev/null +++ b/datagram.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + + "github.com/golang/protobuf/proto" + "github.com/golang/snappy" + "github.com/jordanorelli/hyperstone/bit" + "github.com/jordanorelli/hyperstone/dota" +) + +// datagram represents the top-level envelope in the dota replay format. All +// data in the replay file is packed into datagram frames of at most 65kb. +type dataGram struct { + cmd dota.EDemoCommands + tick int64 + compressed bool + body []byte +} + +func (g dataGram) String() string { + if len(g.body) > 30 { + return fmt.Sprintf("{dataGram cmd: %v tick: %v compressed: %t size: %d body: %q...}", g.cmd, g.tick, g.compressed, len(g.body), g.body[:27]) + } + return fmt.Sprintf("{dataGram cmd: %v tick: %v compressed: %t size: %d body: %q}", g.cmd, g.tick, g.compressed, len(g.body), g.body) +} + +func (g *dataGram) check(dump bool) error { + if g.cmd != dota.EDemoCommands_DEM_Packet { + return fmt.Errorf("wrong command type in openPacket: %v", g.cmd) + } + + if g.compressed { + buf, err := snappy.Decode(nil, g.body) + if err != nil { + return wrap(err, "open packet error: could not decode body") + } + g.body = buf + g.compressed = false + } + + packet := new(dota.CDemoPacket) + if err := proto.Unmarshal(g.body, packet); err != nil { + return wrap(err, "onPacket unable to unmarshal message body") + } + + if dump { + br := bit.NewBytesReader(packet.GetData()) + for { + t := br.ReadUBitVar() + s := br.ReadVarInt() + b := make([]byte, s) + br.Read(b) + if br.Err() != nil { + break + } + fmt.Printf("\t%v\n", entity{t: uint32(t), size: uint32(s), body: b}) + e := entFactory.BuildMessage(int(t)) + if e == nil { + fmt.Printf("\tno known entity for type id %d\n", int(t)) + continue + } + err := proto.Unmarshal(b, e) + if err != nil { + fmt.Printf("entity unmarshal error: %v\n", err) + } else { + fmt.Printf("\t\t%v\n", e) + } + } + } + return nil +} diff --git a/entity.go b/entity.go new file mode 100644 index 0000000..fb76cf1 --- /dev/null +++ b/entity.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" +) + +type entity struct { + t uint32 + size uint32 + body []byte +} + +func (e entity) String() string { + if len(e.body) < 32 { + return fmt.Sprintf("{entity type: %d size: %d data: %x}", e.t, e.size, e.body) + } + return fmt.Sprintf("{entity type: %d size: %d data: %x...}", e.t, e.size, e.body[:32]) +} diff --git a/message.go b/message.go deleted file mode 100644 index 7c17211..0000000 --- a/message.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/golang/protobuf/proto" - "github.com/golang/snappy" - "github.com/jordanorelli/hyperstone/bit" - "github.com/jordanorelli/hyperstone/dota" -) - -// message represents the top-level envelope in the dota replay format. Each -// datum is contained within a message envelope. Each message envelope is at -// most 1<<16 bytes. -type message struct { - cmd dota.EDemoCommands - tick int64 - compressed bool - body []byte -} - -func (m message) String() string { - if len(m.body) > 30 { - return fmt.Sprintf("{cmd: %v tick: %v compressed: %t size: %d body: %q...}", m.cmd, m.tick, m.compressed, len(m.body), m.body[:27]) - } - return fmt.Sprintf("{cmd: %v tick: %v compressed: %t size: %d body: %q}", m.cmd, m.tick, m.compressed, len(m.body), m.body) -} - -type entity struct { - t uint32 - size uint32 - body []byte -} - -func (e entity) String() string { - if len(e.body) < 32 { - return fmt.Sprintf("{%v %v %x}", e.t, e.size, e.body) - } - return fmt.Sprintf("{%v %v %x...}", e.t, e.size, e.body[:32]) -} - -func (m *message) check(dump bool) error { - if m.cmd != dota.EDemoCommands_DEM_Packet { - return fmt.Errorf("wrong command type in openPacket: %v", m.cmd) - } - - if m.compressed { - buf, err := snappy.Decode(nil, m.body) - if err != nil { - return wrap(err, "open packet error: could not decode body") - } - m.body = buf - m.compressed = false - } - - packet := new(dota.CDemoPacket) - if err := proto.Unmarshal(m.body, packet); err != nil { - return wrap(err, "onPacket unable to unmarshal message body") - } - - if dump { - br := bit.NewBytesReader(packet.GetData()) - for { - t := br.ReadUBitVar() - s := br.ReadVarInt() - b := make([]byte, s) - br.Read(b) - if br.Err() != nil { - break - } - fmt.Printf("\t%v\n", entity{t: uint32(t), size: uint32(s), body: b}) - e := entFactory.BuildMessage(int(t)) - if e == nil { - fmt.Printf("\tno known entity for type id %d\n", int(t)) - continue - } - err := proto.Unmarshal(b, e) - if err != nil { - fmt.Printf("entity unmarshal error: %v\n", err) - } else { - fmt.Printf("\t\t%v\n", e) - } - } - } - return nil -} diff --git a/parser.go b/parser.go index 4106022..f67c264 100644 --- a/parser.go +++ b/parser.go @@ -133,7 +133,7 @@ func (p *parser) readCommand() (dota.EDemoCommands, bool, error) { return dota.EDemoCommands(n), compressed, nil } -func (p *parser) readMessage() (*message, error) { +func (p *parser) readMessage() (*dataGram, error) { cmd, compressed, err := p.readCommand() if err != nil { return nil, wrap(err, "readMessage couldn't get a command") @@ -155,9 +155,9 @@ func (p *parser) readMessage() (*message, error) { return nil, wrap(err, "readMessage couldn't read message body") } // TODO: pool these! - return &message{cmd, int64(tick), compressed, buf}, nil + return &dataGram{cmd, int64(tick), compressed, buf}, nil } // TODO: pool these! - return &message{cmd, int64(tick), compressed, nil}, nil + return &dataGram{cmd, int64(tick), compressed, nil}, nil }