diff --git a/datagram.go b/datagram.go index 2342616..c94f4f3 100644 --- a/datagram.go +++ b/datagram.go @@ -2,9 +2,9 @@ package main import ( "fmt" + "io" "github.com/golang/protobuf/proto" - "github.com/golang/snappy" "github.com/jordanorelli/hyperstone/bit" "github.com/jordanorelli/hyperstone/dota" ) @@ -12,17 +12,16 @@ import ( // 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 + cmd dota.EDemoCommands + tick int64 + 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 size: %d body: %q...}", g.cmd, g.tick, 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) + return fmt.Sprintf("{dataGram cmd: %v tick: %v size: %d body: %q}", g.cmd, g.tick, len(g.body), g.body) } func (g *dataGram) check(dump bool) error { @@ -30,42 +29,36 @@ func (g *dataGram) check(dump bool) error { 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 - } + br := bit.NewBytesReader(packet.GetData()) + for { + t := br.ReadUBitVar() + s := br.ReadVarInt() + b := make([]byte, s) + br.Read(b) + switch err := br.Err(); err { + case nil: + break + case io.EOF: + return nil + default: + return err + } + if dump { 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) - } + } + 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) } } return nil diff --git a/gen/main.go b/gen/main.go index 106fee8..7fecd61 100644 --- a/gen/main.go +++ b/gen/main.go @@ -40,11 +40,11 @@ var ( "EDotaUserMessages_DOTA_UM_": "CDOTAUserMsg_", } specials = map[string]string{ - "EDemoCommands_DEM_SignonPacket": "CDemoPacket", "EDotaUserMessages_DOTA_UM_StatsHeroDetails": "CDOTAUserMsg_StatsHeroMinuteDetails", "EDotaUserMessages_DOTA_UM_CombatLogDataHLTV": "CMsgDOTACombatLogEntry", "EDotaUserMessages_DOTA_UM_TournamentDrop": "CMsgGCToClientTournamentItemDrop", "EDotaUserMessages_DOTA_UM_MatchMetadata": "CDOTAClientMsg_MatchMetadata", + "ETEProtobufIds_TE_EffectDispatchId": "CMsgTEEffectDispatch", } skipped = map[string]bool{ "EDemoCommands_DEM_IsCompressed": true, @@ -59,31 +59,30 @@ var ( } // EBaseUserMessages_UM_HandHapticPulse tpl = `package main - - -/*------------------------------------------------------------------------------ - .aMMMb .aMMMb dMMMMb dMMMMMP - dMP"VMP dMP"dMP dMP VMP dMP - dMP dMP dMP dMP dMP dMMMP - dMP.aMP dMP.aMP dMP.aMP dMP - VMMMP" VMMMP" dMMMMP" dMMMMMP - - .aMMMMP dMMMMMP dMMMMb dMMMMMP dMMMMb .aMMMb dMMMMMMP dMMMMMP dMMMMb - dMP" dMP dMP dMP dMP dMP.dMP dMP"dMP dMP dMP dMP VMP - dMP MMP"dMMMP dMP dMP dMMMP dMMMMK" dMMMMMP dMP dMMMP dMP dMP - dMP.dMP dMP dMP dMP dMP dMP"AMF dMP dMP dMP dMP dMP.aMP - VMMMP" dMMMMMP dMP dMP dMMMMMP dMP dMP dMP dMP dMP dMMMMMP dMMMMP" - - -This code was generated by a code-generation program. It was NOT written by -hand. Do not edit this file by hand! Your edits will be destroyed! - -This file can be regenerated by running "go generate" - -The generator program is defined in "gen/main.go" - -------------------------------------------------------------------------------*/ +//////////////////////////////////////////////////////////////////////////////// +// +// .aMMMb .aMMMb dMMMMb dMMMMMP +// dMP"VMP dMP"dMP dMP VMP dMP +// dMP dMP dMP dMP dMP dMMMP +// dMP.aMP dMP.aMP dMP.aMP dMP +// VMMMP" VMMMP" dMMMMP" dMMMMMP +// +// .aMMMMP dMMMMMP dMMMMb dMMMMMP dMMMMb .aMMMb dMMMMMMP dMMMMMP dMMMMb +// dMP" dMP dMP dMP dMP dMP.dMP dMP"dMP dMP dMP dMP VMP +// dMP MMP"dMMMP dMP dMP dMMMP dMMMMK" dMMMMMP dMP dMMMP dMP dMP +// dMP.dMP dMP dMP dMP dMP dMP"AMF dMP dMP dMP dMP dMP.aMP +// VMMMP" dMMMMMP dMP dMP dMMMMMP dMP dMP dMP dMP dMP dMMMMMP dMMMMP" +// +// +// This code was generated by a code-generation program. It was NOT written by +// hand. Do not edit this file by hand! Your edits will be destroyed! +// +// This file can be regenerated by running "go generate" +// +// The generator program is defined in "gen/main.go" +// +//////////////////////////////////////////////////////////////////////////////// import ( "github.com/golang/protobuf/proto" diff --git a/generated.go b/generated.go index 1d0069b..2b4f11c 100644 --- a/generated.go +++ b/generated.go @@ -1,28 +1,28 @@ package main -/*------------------------------------------------------------------------------ - - .aMMMb .aMMMb dMMMMb dMMMMMP - dMP"VMP dMP"dMP dMP VMP dMP - dMP dMP dMP dMP dMP dMMMP - dMP.aMP dMP.aMP dMP.aMP dMP - VMMMP" VMMMP" dMMMMP" dMMMMMP - - .aMMMMP dMMMMMP dMMMMb dMMMMMP dMMMMb .aMMMb dMMMMMMP dMMMMMP dMMMMb - dMP" dMP dMP dMP dMP dMP.dMP dMP"dMP dMP dMP dMP VMP - dMP MMP"dMMMP dMP dMP dMMMP dMMMMK" dMMMMMP dMP dMMMP dMP dMP - dMP.dMP dMP dMP dMP dMP dMP"AMF dMP dMP dMP dMP dMP.aMP - VMMMP" dMMMMMP dMP dMP dMMMMMP dMP dMP dMP dMP dMP dMMMMMP dMMMMP" - - -This code was generated by a code-generation program. It was NOT written by -hand. Do not edit this file by hand! Your edits will be destroyed! - -This file can be regenerated by running "go generate" - -The generator program is defined in "gen/main.go" - -------------------------------------------------------------------------------*/ +//////////////////////////////////////////////////////////////////////////////// +// +// .aMMMb .aMMMb dMMMMb dMMMMMP +// dMP"VMP dMP"dMP dMP VMP dMP +// dMP dMP dMP dMP dMP dMMMP +// dMP.aMP dMP.aMP dMP.aMP dMP +// VMMMP" VMMMP" dMMMMP" dMMMMMP +// +// .aMMMMP dMMMMMP dMMMMb dMMMMMP dMMMMb .aMMMb dMMMMMMP dMMMMMP dMMMMb +// dMP" dMP dMP dMP dMP dMP.dMP dMP"dMP dMP dMP dMP VMP +// dMP MMP"dMMMP dMP dMP dMMMP dMMMMK" dMMMMMP dMP dMMMP dMP dMP +// dMP.dMP dMP dMP dMP dMP dMP"AMF dMP dMP dMP dMP dMP.aMP +// VMMMP" dMMMMMP dMP dMP dMMMMMP dMP dMP dMP dMP dMP dMMMMMP dMMMMP" +// +// +// This code was generated by a code-generation program. It was NOT written by +// hand. Do not edit this file by hand! Your edits will be destroyed! +// +// This file can be regenerated by running "go generate" +// +// The generator program is defined in "gen/main.go" +// +//////////////////////////////////////////////////////////////////////////////// import ( "github.com/golang/protobuf/proto" @@ -48,7 +48,6 @@ var cmdFactory = protoFactory{ 5: func() proto.Message { return new(dota.CDemoClassInfo) }, 6: func() proto.Message { return new(dota.CDemoStringTables) }, 7: func() proto.Message { return new(dota.CDemoPacket) }, - 8: func() proto.Message { return new(dota.CDemoPacket) }, 9: func() proto.Message { return new(dota.CDemoConsoleCmd) }, 10: func() proto.Message { return new(dota.CDemoCustomData) }, 11: func() proto.Message { return new(dota.CDemoCustomDataCallbacks) }, diff --git a/main.go b/main.go index fc3856a..6cc36e5 100644 --- a/main.go +++ b/main.go @@ -49,7 +49,7 @@ type options struct { f string // input file memprofile string cpuprofile string - messages bool // dump messages or no + datagrams bool // dump datagrams or no packets bool // dump packets or no } @@ -105,7 +105,7 @@ func main() { flag.BoolVar(&opts.b, "b", false, "input is expected to be bzip-compressed") flag.BoolVar(&opts.v, "v", false, "verbose mode") flag.StringVar(&opts.f, "f", "--", "input file to be used. -- means stdin") - flag.BoolVar(&opts.messages, "messages", false, "dump top-level messages to stdout") + flag.BoolVar(&opts.datagrams, "datagrams", false, "dump top-level datagram info to stdout") flag.BoolVar(&opts.packets, "packets", false, "dump packets to stdout") flag.StringVar(&opts.memprofile, "memprofile", "", "memory profile destination") flag.StringVar(&opts.cpuprofile, "cpuprofile", "", "cpu profile destination") @@ -125,7 +125,7 @@ func main() { } p := newParser(r) - p.dumpMessages = opts.messages + p.dumpDatagrams = opts.datagrams p.dumpPackets = opts.packets if err := p.start(); err != nil { bail(1, "parse error: %v", err) diff --git a/parser.go b/parser.go index f67c264..a36088a 100644 --- a/parser.go +++ b/parser.go @@ -4,8 +4,10 @@ import ( "bufio" "fmt" "io" + "reflect" "github.com/golang/protobuf/proto" + "github.com/golang/snappy" "github.com/jordanorelli/hyperstone/dota" ) @@ -13,8 +15,8 @@ type parser struct { // the source of replay bytes. Must NOT be compressed. source *bufio.Reader - dumpMessages bool - dumpPackets bool + dumpDatagrams bool + dumpPackets bool } func newParser(r io.Reader) *parser { @@ -38,26 +40,29 @@ func (p *parser) start() error { func (p *parser) run() error { for { - msg, err := p.readMessage() + gram, err := p.readDatagram() if err != nil { - return wrap(err, "read message error in run loop") + return wrap(err, "read datagram error in run loop") } - if p.dumpMessages { - fmt.Println(msg) + if p.dumpDatagrams { + fmt.Println(gram) } - switch msg.cmd { + + if len(gram.body) == 0 { + continue + } + + switch gram.cmd { case dota.EDemoCommands_DEM_Packet: - if err := msg.check(p.dumpPackets); err != nil { + if err := gram.check(p.dumpPackets); err != nil { fmt.Printf("error: %v\n", err) } default: - m := cmdFactory.BuildMessage(int(msg.cmd)) + m := cmdFactory.BuildMessage(int(gram.cmd)) if m != nil { - err := proto.Unmarshal(msg.body, m) + err := proto.Unmarshal(gram.body, m) if err != nil { - fmt.Printf("cmd unmarshal error: %v\n", err) - } else { - fmt.Println(m) + fmt.Printf("cmd unmarshal error unpacking data of length %d with cmd type %s into message type %v: %v\n", len(gram.body), gram.cmd, reflect.TypeOf(m), err) } } } @@ -133,31 +138,39 @@ func (p *parser) readCommand() (dota.EDemoCommands, bool, error) { return dota.EDemoCommands(n), compressed, nil } -func (p *parser) readMessage() (*dataGram, error) { +func (p *parser) readDatagram() (*dataGram, error) { cmd, compressed, err := p.readCommand() if err != nil { - return nil, wrap(err, "readMessage couldn't get a command") + return nil, wrap(err, "readDatagram couldn't get a command") } tick, err := p.decodeVarint() if err != nil { - return nil, wrap(err, "readMessage couldn't read the tick value") + return nil, wrap(err, "readDatagram couldn't read the tick value") } size, err := p.decodeVarint() if err != nil { - return nil, wrap(err, "readMessage couldn't read the size value") + return nil, wrap(err, "readDatagram couldn't read the size value") } if size > 0 { buf := make([]byte, int(size)) if _, err := io.ReadFull(p.source, buf); err != nil { - return nil, wrap(err, "readMessage couldn't read message body") + return nil, wrap(err, "readDatagram couldn't read datagram body") + } + + if compressed { + var err error + buf, err = snappy.Decode(nil, buf) + if err != nil { + return nil, wrap(err, "readDatagram couldn't snappy decode body") + } } // TODO: pool these! - return &dataGram{cmd, int64(tick), compressed, buf}, nil + return &dataGram{cmd, int64(tick), buf}, nil } // TODO: pool these! - return &dataGram{cmd, int64(tick), compressed, nil}, nil + return &dataGram{cmd, int64(tick), nil}, nil }