diff --git a/bit/decode.go b/bit/decode.go index 41c6d0f..5f4c883 100644 --- a/bit/decode.go +++ b/bit/decode.go @@ -61,19 +61,15 @@ func ReadUBitVarFP(r Reader) uint64 { // in little-endian order. The most-significant bit of each byte represents a // continuation bit. func ReadVarInt(r Reader) uint64 { - var ( - x uint64 - b uint64 - shift uint - ) - for ; shift < 64; shift += 7 { - b = r.ReadBits(8) + var x uint64 + for s := uint64(0); s < 70; s += 7 { + b := r.ReadBits(8) if r.Err() != nil { return 0 } - x |= b & 0x7f << shift - if b&0x80 == 0 { - return x + x |= b & 0x7f << s + if b < 0x80 { + break } } return x @@ -81,22 +77,18 @@ func ReadVarInt(r Reader) uint64 { // reads a 32bit varint func ReadVarInt32(r Reader) uint32 { - var ( - x uint64 - b uint64 - shift uint - ) - for ; shift < 32; shift += 7 { - b = r.ReadBits(8) + var x uint32 + for s := uint32(0); s < 35; s += 7 { + b := uint32(r.ReadBits(8)) if r.Err() != nil { return 0 } - x |= b & 0x7f << shift - if b&0x80 == 0 { - return uint32(x) + x |= b & 0x7f << s + if b < 0x80 { + break } } - return uint32(x) + return x } func ReadBool(r Reader) bool { diff --git a/bit/misc.go b/bit/misc.go new file mode 100644 index 0000000..8877f5c --- /dev/null +++ b/bit/misc.go @@ -0,0 +1,9 @@ +package bit + +import ( + "math" +) + +func Length(n uint) uint { + return uint(math.Floor(math.Log2(float64(n)) + 1)) +} diff --git a/ent/array.go b/ent/array.go index 7788c8a..ab211ae 100644 --- a/ent/array.go +++ b/ent/array.go @@ -1,17 +1,149 @@ package ent import ( + "fmt" "strconv" + "strings" + + "github.com/jordanorelli/hyperstone/bit" ) +var constants = map[string]int{ + "MAX_ABILITY_DRAFT_ABILITIES": 48, +} + +func arrayType(spec *typeSpec, env *Env) tÿpe { + if !strings.Contains(spec.typeName, "[") { + return nil + } + elemName, count := parseArrayName(spec.typeName) + elemSpec := *spec + elemSpec.typeName = elemName + elemType := parseTypeSpec(&elemSpec, env) + if elemName == "char" { + return string_t(count) + } + return &array_t{elem: elemType, count: count} +} + +func parseArrayName(s string) (string, uint) { + runes := []rune(s) + if runes[len(runes)-1] != ']' { + panic("invalid array type name: " + s) + } + for i := len(runes) - 2; i >= 0; i-- { + if runes[i] == '[' { + ns := strings.TrimSpace(string(runes[i+1 : len(runes)-1])) + n, err := strconv.Atoi(ns) + if err != nil { + n = constants[ns] + if n <= 0 { + panic("invalid array type name: " + err.Error()) + } + } + return strings.TrimSpace(string(runes[:i])), uint(n) + } + } + panic("invalid array type name: " + s) +} + +type array_t struct { + elem tÿpe + count uint + bits uint +} + +func (t *array_t) sizeBits() uint { + if t.bits == 0 { + t.bits = bit.Length(t.count) + } + return t.bits +} + +func (t *array_t) nü() value { return array{t: t, slots: make([]value, t.count)} } +func (t array_t) typeName() string { return fmt.Sprintf("%s[%d]", t.elem.typeName(), t.count) } + type array struct { - slots []interface{} - _slotType string - decoder + t *array_t + slots []value +} + +func (a array) tÿpe() tÿpe { return a.t } + +func (a array) read(r bit.Reader) error { + n := r.ReadBits(a.t.bits) + Debug.Printf("reading %d array elements", n) + for i := uint64(0); i < n; i++ { + if a.slots[i] == nil { + a.slots[i] = a.t.elem.nü() + } + if err := a.slots[i].read(r); err != nil { + return wrap(err, "array read error at index %d", i) + } + } + return r.Err() +} + +func (a array) String() string { + if len(a.slots) > 8 { + return fmt.Sprintf("%s(%d)%v...", a.t.typeName(), len(a.slots), a.slots[:8]) + } + return fmt.Sprintf("%s(%d)%v", a.t.typeName(), len(a.slots), a.slots) +} + +func (a array) slotType(int) tÿpe { return a.t.elem } +func (a array) slotName(n int) string { return strconv.Itoa(n) } +func (a array) setSlotValue(slot int, v value) { + // TODO: type check here? + a.slots[slot] = v +} +func (a array) getSlotValue(slot int) value { return a.slots[slot] } + +// ------------------------------------------------------------------------------ +// strings are a special case of arrays +// ------------------------------------------------------------------------------ + +type string_t int + +func (t string_t) nü() value { + return &string_v{t: t, buf: make([]byte, int(t))} +} + +func (t string_t) typeName() string { return "string" } + +type string_v struct { + t string_t + buf []byte // the buffer of all possible bytes + valid []byte // selection of current valid bytes within buf +} + +func (s *string_v) tÿpe() tÿpe { return s.t } +func (s *string_v) read(r bit.Reader) error { + for i := 0; i < int(s.t); i++ { + b := r.ReadBits(8) + if b == 0 { + s.valid = s.buf[:i] + return r.Err() + } + s.buf[i] = byte(b & 0xff) + } + s.valid = s.buf + return r.Err() } -func (a *array) slotName(slot int) string { return strconv.Itoa(slot) } -func (a *array) slotValue(slot int) interface{} { return a.slots[slot] } -func (a *array) slotType(slot int) string { return a._slotType } -func (a *array) slotDecoder(slot int) decoder { return a.decoder } -func (a *array) setSlotValue(slot int, val interface{}) { a.slots[slot] = val } +func (s *string_v) String() string { + return string(s.valid) +} + +func (s *string_v) slotType(int) tÿpe { return char_t } +func (s *string_v) slotName(n int) string { return strconv.Itoa(n) } +func (s *string_v) setSlotValue(slot int, v value) { + s.buf[slot] = byte(*v.(*char_v)) + if slot >= len(s.valid) { + s.valid = s.buf[:slot+1] + } +} +func (s *string_v) getSlotValue(slot int) value { + v := char_v(s.buf[slot]) + return &v +} diff --git a/ent/atoms.go b/ent/atoms.go new file mode 100644 index 0000000..935a985 --- /dev/null +++ b/ent/atoms.go @@ -0,0 +1,390 @@ +package ent + +import ( + "fmt" + "github.com/jordanorelli/hyperstone/bit" + "strconv" +) + +// ------------------------------------------------------------------------------ +// bool +// ------------------------------------------------------------------------------ + +var bool_t = &typeLiteral{ + name: "bool", + newFn: func() value { + return new(bool_v) + }, +} + +type bool_v bool + +func (v bool_v) tÿpe() tÿpe { return bool_t } + +func (v *bool_v) read(r bit.Reader) error { + *v = bool_v(bit.ReadBool(r)) + return r.Err() +} + +func (v bool_v) String() string { + if v { + return "true" + } + return "false" +} + +// ------------------------------------------------------------------------------ +// uint8 +// ------------------------------------------------------------------------------ + +var uint8_t = &typeLiteral{ + name: "uint8", + newFn: func() value { + return new(uint8_v) + }, +} + +type uint8_v uint8 + +func (v uint8_v) tÿpe() tÿpe { return uint8_t } +func (v *uint8_v) read(r bit.Reader) error { + *v = uint8_v(bit.ReadVarInt32(r)) + return r.Err() +} + +func (v uint8_v) String() string { + return strconv.FormatUint(uint64(v), 10) +} + +// ------------------------------------------------------------------------------ +// uint16 +// ------------------------------------------------------------------------------ + +var uint16_t = &typeLiteral{ + name: "uint16", + newFn: func() value { + return new(uint16_v) + }, +} + +type uint16_v uint16 + +func (v uint16_v) tÿpe() tÿpe { return uint16_t } +func (v *uint16_v) read(r bit.Reader) error { + *v = uint16_v(bit.ReadVarInt32(r)) + return r.Err() +} + +func (v uint16_v) String() string { + return strconv.FormatUint(uint64(v), 10) +} + +// ------------------------------------------------------------------------------ +// uint32 +// ------------------------------------------------------------------------------ + +var uint32_t = &typeLiteral{ + name: "uint32", + newFn: func() value { + return new(uint32_v) + }, +} + +type uint32_v uint32 + +func (v uint32_v) tÿpe() tÿpe { return uint32_t } +func (v *uint32_v) read(r bit.Reader) error { + *v = uint32_v(bit.ReadVarInt32(r)) + return r.Err() +} + +func (v uint32_v) String() string { + return strconv.FormatUint(uint64(v), 10) +} + +// ------------------------------------------------------------------------------ +// uint64 +// ------------------------------------------------------------------------------ + +var uint64_t = &typeLiteral{ + name: "uint64", + newFn: func() value { + return new(uint64_v) + }, +} + +type uint64_v uint64 + +func (v uint64_v) tÿpe() tÿpe { return uint64_t } +func (v *uint64_v) read(r bit.Reader) error { + *v = uint64_v(bit.ReadVarInt32(r)) + return r.Err() +} + +func (v uint64_v) String() string { + return strconv.FormatUint(uint64(v), 10) +} + +// ------------------------------------------------------------------------------ +// uint64fixed +// +// (a uint64 value that is always represented on the wire with 64 bits) +// ------------------------------------------------------------------------------ + +var uint64fixed_t = &typeLiteral{ + name: "uint64fixed", + newFn: func() value { + return new(uint64fixed_v) + }, +} + +type uint64fixed_v uint64 + +func (v uint64fixed_v) tÿpe() tÿpe { return uint64fixed_t } +func (v *uint64fixed_v) read(r bit.Reader) error { + *v = uint64fixed_v(r.ReadBits(64)) + return r.Err() +} + +func (v uint64fixed_v) String() string { + return strconv.FormatUint(uint64(v), 10) +} + +// ------------------------------------------------------------------------------ +// int8 +// ------------------------------------------------------------------------------ + +var int8_t = &typeLiteral{ + name: "int8", + newFn: func() value { + return new(int8_v) + }, +} + +type int8_v int8 + +func (v int8_v) tÿpe() tÿpe { return int8_t } +func (v *int8_v) read(r bit.Reader) error { + // TODO: bounds check here? + *v = int8_v(bit.ReadZigZag32(r)) + return r.Err() +} + +func (v int8_v) String() string { + return strconv.FormatInt(int64(v), 10) +} + +// ------------------------------------------------------------------------------ +// int16 +// ------------------------------------------------------------------------------ + +var int16_t = &typeLiteral{ + name: "int16", + newFn: func() value { + return new(int16_v) + }, +} + +type int16_v int16 + +func (v int16_v) tÿpe() tÿpe { return int16_t } +func (v *int16_v) read(r bit.Reader) error { + // TODO: bounds check here? + *v = int16_v(bit.ReadZigZag32(r)) + return r.Err() +} + +func (v int16_v) String() string { + return strconv.FormatInt(int64(v), 10) +} + +// ------------------------------------------------------------------------------ +// int32 +// ------------------------------------------------------------------------------ + +var int32_t = &typeLiteral{ + name: "int32", + newFn: func() value { + return new(int32_v) + }, +} + +type int32_v int32 + +func (v int32_v) tÿpe() tÿpe { return int32_t } +func (v *int32_v) read(r bit.Reader) error { + *v = int32_v(bit.ReadZigZag32(r)) + return r.Err() +} + +func (v int32_v) String() string { + return strconv.FormatInt(int64(v), 10) +} + +// ------------------------------------------------------------------------------ +// int64 +// ------------------------------------------------------------------------------ + +var int64_t = &typeLiteral{ + name: "int64", + newFn: func() value { + return new(int64_v) + }, +} + +type int64_v int64 + +func (v int64_v) tÿpe() tÿpe { return int64_t } +func (v *int64_v) read(r bit.Reader) error { + *v = int64_v(bit.ReadZigZag(r)) + return r.Err() +} + +func (v int64_v) String() string { + return strconv.FormatInt(int64(v), 10) +} + +// ------------------------------------------------------------------------------ +// CUtlStringToken +// +// weirdly, this type isn't a string; it's actually a number. The number +// presumably indicates some value on a symbol table. +// ------------------------------------------------------------------------------ + +var stringToken_t = &typeLiteral{ + name: "CUtlStringToken", + newFn: func() value { + return new(stringToken_v) + }, +} + +type stringToken_v uint64 + +func (v stringToken_v) tÿpe() tÿpe { return stringToken_t } +func (v *stringToken_v) read(r bit.Reader) error { + *v = stringToken_v(bit.ReadVarInt(r)) + return r.Err() +} + +func (v stringToken_v) String() string { + return fmt.Sprintf("token:%d", v) +} + +// ------------------------------------------------------------------------------ +// Color +// ------------------------------------------------------------------------------ + +var color_t = &typeLiteral{ + name: "Color", + newFn: func() value { + return new(color) + }, +} + +type color struct{ r, g, b, a uint8 } + +func (c color) tÿpe() tÿpe { return color_t } +func (c *color) read(r bit.Reader) error { + u := bit.ReadVarInt(r) + c.r = uint8(u >> 6 & 0xff) + c.g = uint8(u >> 4 & 0xff) + c.b = uint8(u >> 2 & 0xff) + c.a = uint8(u >> 0 & 0xff) + return r.Err() +} + +func (c color) String() string { + return fmt.Sprintf("#%x%x%x%x", c.r, c.g, c.b, c.a) +} + +// ------------------------------------------------------------------------------ +// CUtlSymbolLarge +// ------------------------------------------------------------------------------ + +var cutl_string_t = typeLiteral{ + name: "CUtlSymbolLarge", + newFn: func() value { + return new(cutl_string_v) + }, +} + +type cutl_string_v string + +func (v cutl_string_v) tÿpe() tÿpe { return cutl_string_t } +func (v cutl_string_v) String() string { return string(v) } +func (v *cutl_string_v) read(r bit.Reader) error { + *v = cutl_string_v(bit.ReadString(r)) + return r.Err() +} + +// ------------------------------------------------------------------------------ +// char +// +// this seems absolutely wrong, but it's how it's done in clarity. a char is a +// cstring? wtf? +// ------------------------------------------------------------------------------ + +var char_t = typeLiteral{ + name: "char", + newFn: func() value { + return new(char_v) + }, +} + +type char_v byte + +func (v char_v) tÿpe() tÿpe { return char_t } +func (v char_v) String() string { return string(v) } +func (v *char_v) read(r bit.Reader) error { + *v = char_v(r.ReadByte()) + return r.Err() +} + +var atom_types = []tÿpe{ + bool_t, + char_t, + uint8_t, + uint16_t, + uint32_t, + int8_t, + int16_t, + int32_t, + int64_t, + stringToken_t, + color_t, + cutl_string_t, +} + +func atomType(spec *typeSpec, env *Env) tÿpe { + for _, t := range atom_types { + if t.typeName() == spec.typeName { + Debug.Printf(" atom type: %s", t.typeName()) + if spec.bits != 0 { + return typeError("spec can't be atom type: has bit specification: %v", spec) + } + if spec.encoder != "" { + return typeError("spec can't be atom type: has encoder specification: %v", spec) + } + if spec.flags != 0 { + return typeError("spec can't be atom type: has flags: %v", spec) + } + if spec.high != 0 { + return typeError("spec can't be atom type: has high value constraint: %v", spec) + } + if spec.low != 0 { + return typeError("spec can't be atom type: has low value constraint: %v", spec) + } + if spec.serializer != "" { + return typeError("spec can't be atom type: has serializer: %v", spec) + } + return t + } + } + if spec.typeName == "uint64" { + if spec.encoder == "fixed64" { + return uint64fixed_t + } + return uint64_t + } + return nil +} diff --git a/ent/class.go b/ent/class.go index bd09da3..0237318 100644 --- a/ent/class.go +++ b/ent/class.go @@ -2,53 +2,57 @@ package ent import ( "fmt" - "github.com/jordanorelli/hyperstone/dota" ) -// Class represents a set of constraints around an Entity. -type Class struct { - name Symbol - Version int - Fields []*Field +type class struct { + name string + version int + fields []field +} - // all other entities for this class use this instance as a prototype - baseline *Entity +func (c class) String() string { return c.typeName() } - // maps field names back to their indexes. Entities use this to access - // their own fields by name instead of by slot. - fieldNames map[string]int +func (c class) typeName() string { + return fmt.Sprintf("%s_v%d", c.name, c.version) } -func (c *Class) Name() string { return c.name.String() } -func (c *Class) Slotted() bool { return true } -func (c *Class) Id() classId { return classId{name: c.name, version: c.Version} } +func (c *class) nü() value { + return &entity{class: c, slots: make([]value, len(c.fields))} +} -func (c *Class) New(serial int, baseline bool) *Entity { - e := &Entity{ - Class: c, - slots: make([]interface{}, len(c.Fields)), - serial: serial, - isBaseline: baseline, - } - for slot := range e.slots { - e.slots[slot] = c.Fields[slot].initializer() - } - return e +type classHistory struct { + versions map[int]*class + oldest *class + newest *class } -func (c Class) String() string { - return fmt.Sprintf("{%s %d}", c.Name, c.Version) +func (h *classHistory) add(c *class) { + if h.oldest == nil || c.version < h.oldest.version { + h.oldest = c + } + if h.newest == nil || c.version > h.newest.version { + h.newest = c + } + if h.versions == nil { + h.versions = make(map[int]*class) + } + h.versions[c.version] = c } -// A class is identified by the union of its name and version. -type classId struct { - name Symbol - version int +func (h *classHistory) version(v int) *class { + if h.versions == nil { + return nil + } + return h.versions[v] } -func (c *Class) fromProto(v *dota.ProtoFlattenedSerializerT, fields []Field) { - c.Fields = make([]*Field, len(v.GetFieldsIndex())) - for i, fi := range v.GetFieldsIndex() { - c.Fields[i] = &fields[fi] +func classType(spec *typeSpec, env *Env) tÿpe { + if spec.serializer != "" { + c := env.classVersion(spec.serializer, spec.serializerV) + if c != nil { + return c + } + return typeError("unable to find class named %s with version %d", spec.serializer, spec.serializerV) } + return nil } diff --git a/ent/cutl.go b/ent/cutl.go deleted file mode 100644 index 3b4b968..0000000 --- a/ent/cutl.go +++ /dev/null @@ -1,20 +0,0 @@ -package ent - -import ( - "strconv" -) - -type cutlVector struct { - slots []interface{} - _slotType string - decoder -} - -func (v *cutlVector) slotName(slot int) string { return strconv.Itoa(slot) } -func (v *cutlVector) slotValue(slot int) interface{} { return v.slots[slot] } -func (v *cutlVector) slotType(slot int) string { return v._slotType } -func (v *cutlVector) slotDecoder(slot int) decoder { return v.decoder } - -func (v *cutlVector) setSlotValue(slot int, val interface{}) { - v.slots[slot] = val -} diff --git a/ent/decoders.go b/ent/decoders.go deleted file mode 100644 index c47c503..0000000 --- a/ent/decoders.go +++ /dev/null @@ -1,232 +0,0 @@ -package ent - -import ( - // "strings" - - "github.com/jordanorelli/hyperstone/bit" -) - -// a decoder decodes an entity value off of a bit reader -type decoder func(bit.Reader) interface{} - -// creates a new field decoder for the field f. -func newFieldDecoder(n *Namespace, f *Field) decoder { - Debug.Printf("new decoder: type: %s name: %s sendNode: %s\n\tbits: %d low: %v high: %v\n\tflags: %d serializer: %v serializerVersion: %v\n\tclass: %v encoder: %v", f._type, f.name, f.sendNode, f.bits, f.low, f.high, f.flags, f.serializer, f.serializerVersion, f.class, f.encoder) - - typeName := f._type.String() - - switch typeName { - case "bool": - return decodeBool - case "uint8", "uint16", "uint32", "uint64": - return decodeVarInt64 - case "Color": - return decodeColor - case "int8", "int16", "int32", "int64": - return decodeZigZag - case "CNetworkedQuantizedFloat", "float32": - return floatDecoder(f) - case "Vector": - return vectorDecoder(f) - case "QAngle": - return qangleDecoder(f) - case "CGameSceneNodeHandle": - // ehhh maybe no? - return decodeVarInt32 - case "CUtlStringToken": - return symbolDecoder(n) - case "CUtlSymbolLarge", "char": - return decodeString - } - - // the field is itself an entity contained within the outer entity. - if f.class != nil { - return entityDecoder(f.class) - } - - // for compound types such as handles, vectors (in the c++ std::vector - // sense), and arrays - switch f.typeSpec.kind { - case t_element: - Debug.Printf("weird typespec: we shouldn't have elements here: %v", f.typeSpec) - return func(bit.Reader) interface{} { - Info.Fatalf("unable to decode element of type: %v", f.typeSpec.name) - return nil - } - case t_object: - return func(br bit.Reader) interface{} { - Debug.Printf("unable to decode object of type: %v", f.typeSpec.name) - return decodeVarInt32(br) - } - case t_array: - return arrayDecoder(n, f) - case t_template: - return templateDecoder(f) - case t_pointer: - return decodeBool - } - - panic("fart") -} - -func decodeBool(br bit.Reader) interface{} { return bit.ReadBool(br) } -func decodeVarInt32(br bit.Reader) interface{} { return bit.ReadVarInt32(br) } -func decodeVarInt64(br bit.Reader) interface{} { return bit.ReadVarInt(br) } -func decodeZigZag(br bit.Reader) interface{} { return bit.ReadZigZag(br) } -func decodeString(br bit.Reader) interface{} { return bit.ReadString(br) } - -type color struct{ r, g, b, a uint8 } - -func decodeColor(br bit.Reader) interface{} { - u := bit.ReadVarInt(br) - return color{ - r: uint8(u & 0xff000000), - g: uint8(u & 0x00ff0000), - b: uint8(u & 0x0000ff00), - a: uint8(u & 0x000000ff), - } -} - -func entityDecoder(c *Class) decoder { - return func(br bit.Reader) interface{} { - bit.ReadBool(br) // what does this do - return c.New(-1, false) - } -} - -func vectorDecoder(f *Field) decoder { - if f.encoder != nil { - switch f.encoder.String() { - case "normal": - return decodeNormalVector - default: - return nil - } - } - - fn := floatDecoder(f) - if fn == nil { - return nil - } - return func(br bit.Reader) interface{} { - return vector{fn(br).(float32), fn(br).(float32), fn(br).(float32)} - } -} - -func decodeNormalVector(br bit.Reader) interface{} { - var v vector - x, y := bit.ReadBool(br), bit.ReadBool(br) - if x { - v.x = bit.ReadNormal(br) - } - if y { - v.y = bit.ReadNormal(br) - } - - // here comes a shitty hack! - bit.ReadBool(br) - // discard this flag, it's concerned with the sign of the z value, which - // we're skipping. - return v - - // ok, so. I have a suspicion that what we're interested in here is a - // surface normal, and that it's describing something about the geometry of - // the scene. I can't for the life of me see why in the hell we'd *ever* - // care about this in the context of parsing a replay, so I'm just turning - // off the z calculation. What follows is the original implementation as - // adapted from Manta and Clarity. The reason I care to skip this is that - // it involves a sqrt, which is a super slow operatation, and one worth - // dispensing with if you don't need the data that it produces. - // - // p := v.x*v.x + v.y*v.y - // if p < 1.0 { - // v.z = float32(math.Sqrt(float64(1.0 - p))) - // } - // if bit.ReadBool(br) { - // // we might wind up with the float representation of negative zero here, - // // but as far as I can tell, negative zero is fine because negative - // // zero is equivalent to positive zero. They'll print differently, but - // // I don't think we care about that. - // v.z = -v.z - // } - // return v -} - -// decodes a qangle, which is a representation of an euler angle. that is, it's -// a three-angle encoding of orientation. -// https://developer.valvesoftware.com/wiki/QAngle -// -// the x, y, and z in this case do not refer to positions along a set of -// cartesian axes, but degress of rotation in an Euler angle. -// -// (-45,10,0) means 45° up, 10° left and 0° roll. -func qangleDecoder(f *Field) decoder { - if f.encoder != nil && f.encoder.String() == "qangle_pitch_yaw" { - return nil - } - bits := f.bits - if bits == 32 { - return nil - } - if bits == 0 { - return func(br bit.Reader) interface{} { - var ( - v vector - x = bit.ReadBool(br) - y = bit.ReadBool(br) - z = bit.ReadBool(br) - ) - if x { - v.x = bit.ReadCoord(br) - } - if y { - v.y = bit.ReadCoord(br) - } - if z { - v.z = bit.ReadCoord(br) - } - return v - } - } - return func(br bit.Reader) interface{} { - return vector{ - x: bit.ReadAngle(br, bits), - y: bit.ReadAngle(br, bits), - z: bit.ReadAngle(br, bits), - } - } -} - -func arrayDecoder(n *Namespace, f *Field) decoder { - return func(br bit.Reader) interface{} { - Debug.Printf("dunno what this int32 val does in array decoder: %d", bit.ReadVarInt32(br)) - if f.initializer != nil { - return f.initializer() - } - return nil - } -} - -func templateDecoder(f *Field) decoder { - switch f.typeSpec.template { - case "CHandle": - return decodeVarInt32 - case "CStrongHandle": - return decodeVarInt64 - case "CUtlVector": - return func(br bit.Reader) interface{} { - v := decodeVarInt32(br) - Debug.Printf("dunno what this varint is for in the cutlvector decoder: %v", v) - return v - } - } - return nil -} - -// so far a sanity check on the values I'm seeing out of this seem wrong. -func symbolDecoder(n *Namespace) decoder { - return func(br bit.Reader) interface{} { - u := bit.ReadVarInt32(br) - return n.Symbol(int(u)) - } -} diff --git a/ent/dict.go b/ent/dict.go deleted file mode 100644 index 4f69bdb..0000000 --- a/ent/dict.go +++ /dev/null @@ -1,221 +0,0 @@ -package ent - -import ( - "fmt" - "strconv" - - "github.com/golang/protobuf/proto" - - "github.com/jordanorelli/hyperstone/bit" - "github.com/jordanorelli/hyperstone/dota" - "github.com/jordanorelli/hyperstone/stbl" -) - -// https://developer.valvesoftware.com/wiki/Entity_limit -// -// There can be up to 4096 entities. This total is split into two groups of 2048: -// Non-networked entities, which exist only on the client or server (e.g. -// death ragdolls on client, logicals on server). -// Entities with associated edicts, which can cross the client/server divide. -// -// If the game tries to assign a 2049th edict it will exit with an error -// message, but if it tries to create a 2049th non-networked entity it will -// merely refuse and print a warning to the console. The logic behind this -// may be that an entity spawned dynamically (i.e. not present in the map) -// but not assigned an edict probably isn't too important. -const e_limit = 2048 - -// Dict corresponds to the edict_t in Valve's documentation for the Source -// engine. See here: https://developer.valvesoftware.com/wiki/Edict_t -// -// From the Valve docs: -// edict_t ("entity dictionary") is an interface struct that allows entities -// to cross the client/server divide: with one attached, an entity has the -// same index at both ends. The edict also manages the state of the entity's -// DataTable and provides a common representation across all DLLs. It cannot -// be used on the client. -type Dict struct { - *Namespace - entities []*Entity - hidx map[int]*Entity // handle index - br *bit.BufReader - sr *selectionReader - - // a reference to our string table of entity baseline data. For whatever - // reason, the first set of baselines sometimes come in before the classes - // are defined. - base *stbl.Table -} - -func NewDict(sd *stbl.Dict) *Dict { - d := &Dict{ - Namespace: new(Namespace), - entities: make([]*Entity, e_limit), - br: new(bit.BufReader), - sr: new(selectionReader), - base: sd.TableForName("instancebaseline"), - } - sd.WatchTable("instancebaseline", d.updateBaselines) - return d -} - -// creates an entity with the provided id. the entity's contents data are read -// off of the Dict's internal bit stream br -func (d *Dict) createEntity(id int) error { - classId := int(d.readClassId(d.br)) - if len(d.Namespace.classes) == 0 { - return fmt.Errorf("unable to create entity %d: namespace has no classes", id) - } - serial := int(d.br.ReadBits(17)) - classV := int(bit.ReadVarInt(d.br)) - className := d.classIds[classId] - class := d.Class(className, classV) - if class == nil { - return fmt.Errorf("unable to create entity %d: no class found for class name %s, version %d", className, classV) - } - e := class.New(serial, false) - d.entities[id] = e - Debug.Printf("create entity id: %d serial: %d classId: %d className: %v class: %v\n", id, serial, classId, className, class) - if err := fillSlots(e, class.Name(), d.sr, d.br); err != nil { - return fmt.Errorf("failed to create entity %d (%s): %v", id, className, err) - } - return nil -} - -func (d *Dict) getEntity(id int) *Entity { - if id < 0 || id >= e_limit { - Debug.Printf("edict refused getEntity request for invalid id %d", id) - return nil - } - return d.entities[id] -} - -func (d *Dict) updateEntity(id int) error { - Debug.Printf("update entity id: %d\n", id) - e := d.getEntity(id) - if e == nil { - return fmt.Errorf("update entity %d refused: no such entity", id) - } - if err := fillSlots(e, e.Class.String(), d.sr, d.br); err != nil { - return fmt.Errorf("failed to update entity %d (%s): %v", id, e.Class.Name(), err) - } - return nil -} - -func (d *Dict) deleteEntity(id int) error { - Debug.Printf("delete entity id: %d\n", id) - if id < 0 || id >= e_limit { - // we're probably reading corrupt shit if we get here - return fmt.Errorf("delete entity %d refused: id is out of bounds", id) - } - if d.entities[id] == nil { - // this one's probably ok to let fly, but it's probably a sign that - // something is wrong elsewhere. - Debug.Printf("delete entity %d refused: no such entity", id) - return nil - } - d.entities[id] = nil - return nil -} - -func (d *Dict) leaveEntity(id int) error { - Debug.Printf("leave entity id: %d\n", id) - // what the shit does this do? - return nil -} - -func (d *Dict) Handle(m proto.Message) { - switch v := m.(type) { - case *dota.CDemoSendTables: - d.mergeSendTables(v) - d.syncBaselines() - - case *dota.CDemoClassInfo: - d.mergeClassInfo(v) - d.syncBaselines() - - case *dota.CSVCMsg_PacketEntities: - if err := d.mergeEntities(v); err != nil { - Info.Fatalf("merge entities error: %v", err) - } - } -} - -func (d *Dict) mergeEntities(m *dota.CSVCMsg_PacketEntities) error { - data := m.GetEntityData() - - Debug.Printf("packet header MaxEntries: %d UpdatedEntries: %v IsDelta: %t UpdateBaseline: %t Baseline: %d DeltaFrom: %d PendingFullFrame: %t ActiveSpawngroupHandle: %d", m.GetMaxEntries(), m.GetUpdatedEntries(), m.GetIsDelta(), m.GetUpdateBaseline(), m.GetBaseline(), m.GetDeltaFrom(), m.GetPendingFullFrame(), m.GetActiveSpawngroupHandle()) - - d.br.SetSource(data) - id := -1 - for i := 0; i < int(m.GetUpdatedEntries()); i++ { - id++ - // there may be a jump indicator, indicating how many id positions - // to skip. - id += int(bit.ReadUBitVar(d.br)) - - // next two bits encode one of four entity mutate operations - var fn func(int) error - switch d.br.ReadBits(2) { - case 0: - fn = d.updateEntity - case 1: - fn = d.leaveEntity - case 2: - fn = d.createEntity - case 3: - fn = d.deleteEntity - } - - if err := fn(id); err != nil { - return fmt.Errorf("error on entity %d: %v", id, err) - } - } - return nil -} - -func (d *Dict) updateBaselines(t *stbl.Table) { - d.base = t - d.syncBaselines() -} - -func (d *Dict) syncBaselines() error { - if !d.hasClassinfo() { - Debug.Printf("syncBaselines skip: no classInfo yet") - return nil - } - Debug.Printf("syncBaselines start") - if d.base == nil { - return fmt.Errorf("syncBaselines failed: reference to baseline string table is nil") - } - - for _, e := range d.base.Entries() { - id, err := strconv.Atoi(e.Key) - if err != nil { - Debug.Printf("syncBaselines skipping entry with key %s: key failed to parse to integer: %v", e.Key, err) - continue - } - - c := d.ClassByNetId(id) - if c == nil { - Debug.Printf("syncBaselines skipping entry with key %s: no such class", e.Key) - continue - } - - if c.baseline == nil { - c.baseline = c.New(-1, true) - } - - if e.Value == nil || len(e.Value) == 0 { - Debug.Printf("syncBaselines skipping entry with key %s: value is empty", e.Key) - continue - } - - d.br.SetSource(e.Value) - Debug.Printf("syncBaselines has new baseline for class %v", c) - if err := fillSlots(c.baseline, c.Name(), d.sr, d.br); err != nil { - return fmt.Errorf("syncBaselines failed to fill a baseline: %v", err) - } - } - return nil -} diff --git a/ent/entity.go b/ent/entity.go index cc19d92..a4142dc 100644 --- a/ent/entity.go +++ b/ent/entity.go @@ -1,23 +1,38 @@ package ent -type Entity struct { - *Class - serial int - slots []interface{} - isBaseline bool +import ( + "fmt" + "github.com/jordanorelli/hyperstone/bit" +) + +type entity struct { + class *class + slots []value } -func (e *Entity) slotName(n int) string { return e.Class.Fields[n].name.String() } -func (e *Entity) slotType(n int) string { return e.Class.Fields[n]._type.String() } -func (e *Entity) slotValue(n int) interface{} { - v := e.slots[n] - if v != nil { - return v +func (e *entity) read(r bit.Reader) error { + bit.ReadBool(r) // ??? + return nil +} + +func (e *entity) className() string { + if e.class != nil { + return e.class.name } - if !e.isBaseline && e.Class.baseline != nil { - return e.Class.baseline.slotValue(n) + return "" +} + +func (e *entity) String() string { + return fmt.Sprintf("%s{id: ?}", e.class.typeName()) +} + +func (e *entity) tÿpe() tÿpe { return e.class } +func (e *entity) slotType(i int) tÿpe { + if i >= len(e.class.fields) { + return typeError("index out of range in slotType: %d is beyond capacity %d", i, len(e.class.fields)) } - return nil + return e.class.fields[i].tÿpe } -func (e *Entity) slotDecoder(n int) decoder { return e.Class.Fields[n].decoder } -func (e *Entity) setSlotValue(n int, v interface{}) { e.slots[n] = v } +func (e *entity) slotName(i int) string { return e.class.fields[i].name } +func (e *entity) setSlotValue(i int, v value) { e.slots[i] = v } +func (e *entity) getSlotValue(i int) value { return e.slots[i] } diff --git a/ent/env.go b/ent/env.go new file mode 100644 index 0000000..ec8ebcc --- /dev/null +++ b/ent/env.go @@ -0,0 +1,203 @@ +package ent + +import ( + "github.com/golang/protobuf/proto" + "strconv" + + "github.com/jordanorelli/hyperstone/bit" + "github.com/jordanorelli/hyperstone/dota" + "github.com/jordanorelli/hyperstone/stbl" +) + +type Env struct { + symbols symbolTable + source bit.BufReader + classes map[string]*classHistory + netIds map[int]string + fields []field + strings *stbl.Dict + entities map[int]*entity +} + +func NewEnv() *Env { + e := &Env{ + classes: make(map[string]*classHistory), + strings: stbl.NewDict(), + } + e.strings.WatchTable("instancebaseline", e.syncBaselineTable) + return e +} + +func (e *Env) Handle(m proto.Message) error { + switch v := m.(type) { + case *dota.CSVCMsg_CreateStringTable: + _, err := e.strings.Create(v) + return err + + case *dota.CSVCMsg_UpdateStringTable: + return e.strings.Update(v) + + case *dota.CDemoSendTables: + if err := e.mergeSendTables(v); err != nil { + return err + } + + case *dota.CDemoClassInfo: + e.mergeClassInfo(v) + e.syncBaseline() + } + return nil +} + +func (e *Env) setSource(buf []byte) { + e.source.SetSource(buf) +} + +// merges the "sendTables" message. "sendTables" is a deeply misleading name, +// but it's the name the structure is given in the protobufs. sendTables +// contains the type definitions that define our entity type system. +func (e *Env) mergeSendTables(m *dota.CDemoSendTables) error { + Debug.Printf("merge send tables") + + flat, err := getSerializers(m) + if err != nil { + return wrap(err, "unable to get serializers in sendtables") + } + for i, s := range flat.GetSymbols() { + Debug.Printf("symbol %d: %s", i, s) + } + e.symbols = symbolTable(flat.GetSymbols()) + e.stubClasses(flat) + if err := e.parseFields(flat); err != nil { + return wrap(err, "unable to parse serializer fields") + } + e.fillClasses(flat) + return nil +} + +// stubs out the classes to be created later. we do this to create empty class +// structs that fields may point to. +func (e *Env) stubClasses(flat *dota.CSVCMsg_FlattenedSerializer) { + serializers := flat.GetSerializers() + for _, s := range serializers { + name := e.symbol(int(s.GetSerializerNameSym())) + v := int(s.GetSerializerVersion()) + c := &class{name: name, version: v} + Debug.Printf("new class: %s", c) + h := e.classes[name] + if h == nil { + h = new(classHistory) + e.classes[name] = h + } + h.add(c) + } +} + +// parses the type definitions for each field. some fields have types that +// refer to class types defined in the replay file. classes must be declared up +// front via stubclasses prior to parseFields working correctly. +func (e *Env) parseFields(flat *dota.CSVCMsg_FlattenedSerializer) error { + e.fields = make([]field, len(flat.GetFields())) + for i, ff := range flat.GetFields() { + f := &e.fields[i] + if err := f.fromProto(ff, e); err != nil { + return err + } + } + return nil +} + +// associates each class with its list of fields. parseFields must be run +// before fillClasses in order for the field definitions to exist. +func (e *Env) fillClasses(flat *dota.CSVCMsg_FlattenedSerializer) { + for _, s := range flat.GetSerializers() { + name := e.symbol(int(s.GetSerializerNameSym())) + v := int(s.GetSerializerVersion()) + class := e.classes[name].version(v) + + class.fields = make([]field, len(s.GetFieldsIndex())) + for i, id := range s.GetFieldsIndex() { + Debug.Printf("class %s has field %s (%s)", name, e.fields[id].name, e.fields[id].typeName()) + class.fields[i] = e.fields[id] + } + } +} + +func (e *Env) mergeClassInfo(m *dota.CDemoClassInfo) { + if e.netIds == nil { + e.netIds = make(map[int]string, len(m.GetClasses())) + } + for _, info := range m.GetClasses() { + id := info.GetClassId() + name := info.GetNetworkName() + table := info.GetTableName() + Debug.Printf("class info id: %d name: %s table: %s", id, name, table) + e.netIds[int(id)] = name + } +} + +func (e *Env) symbol(id int) string { return e.symbols[id] } + +func (e *Env) syncBaseline() { + t := e.strings.TableForName("instancebaseline") + if t != nil { + e.syncBaselineTable(t) + } +} + +func (e *Env) syncBaselineTable(t *stbl.Table) { + if e.netIds == nil || len(e.netIds) == 0 { + Debug.Printf("syncBaselines skipped: net ids are nil") + } + if e.classes == nil || len(e.classes) == 0 { + Debug.Printf("syncBaselines skipped: classes are nil") + } + + r := new(bit.BufReader) + sr := new(selectionReader) + for _, entry := range t.Entries() { + netId, err := strconv.Atoi(entry.Key) + if err != nil { + Debug.Printf("syncBaselines ignored bad key %s: %v", err) + continue + } + className := e.netIds[netId] + if className == "" { + Debug.Printf("syncBaselines couldn't find class with net id %d", netId) + continue + } + c := e.class(className) + if c == nil { + Debug.Printf("syncBaselines couldn't find class named %s", className) + continue + } + Debug.Printf("syncBaselines key: %s className: %s", entry.Key, c.name) + ent := c.nü().(*entity) + r.SetSource(entry.Value) + selections, err := sr.readSelections(r) + if err != nil { + Debug.Printf("unable to read selections for %s: %v", className, err) + continue + } + Debug.Printf("selections: %v", selections) + for _, s := range selections { + if err := s.fillSlots(ent, r); err != nil { + Debug.Printf("syncBaseline fill error: %v", err) + break + } + } + } +} + +func (e *Env) class(name string) *class { + h := e.classes[name] + if h == nil { + return nil + } + return h.newest +} + +func (e *Env) classVersion(name string, version int) *class { + h := e.classes[name] + return h.version(version) +} diff --git a/ent/field.go b/ent/field.go index 3d4aad5..b2b7e8c 100644 --- a/ent/field.go +++ b/ent/field.go @@ -1,109 +1,27 @@ package ent import ( - "bytes" "fmt" - "github.com/jordanorelli/hyperstone/dota" ) -/* -type Field struct { - Name string - Type +type field struct { + name string + tÿpe } -*/ - -type Field struct { - _type Symbol // type of data held by the field - typeSpec typeSpec - name Symbol // name of the field - sendNode Symbol // not sure what this is - bits uint // number of bits used to encode field? - low float32 // lower limit of field values - high float32 // upper limit of field values - flags int // used by float decoder - serializer *Symbol // the field is an entity with this class - serializerVersion *int32 - class *Class // source class on which the field was originally defined - encoder *Symbol // binary encoder, named explicitly in protobuf - decoder // decodes field values from a bit stream - initializer func() interface{} - isTemplate bool // whether or not the field is a template type - templateType string - elemType string -} - -func (f Field) String() string { - var buf bytes.Buffer - fmt.Fprintf(&buf, "{type: %s name: %s send: %s", f._type, f.name, f.sendNode) - if f.bits > 0 { - fmt.Fprintf(&buf, " bits: %d", f.bits) - } - if f.flags > 0 { - fmt.Fprintf(&buf, " flags: %d", f.flags) +func (f *field) fromProto(flat *dota.ProtoFlattenedSerializerFieldT, env *Env) error { + Debug.Printf("parse flat field: %s", prettyFlatField(flat, env)) + t := parseFieldType(flat, env) + if t == nil { + return fmt.Errorf("unable to parse type %s", prettyFlatField(flat, env)) } - fmt.Fprintf(&buf, " low: %f", f.low) - fmt.Fprintf(&buf, " high: %f", f.high) - if f.serializer != nil { - fmt.Fprintf(&buf, " serializer: %s", *f.serializer) - } - if f.serializerVersion != nil { - fmt.Fprintf(&buf, " serializer_v: %d", *f.serializerVersion) - } - if f.encoder != nil { - fmt.Fprintf(&buf, " encoder: %s", *f.encoder) - } - fmt.Fprint(&buf, "}") - return buf.String() -} - -func (f *Field) fromProto(flat *dota.ProtoFlattenedSerializerFieldT, t *SymbolTable) { - f._type = t.Symbol(int(flat.GetVarTypeSym())) - f.name = t.Symbol(int(flat.GetVarNameSym())) - f.bits = uint(flat.GetBitCount()) - f.flags = int(flat.GetEncodeFlags()) - f.low = flat.GetLowValue() - f.high = flat.GetHighValue() - f.initializer = nilInitializer - - if flat.FieldSerializerNameSym == nil { - f.serializer = nil - } else { - f.serializer = new(Symbol) - *f.serializer = t.Symbol(int(flat.GetFieldSerializerNameSym())) + if err, ok := t.(error); ok { + return wrap(err, "unable to parse type %s", prettyFlatField(flat, env)) } - f.serializerVersion = flat.FieldSerializerVersion - // panic if we don't have a send node cause that shit is corrupt yo - f.sendNode = t.Symbol(int(*flat.SendNodeSym)) - Debug.Printf("new field: %v", f) + Debug.Printf(" type: %v", t) + f.tÿpe = t + f.name = env.symbol(int(flat.GetVarNameSym())) + return nil } - -// creates a new field which is a sort of virtual field that represents what a -// field woudl look like if we had one for a container field's elements. -// honestly this is a really shitty hack it just seems easier than rewriting -// the newFieldDecoder logic. -func (f *Field) memberField() *Field { - mf := new(Field) - *mf = *f - mf.typeSpec = *f.typeSpec.member - // yeahhhh - mf._type = Symbol{0, &SymbolTable{mf.typeSpec.name}} - return mf -} - -func (f *Field) isContainer() bool { - if f.typeSpec.kind == t_array { - return true - } - if f.typeSpec.kind == t_template { - if f.typeSpec.template == "CUtlVector" { - return true - } - } - return false -} - -func nilInitializer() interface{} { return nil } diff --git a/ent/float.go b/ent/float.go index b1efe3c..6af3cd6 100644 --- a/ent/float.go +++ b/ent/float.go @@ -1,11 +1,10 @@ package ent import ( - "fmt" "math" + "strconv" "github.com/jordanorelli/hyperstone/bit" - "github.com/jordanorelli/hyperstone/dota" ) const ( @@ -14,80 +13,126 @@ const ( f_center ) -func parseFloatType(n *Namespace, flat *dota.ProtoFlattenedSerializerFieldT) (Type, error) { - if flat.VarEncoderSym != nil { - encoder := n.Symbol(int(flat.GetVarEncoderSym())).String() - switch encoder { - case "coord": - return nil, fmt.Errorf("coord encoder isn't dont yet") - default: - return nil, fmt.Errorf("unknown float encoder: %s", encoder) - } +func floatType(spec *typeSpec, env *Env) tÿpe { + switch spec.typeName { + case "CNetworkedQuantizedFloat": + return qFloatType(spec, env) + case "float32": + case "Vector": + default: + return nil } - - if flat.BitCount != nil { - bits := int(flat.GetBitCount()) - switch { - case bits < 0: - return nil, fmt.Errorf("invalid bit count on float field: %d", bits) - case bits < 32: - return quantizedFloat(n, flat) - case bits == 0, bits == 32: - // these seem meaningless, which is suspicious. - default: - return nil, fmt.Errorf("bit count is too high on float field: %d", bits) - } + if spec.encoder == "coord" { + Debug.Printf(" coord float type") + return coord_t } - - if flat.LowValue != nil || flat.HighValue != nil { - return nil, fmt.Errorf("float32 with a low or high value isn't supported") + if spec.serializer == "simulationtime" { + return nil } + switch spec.bits { + case 0, 32: + Debug.Printf(" std float type") + return float_t + default: + return qFloatType(spec, env) + } +} - type_name := n.Symbol(int(flat.GetVarTypeSym())).String() - return &Primitive{name: type_name, read: readFloat32}, nil +var coord_t = &typeLiteral{ + name: "coord", + newFn: func() value { + return new(coord_v) + }, } -func quantizedFloat(n *Namespace, flat *dota.ProtoFlattenedSerializerFieldT) (Type, error) { - if flat.LowValue == nil && flat.HighValue == nil { - return nil, fmt.Errorf("quantizedFloat has no boundaries") - } +type coord_v float32 - bits := uint(flat.GetBitCount()) - low, high := flat.GetLowValue(), flat.GetHighValue() - flags := int(flat.GetEncodeFlags()) +func (v coord_v) tÿpe() tÿpe { return coord_t } +func (v *coord_v) read(r bit.Reader) error { + *v = coord_v(bit.ReadCoord(r)) + return r.Err() +} - flags = flags & 7 // dunno how to handle -8 lol - steps := uint(1< 0: - special = &low - case flags&f_max > 0: - special = &high - case flags&f_center > 0: - middle := (high + low) * 0.5 - special = &middle +func (v float_v) tÿpe() tÿpe { return float_t } +func (v *float_v) read(r bit.Reader) error { + *v = float_v(math.Float32frombits(uint32(r.ReadBits(32)))) + return r.Err() +} + +func (v float_v) String() string { + return strconv.FormatFloat(float64(v), 'f', 3, 32) +} + +func qFloatType(spec *typeSpec, env *Env) tÿpe { + if spec.bits < 0 { + return typeError("quantized float has invalid negative bit count specifier") } + if spec.high-spec.low < 0 { + return typeError("quantized float has invalid negative range") + } + + t := &qfloat_t{typeSpec: *spec} + t.span = t.high - t.low + t.intervals = uint(1< 0 { + t.special = new(float32) + switch t.flags { + case f_min: + *t.special = t.low + case f_max: + *t.special = t.high + case f_center: + *t.special = t.low + (t.high+t.low)*0.5 + default: + return typeError("dunno how to handle qfloat flag value: %d", t.flags) } - u := br.ReadBits(bits) - return low + float32(u)*step_width, br.Err() } - type_name := n.Symbol(int(flat.GetVarTypeSym())).String() - return &Primitive{name: type_name, read: read}, nil + Debug.Printf(" qfloat type") + return t +} + +type qfloat_t struct { + typeSpec + span float32 // total range of values + intervals uint // number of intervals in the quantization range + interval float32 // width of one interval + special *float32 +} + +func (t *qfloat_t) nü() value { return &qfloat_v{t: t} } +func (t qfloat_t) typeName() string { return "qfloat" } + +type qfloat_v struct { + t *qfloat_t + v float32 +} + +func (v qfloat_v) tÿpe() tÿpe { return v.t } +func (v *qfloat_v) read(r bit.Reader) error { + if v.t.special != nil && bit.ReadBool(r) { + v.v = *v.t.special + } else { + v.v = v.t.low + float32(r.ReadBits(v.t.bits))*v.t.interval + } + return r.Err() } -// reads an IEEE 754 binary float value off of the stream -func readFloat32(br bit.Reader, d *Dict) (interface{}, error) { - return math.Float32frombits(uint32(br.ReadBits(32))), nil +func (v qfloat_v) String() string { + return strconv.FormatFloat(float64(v.v), 'f', 3, 32) } diff --git a/ent/float_decoders.go b/ent/float_decoders.go deleted file mode 100644 index 4dc61e9..0000000 --- a/ent/float_decoders.go +++ /dev/null @@ -1,84 +0,0 @@ -package ent - -import ( - "math" - - "github.com/jordanorelli/hyperstone/bit" -) - -func floatDecoder(f *Field) decoder { - if f.encoder != nil && f.encoder.String() == "coord" { - return nil - } - - if f.bits <= 0 || f.bits >= 32 { - return ieeeFloat32Decoder - } - - // a quantized field value must have some range specified, otherwise the - // quantization makes no sense. - if f.low == 0 && f.high == 0 { - panic("quantization rules make no sense") - } - - bits := f.bits - low := f.low - high := f.high - - flags := f.flags - - // there's a flag that's -8 and i don't know what to do with it. I'm just - // gonna mask away everything except the three least significant bits and - // pray for the best. - flags = flags & 7 - - // number of input steps - steps := int(1< 0: - special = new(float32) - *special = low - case flags&f_max > 0: - special = new(float32) - *special = high - case flags&f_center > 0: - special = new(float32) - middle := (high + low) * 0.5 - // if we're within a step of zero just return zero. - if middle > 0 && middle-step_width < 0 || middle < 0 && middle+step_width > 0 { - middle = 0 - } - *special = middle - } - - return func(br bit.Reader) interface{} { - if special != nil && bit.ReadBool(br) { - Debug.Printf("decode float type: %s low: %f high: %f bits: %d steps: %d span: %f flags: %d special: %v", f._type.String(), low, high, bits, steps, span, flags, *special) - return *special - } - u := br.ReadBits(bits) - out := low + float32(u)*inv_steps*span - Debug.Printf("decode float type: %s low: %f high: %f bits: %d bitVal: %d steps: %d span: %f flags: %d output: %v", f._type.String(), low, high, bits, u, steps, span, flags, out) - return out - } -} - -// reads an IEEE 754 binary float value off of the stream -func ieeeFloat32Decoder(br bit.Reader) interface{} { - return math.Float32frombits(uint32(br.ReadBits(32))) -} diff --git a/ent/generic.go b/ent/generic.go new file mode 100644 index 0000000..c98528d --- /dev/null +++ b/ent/generic.go @@ -0,0 +1,117 @@ +package ent + +import ( + "fmt" + "strconv" + "strings" + + "github.com/jordanorelli/hyperstone/bit" +) + +func genericType(spec *typeSpec, env *Env) tÿpe { + if !strings.Contains(spec.typeName, "<") { + return nil + } + + parts, err := genericName(spec.typeName) + if err != nil { + return typeError("bad generic name: %v", err) + } + + genericName, elemName := parts[0], parts[1] + elemSpec := *spec + elemSpec.typeName = elemName + elem := parseTypeSpec(&elemSpec, env) + + switch genericName { + case "CHandle", "CStrongHandle": + t := handle_t(fmt.Sprintf("%s<%s>", genericName, elem.typeName())) + return &t + case "CUtlVector": + return &cutl_vector_t{elem} + default: + return typeError("unknown generic name: %v", parts[0]) + } +} + +func genericName(name string) ([2]string, error) { + var out [2]string + runes := []rune(strings.TrimSpace(name)) + b_start := 0 + depth := 0 + for i, r := range runes { + if r == '<' { + if depth == 0 { + b_start = i + out[0] = strings.TrimSpace(string(runes[0:i])) + } + depth++ + } + if r == '>' { + depth-- + if depth == 0 { + if i == len(runes)-1 { + out[1] = strings.TrimSpace(string(runes[b_start+1 : i])) + } else { + return out, fmt.Errorf("extra runes in generic type name") + } + } + } + } + if out[0] == "" { + return out, fmt.Errorf("empty generic container name") + } + if out[1] == "" { + return out, fmt.Errorf("empty generic element name") + } + Debug.Printf(" generic name in: %s out: %v", name, out) + return out, nil +} + +type cutl_vector_t struct { + elem tÿpe +} + +func (t *cutl_vector_t) nü() value { + return &cutl_vector{t: t} +} + +func (t cutl_vector_t) typeName() string { + return fmt.Sprintf("CUtlVector<%s>", t.elem.typeName()) +} + +type cutl_vector struct { + t *cutl_vector_t + slots []value +} + +func (v *cutl_vector) tÿpe() tÿpe { return v.t } + +func (v *cutl_vector) read(r bit.Reader) error { + bit.ReadVarInt32(r) // ?? + return r.Err() +} + +func (v cutl_vector) String() string { + if len(v.slots) > 8 { + return fmt.Sprintf("%s(%d)%v...", v.t.typeName(), len(v.slots), v.slots[:8]) + } + return fmt.Sprintf("%s(%d)%v", v.t.typeName(), len(v.slots), v.slots) +} + +func (v *cutl_vector) slotType(int) tÿpe { return v.t.elem } +func (v *cutl_vector) slotName(n int) string { return strconv.Itoa(n) } +func (v *cutl_vector) setSlotValue(slot int, val value) { + if slot == len(v.slots) { + v.slots = append(v.slots, val) + } else { + v.slots[slot] = val + } +} + +func (v *cutl_vector) getSlotValue(slot int) value { + if slot >= len(v.slots) { + return nil + } + return v.slots[slot] +} diff --git a/ent/handle.go b/ent/handle.go index cb58e21..167a53a 100644 --- a/ent/handle.go +++ b/ent/handle.go @@ -2,23 +2,38 @@ package ent import ( "fmt" + "github.com/jordanorelli/hyperstone/bit" ) -type Handle struct{ name string } +type handle_t string + +func (t handle_t) typeName() string { return string(t) } +func (t *handle_t) nü() value { return &handle{t: t} } + +// a handle represents a soft pointer to an entity. handles are represented by +// IDs and can cross the client-server divide. +type handle struct { + t tÿpe + id uint64 +} + +func (h handle) tÿpe() tÿpe { return h.t } +func (h *handle) read(r bit.Reader) error { + h.id = bit.ReadVarInt(r) + return r.Err() +} -func (h *Handle) Name() string { return h.name } -func (h *Handle) New(...interface{}) interface{} { return nil } -func (h *Handle) Slotted() bool { return false } +func (h handle) String() string { + return fmt.Sprintf("%s:%d", h.t.typeName(), h.id) +} -func (h *Handle) Read(br bit.Reader, d *Dict) (interface{}, error) { - id := int(bit.ReadVarInt(br)) - e, ok := d.hidx[id] - if !ok { - if br.Err() != nil { - return nil, br.Err() - } - return nil, fmt.Errorf("no entity found with handle %d", id) +func handleType(spec *typeSpec, env *Env) tÿpe { + if spec.typeName != "CGameSceneNodeHandle" { + return nil } - return e, br.Err() + + Debug.Printf(" handle type") + t := handle_t(spec.typeName) + return &t } diff --git a/ent/hseq.go b/ent/hseq.go new file mode 100644 index 0000000..e9d3760 --- /dev/null +++ b/ent/hseq.go @@ -0,0 +1,34 @@ +package ent + +import ( + "fmt" + + "github.com/jordanorelli/hyperstone/bit" +) + +var hseq_t = &typeLiteral{ + name: "HSequence", + newFn: func() value { + return new(hseq_v) + }, +} + +type hseq_v uint64 + +func (v hseq_v) tÿpe() tÿpe { return hseq_t } +func (v *hseq_v) read(r bit.Reader) error { + *v = hseq_v(bit.ReadVarInt(r) - 1) + return r.Err() +} + +func (v hseq_v) String() string { + return fmt.Sprintf("hseq:%d", uint64(v)) +} + +func hSeqType(spec *typeSpec, env *Env) tÿpe { + if spec.typeName != "HSequence" { + return nil + } + Debug.Printf(" hsequence type") + return hseq_t +} diff --git a/ent/huff.go b/ent/huff.go index c66d74c..6d24ab6 100644 --- a/ent/huff.go +++ b/ent/huff.go @@ -134,7 +134,9 @@ var hlist = nodeList{ r.add(int(bit.ReadUBitVarFP(br)) + 5) }}, lNode{"PushOneLeftDeltaOneRightNonZero", 8, 2942, func(r *selectionReader, br bit.Reader) { - panic("not implemented: PushOneLeftDeltaOneRightNonZero") + Debug.Printf("PushOneLeftDeltaOneRightNonZero") + r.add(1) + r.push(int(bit.ReadUBitVarFP(br))) }}, lNode{"PopAllButOnePlusOne", 29, 1837, func(r *selectionReader, br bit.Reader) { r.pop(-1) @@ -266,4 +268,4 @@ var hlist = nodeList{ }}, } -var htree = makeTree(hlist) +var huffRoot = makeTree(hlist) diff --git a/ent/huff_test.go b/ent/huff_test.go index e40ce5d..d85af9f 100644 --- a/ent/huff_test.go +++ b/ent/huff_test.go @@ -66,5 +66,5 @@ func TestTree(t *testing.T) { } } - testWalk(htree, "") + testWalk(huffRoot, "") } diff --git a/ent/log.go b/ent/log.go index 106312e..4898e4a 100644 --- a/ent/log.go +++ b/ent/log.go @@ -1,6 +1,7 @@ package ent import ( + "fmt" "io/ioutil" "log" "os" @@ -10,3 +11,7 @@ var ( Debug = log.New(ioutil.Discard, "DEBUG ent: ", 0) Info = log.New(os.Stdout, "INFO end: ", 0) ) + +func wrap(err error, t string, args ...interface{}) error { + return fmt.Errorf(t+": %v", append(args, err)...) +} diff --git a/ent/misc.go b/ent/misc.go new file mode 100644 index 0000000..8c08273 --- /dev/null +++ b/ent/misc.go @@ -0,0 +1,64 @@ +package ent + +import ( + "bytes" + "fmt" + + "github.com/golang/protobuf/proto" + + "github.com/jordanorelli/hyperstone/bit" + "github.com/jordanorelli/hyperstone/dota" +) + +func getSerializers(m *dota.CDemoSendTables) (*dota.CSVCMsg_FlattenedSerializer, error) { + data := m.GetData() + r := bit.NewBytesReader(data) + + size := int(bit.ReadVarInt(r)) + buf := make([]byte, size) + + r.Read(buf) + if r.Err() != nil { + return nil, wrap(r.Err(), "error reading serializers body") + } + + flat := dota.CSVCMsg_FlattenedSerializer{} + if err := proto.Unmarshal(buf, &flat); err != nil { + return nil, wrap(err, "error unmarshaling serializers body") + } + return &flat, nil +} + +func prettyFlatField(flat *dota.ProtoFlattenedSerializerFieldT, env *Env) string { + var_name := env.symbol(int(flat.GetVarNameSym())) + var_type := env.symbol(int(flat.GetVarTypeSym())) + + var pretty bytes.Buffer + fmt.Fprintf(&pretty, "{name: %s type: %s", var_name, var_type) + if flat.BitCount != nil { + fmt.Fprintf(&pretty, " bits: %d", flat.GetBitCount()) + } + if flat.LowValue != nil { + fmt.Fprintf(&pretty, " low: %f", flat.GetLowValue()) + } + if flat.HighValue != nil { + fmt.Fprintf(&pretty, " high: %f", flat.GetHighValue()) + } + if flat.EncodeFlags != nil { + fmt.Fprintf(&pretty, " flags: %d", flat.GetEncodeFlags()) + } + if flat.FieldSerializerNameSym != nil { + fmt.Fprintf(&pretty, " serializer: %s", env.symbol(int(flat.GetFieldSerializerNameSym()))) + } + if flat.FieldSerializerVersion != nil { + fmt.Fprintf(&pretty, " s_version: %d", flat.GetFieldSerializerVersion()) + } + if flat.SendNodeSym != nil { + fmt.Fprintf(&pretty, " send: %s", env.symbol(int(flat.GetSendNodeSym()))) + } + if flat.VarEncoderSym != nil { + fmt.Fprintf(&pretty, " var_enc: %s", env.symbol(int(flat.GetVarEncoderSym()))) + } + fmt.Fprint(&pretty, "}") + return pretty.String() +} diff --git a/ent/namespace.go b/ent/namespace.go deleted file mode 100644 index cc44c64..0000000 --- a/ent/namespace.go +++ /dev/null @@ -1,246 +0,0 @@ -package ent - -import ( - "fmt" - "math" - - "github.com/golang/protobuf/proto" - - "github.com/jordanorelli/hyperstone/bit" - "github.com/jordanorelli/hyperstone/dota" -) - -// Namespace registers the names of known classess, associating their ids to -// their network types. -type Namespace struct { - SymbolTable - idBits int - - // maps ClassInfo ids to class names - classIds map[int]string - - // maps classes to their {name, version} id pairs - classes map[classId]*Class - - // maps a class name to every version of the class - classesByName map[string]map[int]*Class -} - -// Merges in the ClassInfo data found in the replay protobufs -func (n *Namespace) mergeClassInfo(ci *dota.CDemoClassInfo) { - if n.classIds == nil { - n.classIds = make(map[int]string, len(ci.GetClasses())) - } - for _, class := range ci.GetClasses() { - n.classIds[int(class.GetClassId())] = class.GetNetworkName() - } - n.idBits = int(math.Floor(math.Log2(float64(len(n.classIds))))) + 1 -} - -func (n *Namespace) hasClassinfo() bool { - return n.classIds != nil && len(n.classIds) > 0 -} - -// merges the send table data found in the replay protobufs. The send table -// data contains a specification for an entity type system. -func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) error { - // sendtables only has one field, a binary data field. - Debug.Printf("merge send tables") - data := st.GetData() - br := bit.NewBytesReader(data) - - // body is length-prefixed - size := int(bit.ReadVarInt(br)) - - buf := make([]byte, size) - br.Read(buf) - - flat := dota.CSVCMsg_FlattenedSerializer{} - if err := proto.Unmarshal(buf, &flat); err != nil { - return fmt.Errorf("unable to merge send tables: %v", err) - } - - n.SymbolTable = SymbolTable(flat.GetSymbols()) - - // the full set of fields that may appear on the classes is read first. - // each class will have a list of fields. - fields := make([]Field, len(flat.GetFields())) - for i, f := range flat.GetFields() { - fields[i].fromProto(f, &n.SymbolTable) - } - - n.classes = make(map[classId]*Class, len(flat.GetSerializers())) - n.classesByName = make(map[string]map[int]*Class, len(flat.GetSerializers())) - - // each serializer in the source data generates a class. - for _, c := range flat.GetSerializers() { - name := n.Symbol(int(c.GetSerializerNameSym())) - version := int(c.GetSerializerVersion()) - - Debug.Printf("new class: %s %v", name, version) - class := Class{name: name, Version: version} - class.fromProto(c, fields) - - id := classId{name: name, version: version} - n.classes[id] = &class - - if n.classesByName[name.String()] == nil { - n.classesByName[name.String()] = map[int]*Class{version: &class} - } else { - n.classesByName[name.String()][version] = &class - } - } - - for i := range fields { - f := &fields[i] - if f.serializer != nil { - if f.serializerVersion != nil { - f.class = n.classesByName[f.serializer.String()][int(*f.serializerVersion)] - } else { - f.class = n.NewestClass(f.serializer.String()) - } - } - - // for some fields, we want to initialize a zero value on the baseline - // instance. specifically, we do this for arrays so that we can't try - // to index into nil. - f.typeSpec = parseTypeName(n, f._type.String()) - if f.isContainer() { - mf := f.memberField() - fn := newFieldDecoder(n, mf) - if f.typeSpec.kind == t_array { - f.initializer = func() interface{} { - return &array{ - slots: make([]interface{}, f.typeSpec.size), - _slotType: mf._type.String(), - decoder: fn, - } - } - } else if f.typeSpec.kind == t_template && f.typeSpec.template == "CUtlVector" { - f.initializer = func() interface{} { - return &cutlVector{ - slots: make([]interface{}, f.typeSpec.size), - _slotType: mf._type.String(), - decoder: fn, - } - } - } - } - - // we also wait until after we've discovered all of the classes to - // build the field decoder functions, because some fields are - // themselves entities. - f.decoder = newFieldDecoder(n, f) - } - - for _, ff := range flat.GetFields() { - t, err := parseType(n, ff) - if err != nil { - Debug.Printf(" parseType error: %v", err) - } else { - Debug.Printf(" parseType type: %v", t) - } - } - - return br.Err() -} - -/* -func (n *Namespace) newMergeSendTables(st *dota.CDemoSendTables) error { - // sendtables only has one field, a binary data field. It's not immediately - // clear why this message exists at all when it could simply contain - // CSVMsg_FlattenedSerializer. - data := st.GetData() - br := bit.NewBytesReader(data) - // body is length-prefixed - size := int(bit.ReadVarInt(br)) - buf := make([]byte, size) - br.Read(buf) - flat := dota.CSVCMsg_FlattenedSerializer{} - if err := proto.Unmarshal(buf, &flat); err != nil { - return fmt.Errorf("unable to merge send tables: %v", err) - } - return n.mergeSerializers(&flat) -} - -func (n *Namespace) mergeSerializers(flat *dota.CSVCMsg_FlattenedSerializer) error { - // most of the names and associated strings are interned in a symbol table. - // We'll need these first since they're referenced throughought the class - // and field definitions. - n.SymbolTable = SymbolTable(flat.GetSymbols()) - - n.classes = make(map[classId]*Class, len(flat.GetSerializers())) - n.classesByName = make(map[string]map[int]*Class, len(flat.GetSerializers())) - - // some fields refer to classes, but classes are collections of fields. - // Their references are potentially cyclical. We start by creating empty - // classes so that fields may point to them. - for _, s := range flat.GetSerializers() { - name_sym := n.Symbol(int(s.GetSerializerNameSym())) - ver := int(s.GetSerializerVersion()) - class := Class{name: name_sym, Version: ver} - n.classes[class.Id()] = &class - if n.classesByName[class.Name()] == nil { - n.classesByName[class.Name()] = make(map[int]*Class) - } - n.classesByName[class.Name()][ver] = &class - } - - // type ProtoFlattenedSerializerFieldT struct { - // VarTypeSym *int32 - // VarNameSym *int32 - // VarEncoderSym *int32 - // FieldSerializerNameSym *int32 - // FieldSerializerVersion *int32 - // BitCount *int32 - // LowValue *float32 - // HighValue *float32 - // EncodeFlags *int32 - // SendNodeSym *int32 - // } - - // next we parse all of the fields along with their type definitions - fields := make(map[int]Field, len(flat.GetFields())) - for id, flatField := range flat.GetFields() { - t := parseType(n, flatField) - fields[id].Name = n.Symbol(int(flatField.GetVarNameSym())).String() - } - os.Exit(1) - return nil -} -*/ - -func (n *Namespace) readClassId(r bit.Reader) int { - return int(r.ReadBits(uint(n.idBits))) -} - -func (n *Namespace) Class(name string, version int) *Class { - return n.classesByName[name][version] -} - -// retrieves the newest version of a class, as referenced by name. -func (n *Namespace) NewestClass(name string) *Class { - versions, newest := n.classesByName[name], -1 - for v, _ := range versions { - if v > newest { - newest = v - } - } - if newest == -1 { - Info.Fatalf("class %s has no known versions in its version map", name) - } - return versions[newest] -} - -func (n *Namespace) ClassByNetId(id int) *Class { - name, ok := n.classIds[id] - if !ok { - Info.Fatalf("can't find class name for net id %d", id) - } - return n.NewestClass(name) -} - -func (n *Namespace) HasClass(name string) bool { - _, ok := n.classesByName[name] - return ok -} diff --git a/ent/primitive.go b/ent/primitive.go deleted file mode 100644 index ac87d02..0000000 --- a/ent/primitive.go +++ /dev/null @@ -1,106 +0,0 @@ -package ent - -import ( - "github.com/jordanorelli/hyperstone/bit" -) - -var primitives map[string]Primitive - -// a Primitive type in the ent object system. -type Primitive struct { - name string - read decodeFn - alloc func() interface{} -} - -func (p *Primitive) New(args ...interface{}) interface{} { - if p.alloc != nil { - return p.alloc() - } - return nil -} -func (p *Primitive) Name() string { return p.name } -func (p *Primitive) Slotted() bool { return false } - -func (p *Primitive) Read(br bit.Reader, d *Dict) (interface{}, error) { - return p.read(br, d) -} - -func init() { - types := []Primitive{ - {name: "bool", read: readBool}, - {name: "uint8", read: readUint8}, - {name: "uint16", read: readUint16}, - {name: "uint32", read: readUint32}, - {name: "uint64", read: readUint64}, - {name: "int8", read: readInt8}, - {name: "int16", read: readInt16}, - {name: "int32", read: readInt32}, - {name: "int64", read: readInt64}, - {name: "Color", read: readColor}, - {name: "HSequence", read: readIntMinusOne}, - } - primitives = make(map[string]Primitive, len(types)) - for _, t := range types { - primitives[t.name] = t - } -} - -func readBool(br bit.Reader, d *Dict) (interface{}, error) { - return bit.ReadBool(br), br.Err() -} - -// ------------------------------------------------------------------------------ -// uints -// ------------------------------------------------------------------------------ - -func readUint8(br bit.Reader, d *Dict) (interface{}, error) { - return uint8(br.ReadBits(8)), br.Err() -} - -func readUint16(br bit.Reader, d *Dict) (interface{}, error) { - return uint16(bit.ReadVarInt(br)), br.Err() -} - -func readUint32(br bit.Reader, d *Dict) (interface{}, error) { - return uint32(bit.ReadVarInt(br)), br.Err() -} - -func readUint64(br bit.Reader, d *Dict) (interface{}, error) { - return bit.ReadVarInt(br), br.Err() -} - -// ------------------------------------------------------------------------------ -// ints -// ------------------------------------------------------------------------------ - -func readInt8(br bit.Reader, d *Dict) (interface{}, error) { - return int8(bit.ReadZigZag(br)), br.Err() -} - -func readInt16(br bit.Reader, d *Dict) (interface{}, error) { - return int16(bit.ReadZigZag(br)), br.Err() -} - -func readInt32(br bit.Reader, d *Dict) (interface{}, error) { - return int32(bit.ReadZigZag(br)), br.Err() -} - -func readInt64(br bit.Reader, d *Dict) (interface{}, error) { - return bit.ReadZigZag(br), br.Err() -} - -func readColor(br bit.Reader, d *Dict) (interface{}, error) { - u := bit.ReadVarInt(br) - return color{ - r: uint8(u & 0xff000000), - g: uint8(u & 0x00ff0000), - b: uint8(u & 0x0000ff00), - a: uint8(u & 0x000000ff), - }, br.Err() -} - -// what in the good fuck is this -func readIntMinusOne(br bit.Reader, d *Dict) (interface{}, error) { - return bit.ReadVarInt(br) - 1, br.Err() -} diff --git a/ent/qangle.go b/ent/qangle.go index 9e34dcf..a960c9f 100644 --- a/ent/qangle.go +++ b/ent/qangle.go @@ -5,70 +5,101 @@ import ( "math" "github.com/jordanorelli/hyperstone/bit" - "github.com/jordanorelli/hyperstone/dota" ) -func parseQAngleType(n *Namespace, flat *dota.ProtoFlattenedSerializerFieldT) (Type, error) { - type_name := n.Symbol(int(flat.GetVarTypeSym())).String() - if flat.VarEncoderSym != nil { - encoder := n.Symbol(int(flat.GetVarEncoderSym())).String() - switch encoder { - case "qangle_pitch_yaw": - return nil, fmt.Errorf("that qangle pitch yaw thing isn't done yet") - default: - return nil, fmt.Errorf("unknown qangle encoder: %s", encoder) - } +var qangle_t = &typeLiteral{ + name: "qangle", + newFn: func() value { + return new(qangle) + }, +} + +type qangle struct{ pitch, yaw, roll float32 } + +func (q qangle) tÿpe() tÿpe { return qangle_t } +func (q *qangle) read(r bit.Reader) error { + pitch, yaw, roll := bit.ReadBool(r), bit.ReadBool(r), bit.ReadBool(r) + if pitch { + q.pitch = bit.ReadCoord(r) + } + if yaw { + q.yaw = bit.ReadCoord(r) + } + if roll { + q.roll = bit.ReadCoord(r) } - if flat.BitCount == nil { - return nil, fmt.Errorf("dunno what to do when qangle type has no bitcount") + return r.Err() +} + +func (q qangle) String() string { + return fmt.Sprintf("qangle{%f %f %f}", q.pitch, q.yaw, q.roll) +} + +func qAngleType(spec *typeSpec, env *Env) tÿpe { + if spec.typeName != "QAngle" { + return nil } - if flat.GetBitCount() < 0 { - return nil, fmt.Errorf("negative bit count wtf") + if spec.encoder == "qangle_pitch_yaw" { + switch { + case spec.bits <= 0 || spec.bits > 32: + return typeError("qangle pitch_yaw has invalid bit size: %d", spec.bits) + case spec.bits == 32: + return pitchYaw_t + default: + t := pitchYawAngles_t(spec.bits) + return &t + } } - bits := uint(flat.GetBitCount()) - switch bits { + switch spec.bits { case 0: - return &Primitive{name: type_name, read: readQAngleCoords}, nil + Debug.Printf(" qangle type") + return qangle_t case 32: - return &Primitive{name: type_name, read: readQAngleFloats}, nil + return nil default: - return &Primitive{name: type_name, read: qangleReader(bits)}, nil + return nil } } -func qangleReader(bits uint) decodeFn { - return func(br bit.Reader, d *Dict) (interface{}, error) { - return &vector{ - x: bit.ReadAngle(br, bits), - y: bit.ReadAngle(br, bits), - z: bit.ReadAngle(br, bits), - }, br.Err() - } +var pitchYaw_t = &typeLiteral{ + name: "qangle:pitchYaw", + newFn: func() value { + return new(pitchYaw_v) + }, } -func readQAngleCoords(br bit.Reader, d *Dict) (interface{}, error) { - var ( - v vector - x = bit.ReadBool(br) - y = bit.ReadBool(br) - z = bit.ReadBool(br) - ) - if x { - v.x = bit.ReadCoord(br) - } - if y { - v.y = bit.ReadCoord(br) - } - if z { - v.z = bit.ReadCoord(br) - } - return v, br.Err() +type pitchYaw_v qangle + +func (v pitchYaw_v) tÿpe() tÿpe { return pitchYaw_t } +func (v *pitchYaw_v) read(r bit.Reader) error { + v.pitch = math.Float32frombits(uint32(r.ReadBits(32))) + v.yaw = math.Float32frombits(uint32(r.ReadBits(32))) + return r.Err() +} + +func (v pitchYaw_v) String() string { + return fmt.Sprintf("qangle:pitchYaw{%f %f}", v.pitch, v.yaw) +} + +type pitchYawAngles_t uint + +func (t pitchYawAngles_t) typeName() string { return "qangle:pitchYawAngles" } +func (t *pitchYawAngles_t) nü() value { + return &pitchYawAngles_v{t: t} +} + +type pitchYawAngles_v struct { + t *pitchYawAngles_t + qangle +} + +func (v pitchYawAngles_v) tÿpe() tÿpe { return v.t } +func (v *pitchYawAngles_v) read(r bit.Reader) error { + v.pitch = bit.ReadAngle(r, uint(*v.t)) + v.yaw = bit.ReadAngle(r, uint(*v.t)) + return r.Err() } -func readQAngleFloats(br bit.Reader, d *Dict) (interface{}, error) { - return &vector{ - x: math.Float32frombits(uint32(br.ReadBits(32))), - y: math.Float32frombits(uint32(br.ReadBits(32))), - z: math.Float32frombits(uint32(br.ReadBits(32))), - }, br.Err() +func (v pitchYawAngles_v) String() string { + return fmt.Sprintf("qangle:pitchYawAngles{%f %f}", v.pitch, v.yaw) } diff --git a/ent/selection.go b/ent/selection.go index 4a4a4ec..c868d5d 100644 --- a/ent/selection.go +++ b/ent/selection.go @@ -19,36 +19,37 @@ type selection struct { func (s selection) String() string { return fmt.Sprint(s.path()) } func (s selection) path() []int { return s.vals[:s.count] } -func (s selection) fill(offset int, displayPath string, dest slotted, br bit.Reader) error { +func (s selection) fillSlots(v slotted, r bit.Reader) error { + return s.fillSlotsIter(0, v, v.tÿpe().typeName(), r) +} + +func (s selection) fillSlotsIter(offset int, dest slotted, path string, r bit.Reader) error { slot := s.vals[offset] if s.count-offset <= 0 { - panic("selection makes no sense") + return fmt.Errorf("unable to fill selection %v having count %d at offset %d", s, s.count, offset) } switch s.count - offset { case 1: - fn := dest.slotDecoder(slot) - if fn == nil { - switch dest.(type) { - case *Entity: - Debug.Printf("%s %s (%s)", s, fmt.Sprintf("%s.%s", displayPath, dest.slotName(slot)), dest.slotType(slot)) - return nil - default: - Info.Printf("slotted value %v has no decoder for slot %d", dest, slot) - return nil - } + old := dest.getSlotValue(slot) + v := dest.slotType(slot).nü() + Debug.Printf("%v %s.%s (%s) %v read", s, path, dest.slotName(slot), dest.slotType(slot).typeName(), old) + if err := v.read(r); err != nil { + return fmt.Errorf("unable to fill selection %v for %s.%s (%s): %v", s, path, dest.slotName(slot), dest.slotType(slot).typeName(), err) } - old := dest.slotValue(slot) - val := fn(br) - dest.setSlotValue(slot, val) - Debug.Printf("%s %s (%s): %v -> %v", s, fmt.Sprintf("%s.%s", displayPath, dest.slotName(slot)), dest.slotType(slot), old, val) + dest.setSlotValue(slot, v) + Debug.Printf("%v %s.%s (%s) %v -> %v", s, path, dest.slotName(slot), dest.slotType(slot).typeName(), old, v) return nil default: - v := dest.slotValue(slot) + v := dest.getSlotValue(slot) + if v == nil { + v = dest.slotType(slot).nü() + dest.setSlotValue(slot, v) + } vs, ok := v.(slotted) if !ok { - return fmt.Errorf("selection %s at offset %d (%d) refers to a slot (%s) that contains a non-slotted type (%s) with value %v", s, offset, slot, fmt.Sprintf("%s.%s", displayPath, dest.slotName(slot)), dest.slotType(slot), v) + return fmt.Errorf("dest %s (%s) isn't slotted", dest.slotName(slot), dest.slotType(slot).typeName()) } - return s.fill(offset+1, fmt.Sprintf("%s.%s", displayPath, dest.slotName(slot)), vs, br) + return s.fillSlotsIter(offset+1, vs, fmt.Sprintf("%s.%s", path, dest.slotName(slot)), r) } } @@ -67,11 +68,11 @@ type selectionReader struct { all [1024]selection } -func (r *selectionReader) readSelections(br bit.Reader, n node) ([]selection, error) { +func (r *selectionReader) readSelections(br bit.Reader) ([]selection, error) { r.cur.count = 1 r.cur.vals[0] = -1 r.count = 0 - for fn := walk(n, br); fn != nil; fn = walk(n, br) { + for fn := walk(huffRoot, br); fn != nil; fn = walk(huffRoot, br) { if err := br.Err(); err != nil { return nil, fmt.Errorf("unable to read selection: bit reader error: %v", err) } diff --git a/ent/slotted.go b/ent/slotted.go index ecf53db..cb1a088 100644 --- a/ent/slotted.go +++ b/ent/slotted.go @@ -1,28 +1,9 @@ package ent -import ( - "fmt" - "github.com/jordanorelli/hyperstone/bit" -) - type slotted interface { + value + slotType(int) tÿpe slotName(int) string - slotValue(int) interface{} - slotType(int) string - slotDecoder(int) decoder - setSlotValue(int, interface{}) -} - -func fillSlots(dest slotted, displayPath string, sr *selectionReader, br bit.Reader) error { - selections, err := sr.readSelections(br, htree) - if err != nil { - return fmt.Errorf("error filling slots: %v", err) - } - - for _, s := range selections { - if err := s.fill(0, displayPath, dest, br); err != nil { - return fmt.Errorf("error filling slots: %v", err) - } - } - return nil + setSlotValue(int, value) + getSlotValue(int) value } diff --git a/ent/symbol.go b/ent/symbol.go index 726293c..0bb1fe3 100644 --- a/ent/symbol.go +++ b/ent/symbol.go @@ -1,17 +1,10 @@ package ent -// the internal representation of table data refers to all labels as -// interned strings (symbols). This array of string contains the mapping of -// symbol ids to symbol display representations. The sample replay I have -// at the time of writing this contains 2215 symbols in its symbol table. -// The dota replay format uses an ordered list of symbols. -type SymbolTable []string - -func (t *SymbolTable) Symbol(id int) Symbol { return Symbol{id: id, table: t} } - -type Symbol struct { +type symbol struct { id int - table *SymbolTable + table *symbolTable } -func (s Symbol) String() string { return (*s.table)[s.id] } +func (s symbol) String() string { return (*s.table)[s.id] } + +type symbolTable []string diff --git a/ent/type.go b/ent/type.go index ffc9dcd..3060680 100644 --- a/ent/type.go +++ b/ent/type.go @@ -1,99 +1,121 @@ package ent import ( - "bytes" "fmt" + "github.com/jordanorelli/hyperstone/bit" "github.com/jordanorelli/hyperstone/dota" ) -type decodeFn func(bit.Reader, *Dict) (interface{}, error) +type tÿpe interface { + nü() value + typeName() string +} -// a Type in the entity type system. Note that not every type is necessarily an -// entity type, since there are necessarily primitives, and above that, arrays -// and generics. -type Type interface { - // creates a new value of the given type. - New(...interface{}) interface{} +type typeLiteral struct { + name string + newFn func() value +} - // name is primarily of interest for debugging - Name() string +func (t typeLiteral) nü() value { return t.newFn() } +func (t typeLiteral) typeName() string { return t.name } - // whether or not the produced values are expected to be slotted. - Slotted() bool +type typeParseFn func(*typeSpec, *Env) tÿpe - // reads a value of this type off of the bit reader - Read(bit.Reader, *Dict) (interface{}, error) +func parseFieldType(flat *dota.ProtoFlattenedSerializerFieldT, env *Env) tÿpe { + spec := new(typeSpec) + spec.fromProto(flat, env) + return parseTypeSpec(spec, env) } -func parseType(n *Namespace, flat *dota.ProtoFlattenedSerializerFieldT) (Type, error) { - Debug.Printf("parseType: %s", prettyFlatField(n, flat)) - type_name := n.Symbol(int(flat.GetVarTypeSym())).String() - - if prim, ok := primitives[type_name]; ok { - Debug.Printf(" parseType: found primitive with name %s", type_name) - return &prim, nil +func parseTypeSpec(spec *typeSpec, env *Env) tÿpe { + Debug.Printf(" parse spec: %v", spec) + coalesce := func(fns ...typeParseFn) tÿpe { + for _, fn := range fns { + if t := fn(spec, env); t != nil { + return t + } + } + return nil } + return coalesce(arrayType, atomType, floatType, handleType, qAngleType, + hSeqType, genericType, vectorType, classType, unknownType) +} - if n.HasClass(type_name) { - Debug.Printf(" parseType: found class with name %s", type_name) - return nil, nil - } +type unknown_t string - switch type_name { - case "float32", "CNetworkedQuantizedFloat": - Debug.Printf(" parseType: parsing as float type") - return parseFloatType(n, flat) - case "CGameSceneNodeHandle": - return &Handle{name: "CGameSceneNodeHandle"}, nil - case "QAngle": - return parseQAngleType(n, flat) - } +func (t unknown_t) typeName() string { return string(t) } +func (t *unknown_t) nü() value { + return &unknown_v{t: t} +} - Debug.Printf(" parseType: failed") - // type ProtoFlattenedSerializerFieldT struct { - // VarTypeSym *int32 - // VarNameSym *int32 - // VarEncoderSym *int32 - // FieldSerializerNameSym *int32 - // FieldSerializerVersion *int32 - // BitCount *int32 - // LowValue *float32 - // HighValue *float32 - // EncodeFlags *int32 - // SendNodeSym *int32 - // } - return nil, nil +type unknown_v struct { + t tÿpe + v uint64 } -func prettyFlatField(n *Namespace, ff *dota.ProtoFlattenedSerializerFieldT) string { - var buf bytes.Buffer - fmt.Fprintf(&buf, "{type: %s", n.Symbol(int(ff.GetVarTypeSym()))) - fmt.Fprintf(&buf, " name: %s", n.Symbol(int(ff.GetVarNameSym()))) - if ff.BitCount != nil { - fmt.Fprintf(&buf, " bits: %d", ff.GetBitCount()) - } - if ff.LowValue != nil { - fmt.Fprintf(&buf, " low: %f", ff.GetLowValue()) - } - if ff.HighValue != nil { - fmt.Fprintf(&buf, " high: %f", ff.GetHighValue()) - } - if ff.EncodeFlags != nil { - fmt.Fprintf(&buf, " flags: %d", ff.GetEncodeFlags()) - } - if ff.FieldSerializerNameSym != nil { - fmt.Fprintf(&buf, " serializer: %s", n.Symbol(int(ff.GetFieldSerializerNameSym()))) +func (v unknown_v) tÿpe() tÿpe { return v.t } +func (v *unknown_v) read(r bit.Reader) error { + v.v = bit.ReadVarInt(r) + return r.Err() +} + +func (v unknown_v) String() string { + return fmt.Sprintf("%s(unknown):%d", v.t.typeName(), v.v) +} + +func unknownType(spec *typeSpec, env *Env) tÿpe { + Debug.Printf("Unknown Type: %v", spec) + t := unknown_t(spec.typeName) + return &t +} + +// a type error is both an error and a type. It represents a type that we were +// unable to correctly parse. It can be interpreted as an error or as a type; +// when interpreted as a type, it errors every time it tries to read a value. +func typeError(t string, args ...interface{}) tÿpe { + Debug.Printf(" type error: %s", fmt.Sprintf(t, args...)) + return error_t(fmt.Sprintf(t, args...)) +} + +type error_t string + +func (e error_t) nü() value { panic("can't create an error val like that") } +func (e error_t) typeName() string { return "error" } +func (e error_t) Error() string { return string(e) } + +type typeSpec struct { + name string + typeName string + bits uint + low float32 + high float32 + flags int + serializer string + serializerV int + send string + encoder string +} + +func (s *typeSpec) fromProto(flat *dota.ProtoFlattenedSerializerFieldT, env *Env) { + s.name = env.symbol(int(flat.GetVarNameSym())) + s.typeName = env.symbol(int(flat.GetVarTypeSym())) + if flat.GetBitCount() < 0 { + // this would cause ridiculously long reads later if we let it overflow + panic("negative bit count: data is likely corrupt") } - if ff.FieldSerializerVersion != nil { - fmt.Fprintf(&buf, " version: %d", ff.GetFieldSerializerVersion()) + s.bits = uint(flat.GetBitCount()) + s.low = flat.GetLowValue() + s.high = flat.GetHighValue() + s.flags = int(flat.GetEncodeFlags()) + if flat.FieldSerializerNameSym != nil { + s.serializer = env.symbol(int(*flat.FieldSerializerNameSym)) } - if ff.SendNodeSym != nil { - fmt.Fprintf(&buf, " send: %s", n.Symbol(int(ff.GetSendNodeSym()))) + s.serializerV = int(flat.GetFieldSerializerVersion()) + if flat.SendNodeSym != nil { + s.send = env.symbol(int(*flat.SendNodeSym)) } - if ff.VarEncoderSym != nil { - fmt.Fprintf(&buf, " encoder: %s", n.Symbol(int(ff.GetVarEncoderSym()))) + if flat.VarEncoderSym != nil { + s.encoder = env.symbol(int(*flat.VarEncoderSym)) } - fmt.Fprintf(&buf, "}") - return buf.String() } diff --git a/ent/type_spec.go b/ent/type_spec.go deleted file mode 100644 index a099d2d..0000000 --- a/ent/type_spec.go +++ /dev/null @@ -1,120 +0,0 @@ -package ent - -import ( - "fmt" - "strconv" - "strings" -) - -const ( - t_element = iota // a known element class - t_object // c++ object type that is not a known element class - t_array // c++ array type - t_template // c++ template type - t_pointer // c++ pointer -) - -// constant identifiers -var cIdents = map[string]int{"MAX_ABILITY_DRAFT_ABILITIES": 48} - -type typeSpec struct { - name string - kind int - size int - template string - member *typeSpec -} - -func (t typeSpec) String() string { - if t.member != nil { - return fmt.Sprintf("{%v %v %v %v %v}", t.name, t.kind, t.size, t.template, *t.member) - } - return fmt.Sprintf("{%v %v %v %v %v}", t.name, t.kind, t.size, t.template, t.member) -} - -func parseTypeName(n *Namespace, s string) typeSpec { - s = strings.TrimSpace(s) - t := typeSpec{name: s} - - if n.HasClass(s) { - t.kind = t_element - return t - } - - if strings.HasSuffix(s, "*") { - t.kind = t_pointer - t.member = new(typeSpec) - *t.member = parseTypeName(n, s[:len(s)-2]) - return t - } - - // presumably this is some sort of array type - if strings.ContainsRune(s, '[') { - memName, count := parseArrayName(s) - t.kind = t_array - t.size = count - t.member = new(typeSpec) - *t.member = parseTypeName(n, memName) - return t - } - - if strings.ContainsRune(s, '<') { - t.kind = t_template - template, member := parseTemplateName(s) - t.template = template - t.member = new(typeSpec) - *t.member = parseTypeName(n, member) - return t - } - - t.kind = t_object - return t -} - -func parseArrayName(s string) (string, int) { - runes := []rune(s) - if runes[len(runes)-1] != ']' { - panic("invalid array type name: " + s) - } - for i := len(runes) - 2; i >= 0; i-- { - if runes[i] == '[' { - ns := strings.TrimSpace(string(runes[i+1 : len(runes)-1])) - n, err := strconv.Atoi(ns) - if err != nil { - n = cIdents[ns] - if n <= 0 { - panic("invalid array type name: " + err.Error()) - } - } - return strings.TrimSpace(string(runes[:i])), n - } - } - panic("invalid array type name: " + s) -} - -func parseTemplateName(s string) (string, string) { - if strings.ContainsRune(s, ',') { - panic("can't handle templates with multiple parameters") - } - - runes := []rune(s) - template := "" - depth := 0 - bracket_start := -1 - for i, r := range runes { - if r == '<' { - if depth == 0 { - bracket_start = i - template = strings.TrimSpace(string(runes[:i])) - } - depth++ - } - if r == '>' { - depth-- - if depth == 0 { - return template, strings.TrimSpace(string(runes[bracket_start+1 : i])) - } - } - } - panic("invalid template type definition: " + s) -} diff --git a/ent/value.go b/ent/value.go new file mode 100644 index 0000000..b03be93 --- /dev/null +++ b/ent/value.go @@ -0,0 +1,11 @@ +package ent + +import ( + "github.com/jordanorelli/hyperstone/bit" +) + +type value interface { + String() string + tÿpe() tÿpe + read(bit.Reader) error +} diff --git a/ent/vector.go b/ent/vector.go index 1f11c96..52b8964 100644 --- a/ent/vector.go +++ b/ent/vector.go @@ -1,7 +1,67 @@ package ent +import ( + "fmt" + "github.com/jordanorelli/hyperstone/bit" +) + +func vectorType(spec *typeSpec, env *Env) tÿpe { + if spec.encoder != "" { + return nil + } + t := floatType(spec, env) + if _, ok := t.(error); ok { + return t + } + if t == nil { + return nil + } + return &vector_t{elem: t} +} + +type vector_t struct { + elem tÿpe +} + +func (t vector_t) typeName() string { + return fmt.Sprintf("vector<%s>", t.elem.typeName()) +} + +func (t *vector_t) nü() value { + return &vector{t: t} +} + type vector struct { - x float32 - y float32 - z float32 + t tÿpe + x, y, z value +} + +func (v vector) tÿpe() tÿpe { return v.t } + +func (v *vector) read(r bit.Reader) error { + if v.x == nil { + v.x = v.t.nü() + } + if v.y == nil { + v.y = v.t.nü() + } + if v.z == nil { + v.z = v.t.nü() + } + + type fn func(bit.Reader) error + coalesce := func(fns ...fn) error { + for _, f := range fns { + if err := f(r); err != nil { + return err + } + } + return nil + } + + return coalesce(v.x.read, v.y.read, v.z.read) +} + +func (v vector) String() string { + return fmt.Sprintf("vector<%s>{%s %s %s}", v.t.typeName, v.x, v.y, v.z) } diff --git a/entity.go b/entity.go deleted file mode 100644 index 7cdb0e3..0000000 --- a/entity.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "fmt" -) - -type entity struct { - t entityType - size uint32 - body []byte -} - -func (e entity) String() string { - if len(e.body) < 30 { - return fmt.Sprintf("{entity type: %s size: %d data: %x}", e.t, e.size, e.body) - } - return fmt.Sprintf("{entity type: %s size: %d data: %x...}", e.t, e.size, e.body[:27]) -} diff --git a/main.go b/main.go index 5b32148..17e4c5f 100644 --- a/main.go +++ b/main.go @@ -142,11 +142,11 @@ func main() { handle = dumpClasses case "entities": ent.Debug = log.New(os.Stdout, "", 0) - sd := stbl.NewDict() - ed := ent.NewDict(sd) + env := ent.NewEnv() handle = func(m proto.Message) { - sd.Handle(m) - ed.Handle(m) + if err := env.Handle(m); err != nil { + bail(1, "%v", err) + } } default: bail(1, "no such action: %s", flag.Arg(0)) diff --git a/stbl/table.go b/stbl/table.go index 7a76024..c6c5fea 100644 --- a/stbl/table.go +++ b/stbl/table.go @@ -61,7 +61,7 @@ func (t *Table) createEntries(br *bit.BufReader, n int) error { } } } - return br.Err() + return nil } func (t *Table) updateEntries(br *bit.BufReader, n int) error {