baselines go to the right classes

but i can't decode any of them yet, natch
master
Jordan Orelli 8 years ago
parent bd3387b605
commit 4b98518812

@ -10,6 +10,9 @@ type Class struct {
Name Symbol
Version int
Fields []*Field
// all other entities for this class use this instance as a prototype
baseline *Entity
}
func (c *Class) New() *Entity {

@ -2,13 +2,29 @@ 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
//
@ -20,16 +36,24 @@ import (
// be used on the client.
type Dict struct {
*Namespace
entities map[int]Entity
entities []Entity
br *bit.BufReader
// 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() *Dict {
return &Dict{
func NewDict(sd *stbl.Dict) *Dict {
d := &Dict{
Namespace: new(Namespace),
entities: make(map[int]Entity),
entities: make([]Entity, e_limit),
br: new(bit.BufReader),
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
@ -52,18 +76,36 @@ func (d *Dict) createEntity(id int) error {
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)
}
e.Read(d.br)
return nil
}
func (d *Dict) deleteEntity(id int) error {
Debug.Printf("delete entity id: %d\n", id)
if id < 0 || id >= e_limit {
return fmt.Errorf("delete entity %d refused: no such entity", id)
}
d.entities[id] = Entity{}
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
}
@ -71,9 +113,11 @@ 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:
d.mergeEntities(v)
@ -113,3 +157,38 @@ func (d *Dict) mergeEntities(m *dota.CSVCMsg_PacketEntities) error {
}
return nil
}
func (d *Dict) updateBaselines(t *stbl.Table) {
d.base = t
d.syncBaselines()
}
func (d *Dict) syncBaselines() {
Debug.Printf("syncBaselines start")
if d.base == nil {
Debug.Printf("syncBaselines failed: reference to baseline string table is nil")
return
}
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()
}
d.br.SetSource(e.Value)
Debug.Printf("syncBaselines has new baseline for class %v", c)
c.baseline.Read(d.br)
}
}

@ -10,6 +10,10 @@ type Entity struct {
*Class
}
func (e *Entity) Read(br bit.Reader) {
fmt.Printf("Entity %v read\n", e)
func (e *Entity) Read(br bit.Reader) error {
if e.Class == nil {
return fmt.Errorf("unable to read entity: entity has no class")
}
Debug.Printf("Entity %v read", e)
return nil
}

@ -39,8 +39,9 @@ func (n *Namespace) mergeClassInfo(ci *dota.CDemoClassInfo) {
// 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) {
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)
@ -52,8 +53,7 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) {
flat := dota.CSVCMsg_FlattenedSerializer{}
if err := proto.Unmarshal(buf, &flat); err != nil {
fmt.Printf("error: %v\n", err)
return
return fmt.Errorf("unable to merge send tables: %v", err)
}
n.SymbolTable = SymbolTable(flat.GetSymbols())
@ -67,12 +67,14 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) {
n.classesByName = make(map[string]map[int]*Class, len(flat.GetSerializers()))
for _, c := range flat.GetSerializers() {
class := Class{}
class.fromProto(c, fields)
name := n.Symbol(int(c.GetSerializerNameSym()))
class.Name = name
version := int(c.GetSerializerVersion())
class := Class{Name: name, Version: version}
class.fromProto(c, fields)
Debug.Printf("new class: %v", class)
id := classId{name: name, version: version}
n.classes[id] = &class
@ -82,6 +84,8 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) {
n.classesByName[name.String()][version] = &class
}
}
return br.Err()
}
func (n *Namespace) readClassId(r bit.Reader) int {
@ -91,3 +95,22 @@ func (n *Namespace) readClassId(r bit.Reader) int {
func (n *Namespace) Class(name string, version int) *Class {
return n.classesByName[name][version]
}
func (n *Namespace) ClassByNetId(id int) *Class {
name, ok := n.classIds[id]
if !ok {
Debug.Printf("can't find class name for net id %d", id)
return nil
}
versions, newest := n.classesByName[name], -1
for v, _ := range versions {
if v > newest {
newest = v
}
}
if newest == -1 {
Debug.Printf("class %s has no known versions in its version map", name)
return nil
}
return versions[newest]
}

@ -142,8 +142,12 @@ func main() {
handle = dumpClasses
case "entities":
ent.Debug = log.New(os.Stdout, "", 0)
d := ent.NewDict()
handle = d.Handle
sd := stbl.NewDict()
ed := ent.NewDict(sd)
handle = func(m proto.Message) {
sd.Handle(m)
ed.Handle(m)
}
default:
bail(1, "no such action: %s", flag.Arg(0))
}

@ -17,6 +17,7 @@ type Dict struct {
byName map[string]*Table
br *bit.BufReader
scratch []byte
observers map[string][]func(*Table)
}
func NewDict() *Dict {
@ -25,6 +26,7 @@ func NewDict() *Dict {
byName: make(map[string]*Table, 64),
br: new(bit.BufReader),
scratch: make([]byte, 1<<16),
observers: make(map[string][]func(*Table)),
}
}
@ -41,6 +43,7 @@ func (d *Dict) newTable(name string) *Table {
// retained in the dict, but a pointer to the table is also returned in case
// the newly-created table is of use to the caller.
func (d *Dict) Create(m *dota.CSVCMsg_CreateStringTable) (*Table, error) {
defer d.notifyObservers(m.GetName())
Debug.Printf("create table %s", m.GetName())
t := d.newTable(m.GetName())
@ -83,6 +86,7 @@ func (d *Dict) Update(m *dota.CSVCMsg_UpdateStringTable) error {
if t == nil {
return fmt.Errorf("no known string table for id %d", m.GetTableId())
}
defer d.notifyObservers(t.name)
d.br.SetSource(m.GetStringData())
return t.updateEntries(d.br, int(m.GetNumChangedEntries()))
@ -112,3 +116,16 @@ func (d *Dict) Handle(m proto.Message) {
Debug.Println("ignoring a full stringtable dump")
}
}
func (d *Dict) WatchTable(name string, fn func(*Table)) {
if d.observers[name] == nil {
d.observers[name] = make([]func(*Table), 0, 8)
}
d.observers[name] = append(d.observers[name], fn)
}
func (d *Dict) notifyObservers(name string) {
for _, fn := range d.observers[name] {
fn(d.TableForName(name))
}
}

@ -8,21 +8,21 @@ import (
// Entry represents a single record in a string table. It's not called "Record"
// because it's called "Entry" in the protobufs.
type Entry struct {
key string
value []byte
Key string
Value []byte
}
func (e Entry) String() string {
if e.value == nil {
return fmt.Sprintf("{%s nil}", e.key)
if e.Value == nil {
return fmt.Sprintf("{%s nil}", e.Key)
}
if utf8.Valid(e.value) {
return fmt.Sprintf("{%s %s}", e.key, e.value)
if utf8.Valid(e.Value) {
return fmt.Sprintf("{%s %s}", e.Key, e.Value)
}
if len(e.value) > 32 {
return fmt.Sprintf("{%s 0x%x}", e.key, e.value[:32])
if len(e.Value) > 32 {
return fmt.Sprintf("{%s 0x%x}", e.Key, e.Value[:32])
}
return fmt.Sprintf("{%s 0x%x}", e.key, e.value)
return fmt.Sprintf("{%s 0x%x}", e.Key, e.Value)
}

@ -10,9 +10,13 @@ type Table struct {
name string
entries []Entry
byteSize int
bitSize int // this is in the protobuf message but I don't know what it does.
// this is in the protobuf message but I don't know what it does.
bitSize int
}
func (t *Table) Entries() []Entry { return t.entries }
// creates n entries from the bit stream br
func (t *Table) createEntries(br *bit.BufReader, n int) error {
Debug.Printf("table %s create %d entries", t.name, n)
@ -38,22 +42,22 @@ func (t *Table) createEntries(br *bit.BufReader, n int) error {
// backreading flag: indicates that the key references an earlier
// key or a portion of an earlier key as a prefix
if bit.ReadBool(br) {
entry.key = t.entries[base+br.ReadBits(5)].key[:br.ReadBits(5)] + bit.ReadString(br)
entry.Key = t.entries[base+br.ReadBits(5)].Key[:br.ReadBits(5)] + bit.ReadString(br)
} else {
entry.key = bit.ReadString(br)
entry.Key = bit.ReadString(br)
}
}
// value flag: indicates that a value is present
if bit.ReadBool(br) {
if t.byteSize != 0 {
entry.value = make([]byte, t.byteSize)
br.Read(entry.value)
entry.Value = make([]byte, t.byteSize)
br.Read(entry.Value)
} else {
size := br.ReadBits(14)
br.ReadBits(3) // ???
entry.value = make([]byte, size)
br.Read(entry.value)
entry.Value = make([]byte, size)
br.Read(entry.Value)
}
}
}
@ -91,32 +95,32 @@ func (t *Table) updateEntries(br *bit.BufReader, n int) error {
prev, pLen := h.at(int(br.ReadBits(5))), int(br.ReadBits(5))
if prev < len(t.entries) {
prevEntry := &t.entries[prev]
entry.key = prevEntry.key[:pLen] + bit.ReadString(br)
entry.Key = prevEntry.Key[:pLen] + bit.ReadString(br)
} else {
return fmt.Errorf("backread error")
}
} else {
entry.key = bit.ReadString(br)
entry.Key = bit.ReadString(br)
}
}
// value flag
if bit.ReadBool(br) {
if t.byteSize != 0 {
if entry.value == nil {
entry.value = make([]byte, t.byteSize)
if entry.Value == nil {
entry.Value = make([]byte, t.byteSize)
}
} else {
size, _ := int(br.ReadBits(14)), br.ReadBits(3)
if len(entry.value) < size {
entry.value = make([]byte, size)
if len(entry.Value) < size {
entry.Value = make([]byte, size)
} else {
entry.value = entry.value[:size]
entry.Value = entry.Value[:size]
}
}
br.Read(entry.value)
br.Read(entry.Value)
}
Debug.Printf("%s:%s = %x", t.name, entry.key, entry.value)
Debug.Printf("%s:%s = %x", t.name, entry.Key, entry.Value)
}
return nil
}

Loading…
Cancel
Save