package parse import ( "bufio" "encoding/binary" "errors" "fmt" "io" "github.com/golang/snappy" "github.com/olemorud/replay-parser/demo" "google.golang.org/protobuf/proto" ) type Frame struct { Kind demo.EDemoCommands `json:"kind"` // see demo.proto Tick uint64 `json:"tick"` // time elapsed in replay time Message proto.Message `json:"message"` // protobuf encoded message (may be compressed with snappy) HasEmbeddedData bool } // Return true if the frame message is compressed (with google snappy) func (frame *Frame) isCompressed() bool { return (frame.Kind&demo.EDemoCommands_DEM_IsCompressed != 0) } // Checks if the file header corresponds to a Dota 2 replay file and returns the position of the last frame func First(r *bufio.Reader) (uint64, error) { // Check if first 8 bytes of file is PBDEMS2\0 header := make([]byte, 8) if _, err := io.ReadFull(r, header); err != nil { return 0, fmt.Errorf("error when reading file: %v", err) } if string(header) != SOURCE2_SIGN { return 0, fmt.Errorf("wrong file signature: %v, should be ", SOURCE2_SIGN) } // Read offset to last frame var offset uint32 err := binary.Read(r, binary.LittleEndian, &offset) if err != nil { return 0, fmt.Errorf("error reading number of frames in replay: %v", err) } return uint64(offset), nil } // Parses the next frame func DecodeNextFrame(r *bufio.Reader) (*Frame, error) { // Read command c, err := binary.ReadUvarint(r) if err != nil { return nil, fmt.Errorf("error reading frame command: %v", err) } command := demo.EDemoCommands(c) // Read tick tick, err := binary.ReadUvarint(r) if err != nil { return nil, fmt.Errorf("error reading frame tick: %v", err) } if tick == 0xFFFFFFFF { tick = 0 } // Read size size, err := binary.ReadUvarint(r) if err != nil { return nil, fmt.Errorf("error reading frame size: %v", err) } // Instanciate frame with data frame := &Frame{ Kind: command, Tick: tick, Message: nil, HasEmbeddedData: false, } frame.setMessageType() // Debug if !frame.HasEmbeddedData { fmt.Printf(` kind: %d tick: %d size: %d `, command & ^demo.EDemoCommands_DEM_IsCompressed, tick, size) } if frame.HasEmbeddedData { r.Discard(int(size)) return nil, nil } // Read message message := make([]byte, size) io.ReadFull(r, message) if frame.isCompressed() { decoded, err := snappy.Decode(nil, message) if err != nil { return nil, fmt.Errorf("error decoding message: %v", err) } proto.Unmarshal(decoded, frame.Message) } else { proto.Unmarshal(message, frame.Message) } return frame, nil } // I hate reflection func (f *Frame) setMessageType() { switch f.Kind & ^demo.EDemoCommands_DEM_IsCompressed { default: f.Message = nil // case demo.EDemoCommands_DEM_Error: // f.Message = &demo.CDemoError{} case demo.EDemoCommands_DEM_Stop: f.Message = &demo.CDemoStop{} case demo.EDemoCommands_DEM_FileHeader: f.Message = &demo.CDemoFileHeader{} case demo.EDemoCommands_DEM_FileInfo: f.Message = &demo.CDemoFileInfo{} case demo.EDemoCommands_DEM_SyncTick: f.Message = &demo.CDemoSyncTick{} case demo.EDemoCommands_DEM_SendTables: f.Message = &demo.CDemoSendTables{} f.HasEmbeddedData = true case demo.EDemoCommands_DEM_ClassInfo: f.Message = &demo.CDemoClassInfo{} case demo.EDemoCommands_DEM_StringTables: f.Message = &demo.CDemoStringTables{} case demo.EDemoCommands_DEM_Packet: f.Message = &demo.CDemoPacket{} f.HasEmbeddedData = true case demo.EDemoCommands_DEM_SignonPacket: f.Message = nil //&demo.CDemoSignonPacket{} f.HasEmbeddedData = true case demo.EDemoCommands_DEM_ConsoleCmd: f.Message = &demo.CDemoConsoleCmd{} case demo.EDemoCommands_DEM_CustomData: f.Message = &demo.CDemoCustomData{} case demo.EDemoCommands_DEM_CustomDataCallbacks: f.Message = &demo.CDemoCustomDataCallbacks{} case demo.EDemoCommands_DEM_UserCmd: f.Message = &demo.CDemoUserCmd{} case demo.EDemoCommands_DEM_FullPacket: f.Message = &demo.CDemoFullPacket{} f.HasEmbeddedData = true case demo.EDemoCommands_DEM_SaveGame: f.Message = &demo.CDemoSaveGame{} case demo.EDemoCommands_DEM_SpawnGroups: f.Message = &demo.CDemoSpawnGroups{} } } // Following code is stolen from the binary package and adjusted to 32 bit numbers const MaxVarintLen32 = 5 func ReadUvarint32(r io.ByteReader) (uint32, error) { var x uint32 var s uint for i := 0; i < MaxVarintLen32; i++ { b, err := r.ReadByte() if err != nil { return x, err } if b < 0x80 { if i == MaxVarintLen32-1 && b > 1 { return x, errors.New("binary: varint overflows a 32-bit integer") } return x | uint32(b)<