You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
3.4 KiB
Go

package stbl
import (
"fmt"
"github.com/golang/protobuf/proto"
"github.com/golang/snappy"
"github.com/jordanorelli/hyperstone/bit"
"github.com/jordanorelli/hyperstone/dota"
)
// Dict represents a dictionary of string tables. Each table may be referenced
// by either a numeric ID or its name.
type Dict struct {
byId []Table
byName map[string]*Table
br *bit.BufReader
scratch []byte
observers map[string][]func(*Table)
}
func NewDict() *Dict {
return &Dict{
byId: make([]Table, 0, 64),
byName: make(map[string]*Table, 64),
br: new(bit.BufReader),
scratch: make([]byte, 1<<16),
observers: make(map[string][]func(*Table)),
}
}
// creates a new table, appending it to our list of tables and adding an entry
// in the table name index
func (d *Dict) newTable(name string) *Table {
d.byId = append(d.byId, Table{name: name})
t := &d.byId[len(d.byId)-1]
d.byName[name] = t
return t
}
// Creates a string table based on the provided protobuf message. The table is
// 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())
if m.GetUserDataFixedSize() {
t.byteSize = int(m.GetUserDataSize())
t.bitSize = int(m.GetUserDataSizeBits())
}
data := m.GetStringData()
if data == nil || len(data) == 0 {
Debug.Printf("table %s created as empty table", m.GetName())
return t, nil
}
if m.GetDataCompressed() {
switch string(data[:4]) {
case "LZSS":
return nil, fmt.Errorf("stbl: LZSS compression is not supported")
default:
var err error
data, err = snappy.Decode(d.scratch, data)
if err != nil {
return nil, fmt.Errorf("stbl: decode error: %v", err)
}
}
}
d.br.SetSource(data)
if err := t.createEntries(d.br, int(m.GetNumEntries())); err != nil {
return nil, err
}
return t, nil
}
// updates a string table in the dict based on the data found in a protobuf
// message
func (d *Dict) Update(m *dota.CSVCMsg_UpdateStringTable) error {
Debug.Printf("dict: update %d entries in table having id %d", m.GetNumChangedEntries(), m.GetTableId())
t := d.TableForId(int(m.GetTableId()))
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()))
}
func (d *Dict) TableForId(id int) *Table {
if id >= len(d.byId) {
Debug.Printf("bad dict access: id %d is greater than the max table id %d", id, len(d.byId)-1)
return nil
}
return &d.byId[id]
}
func (d *Dict) TableForName(name string) *Table {
return d.byName[name]
}
func (d *Dict) Handle(m proto.Message) {
switch v := m.(type) {
case *dota.CSVCMsg_CreateStringTable:
d.Create(v)
case *dota.CSVCMsg_UpdateStringTable:
d.Update(v)
case *dota.CSVCMsg_ClearAllStringTables:
Debug.Println("clear all string tables")
case *dota.CDemoStringTables:
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))
}
}