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 Name Symbol
Version int Version int
Fields []*Field Fields []*Field
// all other entities for this class use this instance as a prototype
baseline *Entity
} }
func (c *Class) New() *Entity { func (c *Class) New() *Entity {

@ -2,13 +2,29 @@ package ent
import ( import (
"fmt" "fmt"
"strconv"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/jordanorelli/hyperstone/bit" "github.com/jordanorelli/hyperstone/bit"
"github.com/jordanorelli/hyperstone/dota" "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 // Dict corresponds to the edict_t in Valve's documentation for the Source
// engine. See here: https://developer.valvesoftware.com/wiki/Edict_t // engine. See here: https://developer.valvesoftware.com/wiki/Edict_t
// //
@ -20,16 +36,24 @@ import (
// be used on the client. // be used on the client.
type Dict struct { type Dict struct {
*Namespace *Namespace
entities map[int]Entity entities []Entity
br *bit.BufReader 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 { func NewDict(sd *stbl.Dict) *Dict {
return &Dict{ d := &Dict{
Namespace: new(Namespace), Namespace: new(Namespace),
entities: make(map[int]Entity), entities: make([]Entity, e_limit),
br: new(bit.BufReader), 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 // 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 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 { func (d *Dict) updateEntity(id int) error {
Debug.Printf("update entity id: %d\n", id) 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 return nil
} }
func (d *Dict) deleteEntity(id int) error { func (d *Dict) deleteEntity(id int) error {
Debug.Printf("delete entity id: %d\n", id) 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 return nil
} }
func (d *Dict) leaveEntity(id int) error { func (d *Dict) leaveEntity(id int) error {
Debug.Printf("leave entity id: %d\n", id) Debug.Printf("leave entity id: %d\n", id)
// what the shit does this do?
return nil return nil
} }
@ -71,9 +113,11 @@ func (d *Dict) Handle(m proto.Message) {
switch v := m.(type) { switch v := m.(type) {
case *dota.CDemoSendTables: case *dota.CDemoSendTables:
d.mergeSendTables(v) d.mergeSendTables(v)
d.syncBaselines()
case *dota.CDemoClassInfo: case *dota.CDemoClassInfo:
d.mergeClassInfo(v) d.mergeClassInfo(v)
d.syncBaselines()
case *dota.CSVCMsg_PacketEntities: case *dota.CSVCMsg_PacketEntities:
d.mergeEntities(v) d.mergeEntities(v)
@ -113,3 +157,38 @@ func (d *Dict) mergeEntities(m *dota.CSVCMsg_PacketEntities) error {
} }
return nil 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 *Class
} }
func (e *Entity) Read(br bit.Reader) { func (e *Entity) Read(br bit.Reader) error {
fmt.Printf("Entity %v read\n", e) 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 // merges the send table data found in the replay protobufs. The send table
// data contains a specification for an entity type system. // 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. // sendtables only has one field, a binary data field.
Debug.Printf("merge send tables")
data := st.GetData() data := st.GetData()
br := bit.NewBytesReader(data) br := bit.NewBytesReader(data)
@ -52,8 +53,7 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) {
flat := dota.CSVCMsg_FlattenedSerializer{} flat := dota.CSVCMsg_FlattenedSerializer{}
if err := proto.Unmarshal(buf, &flat); err != nil { if err := proto.Unmarshal(buf, &flat); err != nil {
fmt.Printf("error: %v\n", err) return fmt.Errorf("unable to merge send tables: %v", err)
return
} }
n.SymbolTable = SymbolTable(flat.GetSymbols()) 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())) n.classesByName = make(map[string]map[int]*Class, len(flat.GetSerializers()))
for _, c := range flat.GetSerializers() { for _, c := range flat.GetSerializers() {
class := Class{}
class.fromProto(c, fields)
name := n.Symbol(int(c.GetSerializerNameSym())) name := n.Symbol(int(c.GetSerializerNameSym()))
class.Name = name
version := int(c.GetSerializerVersion()) 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} id := classId{name: name, version: version}
n.classes[id] = &class n.classes[id] = &class
@ -82,6 +84,8 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) {
n.classesByName[name.String()][version] = &class n.classesByName[name.String()][version] = &class
} }
} }
return br.Err()
} }
func (n *Namespace) readClassId(r bit.Reader) int { 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 { func (n *Namespace) Class(name string, version int) *Class {
return n.classesByName[name][version] 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 handle = dumpClasses
case "entities": case "entities":
ent.Debug = log.New(os.Stdout, "", 0) ent.Debug = log.New(os.Stdout, "", 0)
d := ent.NewDict() sd := stbl.NewDict()
handle = d.Handle ed := ent.NewDict(sd)
handle = func(m proto.Message) {
sd.Handle(m)
ed.Handle(m)
}
default: default:
bail(1, "no such action: %s", flag.Arg(0)) bail(1, "no such action: %s", flag.Arg(0))
} }

@ -13,18 +13,20 @@ import (
// Dict represents a dictionary of string tables. Each table may be referenced // Dict represents a dictionary of string tables. Each table may be referenced
// by either a numeric ID or its name. // by either a numeric ID or its name.
type Dict struct { type Dict struct {
byId []Table byId []Table
byName map[string]*Table byName map[string]*Table
br *bit.BufReader br *bit.BufReader
scratch []byte scratch []byte
observers map[string][]func(*Table)
} }
func NewDict() *Dict { func NewDict() *Dict {
return &Dict{ return &Dict{
byId: make([]Table, 0, 64), byId: make([]Table, 0, 64),
byName: make(map[string]*Table, 64), byName: make(map[string]*Table, 64),
br: new(bit.BufReader), br: new(bit.BufReader),
scratch: make([]byte, 1<<16), 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 // 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. // the newly-created table is of use to the caller.
func (d *Dict) Create(m *dota.CSVCMsg_CreateStringTable) (*Table, error) { func (d *Dict) Create(m *dota.CSVCMsg_CreateStringTable) (*Table, error) {
defer d.notifyObservers(m.GetName())
Debug.Printf("create table %s", m.GetName()) Debug.Printf("create table %s", m.GetName())
t := d.newTable(m.GetName()) t := d.newTable(m.GetName())
@ -83,6 +86,7 @@ func (d *Dict) Update(m *dota.CSVCMsg_UpdateStringTable) error {
if t == nil { if t == nil {
return fmt.Errorf("no known string table for id %d", m.GetTableId()) return fmt.Errorf("no known string table for id %d", m.GetTableId())
} }
defer d.notifyObservers(t.name)
d.br.SetSource(m.GetStringData()) d.br.SetSource(m.GetStringData())
return t.updateEntries(d.br, int(m.GetNumChangedEntries())) 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") 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" // Entry represents a single record in a string table. It's not called "Record"
// because it's called "Entry" in the protobufs. // because it's called "Entry" in the protobufs.
type Entry struct { type Entry struct {
key string Key string
value []byte Value []byte
} }
func (e Entry) String() string { func (e Entry) String() string {
if e.value == nil { if e.Value == nil {
return fmt.Sprintf("{%s nil}", e.key) return fmt.Sprintf("{%s nil}", e.Key)
} }
if utf8.Valid(e.value) { if utf8.Valid(e.Value) {
return fmt.Sprintf("{%s %s}", e.key, e.value) return fmt.Sprintf("{%s %s}", e.Key, e.Value)
} }
if len(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[: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 name string
entries []Entry entries []Entry
byteSize int 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 // creates n entries from the bit stream br
func (t *Table) createEntries(br *bit.BufReader, n int) error { func (t *Table) createEntries(br *bit.BufReader, n int) error {
Debug.Printf("table %s create %d entries", t.name, n) 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 // backreading flag: indicates that the key references an earlier
// key or a portion of an earlier key as a prefix // key or a portion of an earlier key as a prefix
if bit.ReadBool(br) { 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 { } else {
entry.key = bit.ReadString(br) entry.Key = bit.ReadString(br)
} }
} }
// value flag: indicates that a value is present // value flag: indicates that a value is present
if bit.ReadBool(br) { if bit.ReadBool(br) {
if t.byteSize != 0 { if t.byteSize != 0 {
entry.value = make([]byte, t.byteSize) entry.Value = make([]byte, t.byteSize)
br.Read(entry.value) br.Read(entry.Value)
} else { } else {
size := br.ReadBits(14) size := br.ReadBits(14)
br.ReadBits(3) // ??? br.ReadBits(3) // ???
entry.value = make([]byte, size) entry.Value = make([]byte, size)
br.Read(entry.value) 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)) prev, pLen := h.at(int(br.ReadBits(5))), int(br.ReadBits(5))
if prev < len(t.entries) { if prev < len(t.entries) {
prevEntry := &t.entries[prev] prevEntry := &t.entries[prev]
entry.key = prevEntry.key[:pLen] + bit.ReadString(br) entry.Key = prevEntry.Key[:pLen] + bit.ReadString(br)
} else { } else {
return fmt.Errorf("backread error") return fmt.Errorf("backread error")
} }
} else { } else {
entry.key = bit.ReadString(br) entry.Key = bit.ReadString(br)
} }
} }
// value flag // value flag
if bit.ReadBool(br) { if bit.ReadBool(br) {
if t.byteSize != 0 { if t.byteSize != 0 {
if entry.value == nil { if entry.Value == nil {
entry.value = make([]byte, t.byteSize) entry.Value = make([]byte, t.byteSize)
} }
} else { } else {
size, _ := int(br.ReadBits(14)), br.ReadBits(3) size, _ := int(br.ReadBits(14)), br.ReadBits(3)
if len(entry.value) < size { if len(entry.Value) < size {
entry.value = make([]byte, size) entry.Value = make([]byte, size)
} else { } 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 return nil
} }

Loading…
Cancel
Save