OpenDiablo2/d2common/d2fileformats/d2tbl/text_dictionary.go

168 lines
3.0 KiB
Go

package d2tbl
import (
"errors"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
// TextDictionary is a string map
type TextDictionary map[string]string
func (td TextDictionary) loadHashEntries(hashEntries []*textDictionaryHashEntry, br *d2datautils.StreamReader) error {
for i := 0; i < len(hashEntries); i++ {
entry := textDictionaryHashEntry{}
active, err := br.ReadByte()
if err != nil {
return err
}
entry.IsActive = active > 0
entry.Index, err = br.ReadUInt16()
if err != nil {
return err
}
entry.HashValue, err = br.ReadUInt32()
if err != nil {
return err
}
entry.IndexString, err = br.ReadUInt32()
if err != nil {
return err
}
entry.NameString, err = br.ReadUInt32()
if err != nil {
return err
}
entry.NameLength, err = br.ReadUInt16()
if err != nil {
return err
}
hashEntries[i] = &entry
}
for idx := range hashEntries {
if !hashEntries[idx].IsActive {
continue
}
if err := td.loadHashEntry(idx, hashEntries[idx], br); err != nil {
return err
}
}
return nil
}
func (td TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEntry, br *d2datautils.StreamReader) error {
br.SetPosition(uint64(hashEntry.NameString))
nameVal, err := br.ReadBytes(int(hashEntry.NameLength - 1))
if err != nil {
return err
}
value := string(nameVal)
br.SetPosition(uint64(hashEntry.IndexString))
key := ""
for {
b, err := br.ReadByte()
if b == 0 {
break
}
if err != nil {
return err
}
key += string(b)
}
if key == "x" || key == "X" {
key = "#" + strconv.Itoa(idx)
}
_, exists := td[key]
if !exists {
td[key] = value
}
return nil
}
type textDictionaryHashEntry struct {
IsActive bool
Index uint16
HashValue uint32
IndexString uint32
NameString uint32
NameLength uint16
}
const (
crcByteCount = 2
)
// LoadTextDictionary loads the text dictionary from the given data
func LoadTextDictionary(dictionaryData []byte) (TextDictionary, error) {
lookupTable := make(TextDictionary)
br := d2datautils.CreateStreamReader(dictionaryData)
// skip past the CRC
_, _ = br.ReadBytes(crcByteCount)
var err error
numberOfElements, err := br.ReadUInt16()
if err != nil {
return nil, err
}
hashTableSize, err := br.ReadUInt32()
if err != nil {
return nil, err
}
// Version (always 0)
if _, err = br.ReadByte(); err != nil {
return nil, errors.New("error reading Version record")
}
_, _ = br.ReadUInt32() // StringOffset
// When the number of times you have missed a match with a
// hash key equals this value, you give up because it is not there.
_, _ = br.ReadUInt32()
_, _ = br.ReadUInt32() // FileSize
elementIndex := make([]uint16, numberOfElements)
for i := 0; i < int(numberOfElements); i++ {
elementIndex[i], err = br.ReadUInt16()
if err != nil {
return nil, err
}
}
hashEntries := make([]*textDictionaryHashEntry, hashTableSize)
err = lookupTable.loadHashEntries(hashEntries, br)
if err != nil {
return nil, err
}
return lookupTable, nil
}