package main import ( "bufio" "fmt" "io" "github.com/jordanorelli/hyperstone/dota" ) type parser struct { // the source of replay bytes. Must NOT be compressed. source *bufio.Reader // re-useable scratch buffer. Contents never guaranteed to be clean. scratch []byte dumpMessages bool dumpPackets bool } func newParser(r io.Reader) *parser { br := bufio.NewReaderSize(r, 1<<16) return &parser{source: br, scratch: make([]byte, 1<<10)} } func (p *parser) start() error { ok, err := p.checkHeader() if err != nil { return wrap(err, "parser start error") } if !ok { return fmt.Errorf("parser start error: invalid header") } if _, err := p.source.Discard(8); err != nil { return wrap(err, "parser start error") } return nil } func (p *parser) run() error { for { msg, err := p.readMessage() if err != nil { return wrap(err, "read message error in run loop") } if p.dumpMessages { fmt.Println(msg) } switch msg.cmd { case dota.EDemoCommands_DEM_Packet: if err := msg.check(p.dumpPackets); err != nil { fmt.Printf("error: %v\n", err) } } } } // grows the scratch buffer until it is at least n bytes wide func (p *parser) growScratch(n int) { for len(p.scratch) < n { p.scratch = make([]byte, 2*len(p.scratch)) } } // DecodeVarint reads a varint-encoded integer from the source reader. // It returns the value as a uin64 and any errors encountered. The reader will // be advanced by the number of bytes needed to consume this value. On error, // the reader will not be advanced. // // This is the format for the // int32, int64, uint32, uint64, bool, and enum func (p *parser) decodeVarint() (uint64, error) { // protobuf defines values that are up to 64 bits wide. The largest value // stored in a protobuf varint is 64 data bits, which in varint encoding, // would require 10 bytes. buf, err := p.source.Peek(10) if err != nil { return 0, wrap(err, "decode varint couldn't peek 10 bytes") } var x uint64 var s uint for i, b := range buf { // when msb is 0, we're at the last byte of the value if b < 0x80 { if _, err := p.source.Discard(i + 1); err != nil { return 0, wrap(err, "decode varint couldn't discard %d bytes", i) } return x | uint64(b)< 0 { compressed = true n &^= 0x40 } return dota.EDemoCommands(n), compressed, nil } func (p *parser) readMessage() (*message, error) { cmd, compressed, err := p.readCommand() if err != nil { return nil, wrap(err, "readMessage couldn't get a command") } tick, err := p.decodeVarint() if err != nil { return nil, wrap(err, "readMessage couldn't read the tick value") } size, err := p.decodeVarint() if err != nil { return nil, wrap(err, "readMessage 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") } // TODO: pool these! return &message{cmd, int64(tick), compressed, buf}, nil } // TODO: pool these! return &message{cmd, int64(tick), compressed, nil}, nil }