diff --git a/hyperstone.pb.go b/hyperstone.pb.go new file mode 100644 index 0000000..3c42b49 --- /dev/null +++ b/hyperstone.pb.go @@ -0,0 +1,162 @@ +// Code generated by protoc-gen-go. +// source: hyperstone.proto +// DO NOT EDIT! + +/* +Package main is a generated protocol buffer package. + +It is generated from these files: + hyperstone.proto + +It has these top-level messages: + Envelope +*/ +package main + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type EDemoCommands int32 + +const ( + EDemoCommands_DEM_Error EDemoCommands = -1 + EDemoCommands_DEM_Stop EDemoCommands = 0 + EDemoCommands_DEM_FileHeader EDemoCommands = 1 + EDemoCommands_DEM_FileInfo EDemoCommands = 2 + EDemoCommands_DEM_SyncTick EDemoCommands = 3 + EDemoCommands_DEM_SendTables EDemoCommands = 4 + EDemoCommands_DEM_ClassInfo EDemoCommands = 5 + EDemoCommands_DEM_StringTables EDemoCommands = 6 + EDemoCommands_DEM_Packet EDemoCommands = 7 + EDemoCommands_DEM_SignonPacket EDemoCommands = 8 + EDemoCommands_DEM_ConsoleCmd EDemoCommands = 9 + EDemoCommands_DEM_CustomData EDemoCommands = 10 + EDemoCommands_DEM_CustomDataCallbacks EDemoCommands = 11 + EDemoCommands_DEM_UserCmd EDemoCommands = 12 + EDemoCommands_DEM_FullPacket EDemoCommands = 13 + EDemoCommands_DEM_SaveGame EDemoCommands = 14 + EDemoCommands_DEM_SpawnGroups EDemoCommands = 15 + EDemoCommands_DEM_Max EDemoCommands = 16 + EDemoCommands_DEM_IsCompressed EDemoCommands = 64 +) + +var EDemoCommands_name = map[int32]string{ + -1: "DEM_Error", + 0: "DEM_Stop", + 1: "DEM_FileHeader", + 2: "DEM_FileInfo", + 3: "DEM_SyncTick", + 4: "DEM_SendTables", + 5: "DEM_ClassInfo", + 6: "DEM_StringTables", + 7: "DEM_Packet", + 8: "DEM_SignonPacket", + 9: "DEM_ConsoleCmd", + 10: "DEM_CustomData", + 11: "DEM_CustomDataCallbacks", + 12: "DEM_UserCmd", + 13: "DEM_FullPacket", + 14: "DEM_SaveGame", + 15: "DEM_SpawnGroups", + 16: "DEM_Max", + 64: "DEM_IsCompressed", +} +var EDemoCommands_value = map[string]int32{ + "DEM_Error": -1, + "DEM_Stop": 0, + "DEM_FileHeader": 1, + "DEM_FileInfo": 2, + "DEM_SyncTick": 3, + "DEM_SendTables": 4, + "DEM_ClassInfo": 5, + "DEM_StringTables": 6, + "DEM_Packet": 7, + "DEM_SignonPacket": 8, + "DEM_ConsoleCmd": 9, + "DEM_CustomData": 10, + "DEM_CustomDataCallbacks": 11, + "DEM_UserCmd": 12, + "DEM_FullPacket": 13, + "DEM_SaveGame": 14, + "DEM_SpawnGroups": 15, + "DEM_Max": 16, + "DEM_IsCompressed": 64, +} + +func (x EDemoCommands) Enum() *EDemoCommands { + p := new(EDemoCommands) + *p = x + return p +} +func (x EDemoCommands) String() string { + return proto.EnumName(EDemoCommands_name, int32(x)) +} +func (x *EDemoCommands) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(EDemoCommands_value, data, "EDemoCommands") + if err != nil { + return err + } + *x = EDemoCommands(value) + return nil +} +func (EDemoCommands) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +type Envelope struct { + Command *EDemoCommands `protobuf:"varint,1,req,name=command,enum=main.EDemoCommands" json:"command,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Envelope) Reset() { *m = Envelope{} } +func (m *Envelope) String() string { return proto.CompactTextString(m) } +func (*Envelope) ProtoMessage() {} +func (*Envelope) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Envelope) GetCommand() EDemoCommands { + if m != nil && m.Command != nil { + return *m.Command + } + return EDemoCommands_DEM_Error +} + +func init() { + proto.RegisterType((*Envelope)(nil), "main.Envelope") + proto.RegisterEnum("main.EDemoCommands", EDemoCommands_name, EDemoCommands_value) +} + +func init() { proto.RegisterFile("hyperstone.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 320 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x54, 0xd0, 0x4d, 0x4f, 0xf2, 0x40, + 0x10, 0x00, 0xe0, 0x97, 0x8f, 0x57, 0x60, 0x80, 0x32, 0x0e, 0x46, 0x4d, 0xbc, 0x18, 0xe3, 0xc1, + 0x78, 0x20, 0xc6, 0x5f, 0x60, 0x52, 0x2a, 0x72, 0x20, 0x31, 0x01, 0xcf, 0x66, 0x69, 0x47, 0x6c, + 0xd8, 0xee, 0x6e, 0x76, 0x0b, 0xca, 0xdd, 0xff, 0xad, 0x2c, 0xd0, 0x26, 0xf4, 0xd6, 0x67, 0xbe, + 0x76, 0x06, 0xf0, 0x73, 0x63, 0xd8, 0xba, 0x5c, 0x2b, 0x1e, 0x18, 0xab, 0x73, 0x4d, 0xf5, 0x4c, + 0xa4, 0xea, 0xe6, 0x01, 0x9a, 0x91, 0x5a, 0xb3, 0xd4, 0x86, 0xe9, 0x16, 0x1a, 0xb1, 0xce, 0x32, + 0xa1, 0x92, 0xcb, 0xca, 0x75, 0xf5, 0x2e, 0x78, 0xec, 0x0f, 0x7c, 0xce, 0x20, 0x1a, 0x72, 0xa6, + 0xc3, 0x7d, 0xc4, 0xdd, 0xff, 0xd4, 0xa0, 0x7b, 0x24, 0x74, 0x0e, 0xad, 0x61, 0x34, 0x79, 0x8f, + 0xac, 0xd5, 0x16, 0x7f, 0x8b, 0xaf, 0x42, 0x1d, 0x68, 0x7a, 0x9f, 0xe6, 0xda, 0xe0, 0x3f, 0x22, + 0x08, 0xfc, 0xdf, 0x73, 0x2a, 0xf9, 0x85, 0x45, 0xc2, 0x16, 0x2b, 0x84, 0xd0, 0x29, 0x6c, 0xac, + 0x3e, 0x34, 0x56, 0x0b, 0x99, 0x6e, 0x54, 0x3c, 0x4b, 0xe3, 0x25, 0xd6, 0x8a, 0xba, 0x29, 0xab, + 0x64, 0x26, 0xe6, 0x92, 0x1d, 0xd6, 0xe9, 0x14, 0xba, 0xde, 0x42, 0x29, 0x9c, 0xdb, 0x15, 0xfe, + 0xa7, 0x33, 0xc0, 0xfd, 0x30, 0x9b, 0xaa, 0xc5, 0x21, 0xf1, 0x84, 0x02, 0x00, 0xaf, 0xaf, 0x22, + 0x5e, 0x72, 0x8e, 0x8d, 0x32, 0x2b, 0x5d, 0x28, 0xad, 0x0e, 0xda, 0x2c, 0x46, 0x84, 0x5a, 0x39, + 0x2d, 0x39, 0xcc, 0x12, 0x6c, 0x95, 0xb6, 0xda, 0xde, 0x2c, 0x1b, 0x8a, 0x5c, 0x20, 0xd0, 0x15, + 0x5c, 0x1c, 0x5b, 0x28, 0xa4, 0x9c, 0x6f, 0xbb, 0x38, 0x6c, 0x53, 0x0f, 0xda, 0x3e, 0xf8, 0xe6, + 0xd8, 0xfa, 0x0e, 0x9d, 0x72, 0xe1, 0x95, 0x94, 0x87, 0x49, 0xdd, 0x72, 0x3d, 0xb1, 0xe6, 0x91, + 0xc8, 0x18, 0x03, 0xea, 0x43, 0x6f, 0x27, 0x46, 0x7c, 0xa9, 0x91, 0xd5, 0x2b, 0xe3, 0xb0, 0x47, + 0x6d, 0x68, 0x78, 0x9c, 0x88, 0x6f, 0xc4, 0xe2, 0xcd, 0x63, 0xb7, 0x3d, 0xb8, 0xb1, 0xec, 0x1c, + 0x27, 0xf8, 0xf4, 0x17, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xcd, 0xc1, 0x45, 0xd1, 0x01, 0x00, 0x00, +} diff --git a/hyperstone.proto b/hyperstone.proto new file mode 100644 index 0000000..a76420e --- /dev/null +++ b/hyperstone.proto @@ -0,0 +1,28 @@ +package main; + +enum EDemoCommands { + DEM_Error = -1; + DEM_Stop = 0; + DEM_FileHeader = 1; + DEM_FileInfo = 2; + DEM_SyncTick = 3; + DEM_SendTables = 4; + DEM_ClassInfo = 5; + DEM_StringTables = 6; + DEM_Packet = 7; + DEM_SignonPacket = 8; + DEM_ConsoleCmd = 9; + DEM_CustomData = 10; + DEM_CustomDataCallbacks = 11; + DEM_UserCmd = 12; + DEM_FullPacket = 13; + DEM_SaveGame = 14; + DEM_SpawnGroups = 15; + DEM_Max = 16; + DEM_IsCompressed = 64; +} + +message Envelope { + required EDemoCommands command = 1; +} + diff --git a/main.go b/main.go index fa18eb8..4b6c993 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,7 @@ import ( ) const ( - replay_header = "PBDEMS2\000" + replayHeader = "PBDEMS2\000" ) func ensureNewline(t string) string { @@ -72,4 +72,5 @@ func main() { if err := p.start(); err != nil { bail(1, "parse error: %v", err) } + p.run() } diff --git a/parser.go b/parser.go index dfa01c9..3c9c3a8 100644 --- a/parser.go +++ b/parser.go @@ -1,20 +1,23 @@ package main import ( + "bufio" "fmt" "io" + // "github.com/golang/protobuf/proto" ) type parser struct { // the source of replay bytes. Must NOT be compressed. - source io.Reader + source *bufio.Reader // re-useable scratch buffer. Contents never guaranteed to be clean. scratch []byte } func newParser(r io.Reader) *parser { - return &parser{source: r, scratch: make([]byte, 1<<10)} + br := bufio.NewReader(r) + return &parser{source: br, scratch: make([]byte, 1<<10)} } func (p *parser) start() error { @@ -25,22 +28,123 @@ func (p *parser) start() error { if !ok { return fmt.Errorf("parser start error: invalid header") } + if _, err := p.source.Discard(8); err != nil { + return err + } return nil } +func (p *parser) run() { + for { + msg, err := p.readMessage() + if err != nil { + fmt.Printf("error: %v\n", err) + return + } + fmt.Println(msg) + } +} + +// 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) { + buf, err := p.source.Peek(9) + if err != nil { + return 0, fmt.Errorf("decode varint couldn't peek 9 bytes: %v", err) + } + + 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, fmt.Errorf("decode varint couldn't discard %d bytes: %v", i, err) + } + return x | uint64(b)< cap(p.scratch) { + p.scratch = make([]byte, 2*cap(p.scratch)) + return p.readn(n) + } + buf := p.scratch[:n] + if _, err := io.ReadFull(p.source, buf); err != nil { + return nil, fmt.Errorf("error reading %d bytes: %v", n, err) + } + return buf, nil +} + // checks whether we have an acceptable header at the current reader position. func (p *parser) checkHeader() (bool, error) { - buf := p.scratch[:8] - if _, err := p.source.Read(buf); err != nil { + buf, err := p.readn(8) + if err != nil { return false, fmt.Errorf("unable to read header bytes: %v", err) } - return string(buf) == replay_header, nil + return string(buf) == replayHeader, nil } -// skips n bytes in the underlying source -func (p *parser) skip(n int) error { - if _, err := p.source.Read(p.scratch[:n]); err != nil { - return fmt.Errorf("unable to skip %d bytes: %v", n, err) +func (p *parser) readCommand() (EDemoCommands, error) { + n, err := p.decodeVarint() + if err != nil { + return EDemoCommands_DEM_Error, fmt.Errorf("readCommand couldn't read varint: %v", err) } - return nil + return EDemoCommands(n), nil +} + +type message struct { + cmd EDemoCommands + tick int64 + body []byte +} + +func (m *message) String() string { + if len(m.body) > 30 { + return fmt.Sprintf("{cmd: %v tick: %v body: %q...}", m.cmd, m.tick, m.body[:27]) + } + return fmt.Sprintf("{cmd: %v tick: %v body: %q}", m.cmd, m.tick, m.body) +} + +func (p *parser) readMessage() (*message, error) { + cmd, err := p.readCommand() + if err != nil { + return nil, fmt.Errorf("readMessage couldn't get a command: %v", err) + } + + tick, err := p.decodeVarint() + if err != nil { + return nil, fmt.Errorf("readMessage couldn't read the tick value: %v", err) + } + + size, err := p.decodeVarint() + if err != nil { + return nil, fmt.Errorf("readMessage couldn't read the size value: %v", err) + } + + if size > 0 { + buf, err := p.readn(int(size)) + if err != nil { + return nil, fmt.Errorf("readMessage couldn't read message body: %v", err) + } + return &message{cmd, int64(tick), buf}, nil + } + + return &message{cmd, int64(tick), nil}, nil }