1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-11 18:20:42 +00:00
OpenDiablo2/d2common/d2fileformats/d2tbl/text_dictionary.go
2021-02-02 10:08:32 +01:00

271 lines
5.3 KiB
Go

package d2tbl
import (
"errors"
"sort"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
// TextDictionary is a string map
type TextDictionary struct {
crcBytes []byte
elementIndex []uint16
hashTableSize uint32
version byte
stringOffset uint32
unknown1 uint32
fileSize uint32
hashEntries []*textDictionaryHashEntry
Entries map[string]string
}
func (td *TextDictionary) loadHashEntries(br *d2datautils.StreamReader) error {
var err error
for i := 0; i < len(td.hashEntries); i++ {
entry := textDictionaryHashEntry{}
entry.active, err = br.ReadByte()
if err != nil {
return err
}
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
}
td.hashEntries[i] = &entry
}
for idx := range td.hashEntries {
if !td.hashEntries[idx].IsActive() {
continue
}
if err := td.loadHashEntry(idx, td.hashEntries[idx], br); err != nil {
return err
}
}
return nil
}
func (td *TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEntry, br *d2datautils.StreamReader) error {
var err error
br.SetPosition(uint64(hashEntry.NameString))
nameVal, err := br.ReadBytes(int(hashEntry.NameLength - 1))
if err != nil {
return err
}
hashEntry.name = 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)
}
hashEntry.key = key
if hashEntry.key == "x" || hashEntry.key == "X" {
key = "#" + strconv.Itoa(idx)
}
_, exists := td.Entries[key]
if !exists {
td.Entries[key] = hashEntry.name
}
return nil
}
type textDictionaryHashEntry struct {
name string
key string
HashValue uint32
IndexString uint32
NameString uint32
Index uint16
NameLength uint16
active byte
}
func (t *textDictionaryHashEntry) IsActive() bool {
return t.active > 0
}
const (
crcByteCount = 2
)
// LoadTextDictionary loads the text dictionary from the given data
func LoadTextDictionary(dictionaryData []byte) (*TextDictionary, error) {
var err error
lookupTable := &TextDictionary{
Entries: make(map[string]string),
}
br := d2datautils.CreateStreamReader(dictionaryData)
// skip past the CRC
lookupTable.crcBytes, err = br.ReadBytes(crcByteCount)
if err != nil {
return nil, err
}
numberOfElements, err := br.ReadUInt16()
if err != nil {
return nil, err
}
lookupTable.hashTableSize, err = br.ReadUInt32()
if err != nil {
return nil, err
}
// Version (always 0)
if lookupTable.version, err = br.ReadByte(); err != nil {
return nil, errors.New("error reading Version record")
}
// StringOffset
lookupTable.stringOffset, err = br.ReadUInt32()
if err != nil {
return nil, errors.New("error reading string offset")
}
// 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.
lookupTable.unknown1, err = br.ReadUInt32()
if err != nil {
return nil, err
}
// FileSize
lookupTable.fileSize, err = br.ReadUInt32()
if err != nil {
return nil, err
}
elementIndex := make([]uint16, numberOfElements)
for i := 0; i < int(numberOfElements); i++ {
elementIndex[i], err = br.ReadUInt16()
if err != nil {
return nil, err
}
}
lookupTable.elementIndex = elementIndex
lookupTable.hashEntries = make([]*textDictionaryHashEntry, lookupTable.hashTableSize)
err = lookupTable.loadHashEntries(br)
if err != nil {
return nil, err
}
return lookupTable, nil
}
// Marshal encodes text dictionary back to byte slice
func (td *TextDictionary) Marshal() []byte {
// create stream writter
sw := d2datautils.CreateStreamWriter()
sw.PushBytes(td.crcBytes...)
sw.PushUint16(uint16(len(td.elementIndex)))
sw.PushUint32(td.hashTableSize)
sw.PushBytes(td.version)
sw.PushUint32(td.stringOffset)
sw.PushUint32(td.unknown1)
sw.PushUint32(td.fileSize)
for _, i := range td.elementIndex {
sw.PushUint16(i)
}
for i := 0; i < len(td.hashEntries); i++ {
sw.PushBytes(td.hashEntries[i].active)
sw.PushUint16(td.hashEntries[i].Index)
sw.PushUint32(td.hashEntries[i].HashValue)
sw.PushUint32(td.hashEntries[i].IndexString)
sw.PushUint32(td.hashEntries[i].NameString)
sw.PushUint16(td.hashEntries[i].NameLength)
}
// values are table entries data (key & values)
var values map[int]string = make(map[int]string)
// valuesSorted are sorted values
var valuesSorted map[int]string = make(map[int]string)
// add values key / names to map
for _, i := range td.hashEntries {
values[int(i.IndexString)] = i.key
values[int(i.NameString)] = i.name
}
// added map keys
keys := make([]int, 0, len(values))
for k := range values {
keys = append(keys, k)
}
// sort keys
sort.Ints(keys)
// create sorted values map
for _, k := range keys {
valuesSorted[k] = values[k]
}
// add first value (without 0-byte separator)
sw.PushBytes([]byte(valuesSorted[keys[0]])...)
// adds values to result
for i := 1; i < len(valuesSorted); i++ {
sw.PushBytes([]byte(valuesSorted[keys[i]])...)
sw.PushBytes(0)
}
return sw.GetBytes()
}