1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-30 11:05:23 +00:00

Merge branch 'master' into master

This commit is contained in:
Tim Sarbin 2021-02-08 12:37:46 -05:00 committed by GitHub
commit a85a7a18c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 232 additions and 33 deletions

View File

@ -0,0 +1,95 @@
package d2datautils
import (
"testing"
)
func getTestData() []byte {
result := []byte{33, 23, 4, 33, 192, 243}
return result
}
func TestBitmuncherCopy(t *testing.T) {
bm1 := CreateBitMuncher(getTestData(), 0)
bm2 := CopyBitMuncher(bm1)
for i := range bm1.data {
if bm1.data[i] != bm2.data[i] {
t.Fatal("original bitmuncher isn't equal to copied")
}
}
}
func TestBitmuncherSetOffset(t *testing.T) {
bm := CreateBitMuncher(getTestData(), 0)
bm.SetOffset(5)
if bm.Offset() != 5 {
t.Fatal("Set Offset method didn't set offset to expected number")
}
}
func TestBitmuncherSteBitsRead(t *testing.T) {
bm := CreateBitMuncher(getTestData(), 0)
bm.SetBitsRead(8)
if bm.BitsRead() != 8 {
t.Fatal("Set bits read method didn't set bits read to expected value")
}
}
func TestBitmuncherReadBit(t *testing.T) {
td := getTestData()
bm := CreateBitMuncher(td, 0)
var result byte
for i := 0; i < bitsPerByte; i++ {
v := bm.GetBit()
result |= byte(v) << byte(i)
}
if result != td[0] {
t.Fatal("result of rpeated 8 times get bit didn't return expected byte")
}
}
func TestBitmuncherGetBits(t *testing.T) {
td := getTestData()
bm := CreateBitMuncher(td, 0)
res := bm.GetBits(bitsPerByte)
if byte(res) != td[0] {
t.Fatal("get bits didn't return expected value")
}
}
func TestBitmuncherSkipBits(t *testing.T) {
td := getTestData()
bm := CreateBitMuncher(td, 0)
bm.SkipBits(bitsPerByte)
if bm.GetByte() != td[1] {
t.Fatal("skipping 8 bits didn't moved bit muncher's position into next byte")
}
}
func TestBitmuncherGetInt32(t *testing.T) {
td := getTestData()
bm := CreateBitMuncher(td, 0)
var testInt int32
for i := 0; i < bytesPerint32; i++ {
testInt |= int32(td[i]) << int32(bitsPerByte*i)
}
bmInt := bm.GetInt32()
if bmInt != testInt {
t.Fatal("int32 value wasn't returned properly")
}
}

View File

@ -2,25 +2,36 @@ package d2tbl
import ( import (
"errors" "errors"
"sort"
"strconv" "strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
) )
// TextDictionary is a string map // TextDictionary is a string map
type TextDictionary map[string]string 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(hashEntries []*textDictionaryHashEntry, br *d2datautils.StreamReader) error { func (td *TextDictionary) loadHashEntries(br *d2datautils.StreamReader) error {
for i := 0; i < len(hashEntries); i++ { var err error
for i := 0; i < len(td.hashEntries); i++ {
entry := textDictionaryHashEntry{} entry := textDictionaryHashEntry{}
active, err := br.ReadByte() entry.active, err = br.ReadByte()
if err != nil { if err != nil {
return err return err
} }
entry.IsActive = active > 0
entry.Index, err = br.ReadUInt16() entry.Index, err = br.ReadUInt16()
if err != nil { if err != nil {
return err return err
@ -46,15 +57,15 @@ func (td TextDictionary) loadHashEntries(hashEntries []*textDictionaryHashEntry,
return err return err
} }
hashEntries[i] = &entry td.hashEntries[i] = &entry
} }
for idx := range hashEntries { for idx := range td.hashEntries {
if !hashEntries[idx].IsActive { if !td.hashEntries[idx].IsActive() {
continue continue
} }
if err := td.loadHashEntry(idx, hashEntries[idx], br); err != nil { if err := td.loadHashEntry(idx, td.hashEntries[idx], br); err != nil {
return err return err
} }
} }
@ -62,7 +73,9 @@ func (td TextDictionary) loadHashEntries(hashEntries []*textDictionaryHashEntry,
return nil return nil
} }
func (td TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEntry, br *d2datautils.StreamReader) error { func (td *TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEntry, br *d2datautils.StreamReader) error {
var err error
br.SetPosition(uint64(hashEntry.NameString)) br.SetPosition(uint64(hashEntry.NameString))
nameVal, err := br.ReadBytes(int(hashEntry.NameLength - 1)) nameVal, err := br.ReadBytes(int(hashEntry.NameLength - 1))
@ -70,7 +83,7 @@ func (td TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEnt
return err return err
} }
value := string(nameVal) hashEntry.name = string(nameVal)
br.SetPosition(uint64(hashEntry.IndexString)) br.SetPosition(uint64(hashEntry.IndexString))
@ -89,25 +102,33 @@ func (td TextDictionary) loadHashEntry(idx int, hashEntry *textDictionaryHashEnt
key += string(b) key += string(b)
} }
if key == "x" || key == "X" { hashEntry.key = key
if hashEntry.key == "x" || hashEntry.key == "X" {
key = "#" + strconv.Itoa(idx) key = "#" + strconv.Itoa(idx)
} }
_, exists := td[key] _, exists := td.Entries[key]
if !exists { if !exists {
td[key] = value td.Entries[key] = hashEntry.name
} }
return nil return nil
} }
type textDictionaryHashEntry struct { type textDictionaryHashEntry struct {
IsActive bool name string
Index uint16 key string
HashValue uint32 HashValue uint32
IndexString uint32 IndexString uint32
NameString uint32 NameString uint32
Index uint16
NameLength uint16 NameLength uint16
active byte
}
func (t *textDictionaryHashEntry) IsActive() bool {
return t.active > 0
} }
const ( const (
@ -115,38 +136,54 @@ const (
) )
// LoadTextDictionary loads the text dictionary from the given data // LoadTextDictionary loads the text dictionary from the given data
func LoadTextDictionary(dictionaryData []byte) (TextDictionary, error) { func LoadTextDictionary(dictionaryData []byte) (*TextDictionary, error) {
lookupTable := make(TextDictionary) var err error
lookupTable := &TextDictionary{
Entries: make(map[string]string),
}
br := d2datautils.CreateStreamReader(dictionaryData) br := d2datautils.CreateStreamReader(dictionaryData)
// skip past the CRC // skip past the CRC
_, _ = br.ReadBytes(crcByteCount) lookupTable.crcBytes, err = br.ReadBytes(crcByteCount)
if err != nil {
var err error return nil, err
}
numberOfElements, err := br.ReadUInt16() numberOfElements, err := br.ReadUInt16()
if err != nil { if err != nil {
return nil, err return nil, err
} }
hashTableSize, err := br.ReadUInt32() lookupTable.hashTableSize, err = br.ReadUInt32()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Version (always 0) // Version (always 0)
if _, err = br.ReadByte(); err != nil { if lookupTable.version, err = br.ReadByte(); err != nil {
return nil, errors.New("error reading Version record") return nil, errors.New("error reading Version record")
} }
_, _ = br.ReadUInt32() // StringOffset // 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 // 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. // hash key equals this value, you give up because it is not there.
_, _ = br.ReadUInt32() lookupTable.unknown1, err = br.ReadUInt32()
if err != nil {
return nil, err
}
_, _ = br.ReadUInt32() // FileSize // FileSize
lookupTable.fileSize, err = br.ReadUInt32()
if err != nil {
return nil, err
}
elementIndex := make([]uint16, numberOfElements) elementIndex := make([]uint16, numberOfElements)
for i := 0; i < int(numberOfElements); i++ { for i := 0; i < int(numberOfElements); i++ {
@ -156,12 +193,78 @@ func LoadTextDictionary(dictionaryData []byte) (TextDictionary, error) {
} }
} }
hashEntries := make([]*textDictionaryHashEntry, hashTableSize) lookupTable.elementIndex = elementIndex
err = lookupTable.loadHashEntries(hashEntries, br) lookupTable.hashEntries = make([]*textDictionaryHashEntry, lookupTable.hashTableSize)
err = lookupTable.loadHashEntries(br)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return lookupTable, nil 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()
}

View File

@ -64,7 +64,8 @@ const (
type AssetManager struct { type AssetManager struct {
*d2util.Logger *d2util.Logger
*d2loader.Loader *d2loader.Loader
tables []d2tbl.TextDictionary
tables []*d2tbl.TextDictionary
dt1s d2interface.Cache dt1s d2interface.Cache
ds1s d2interface.Cache ds1s d2interface.Cache
cofs d2interface.Cache cofs d2interface.Cache
@ -270,7 +271,7 @@ func (am *AssetManager) LoadPalette(palettePath string) (d2interface.Palette, er
} }
// LoadStringTable loads a string table from the given path // LoadStringTable loads a string table from the given path
func (am *AssetManager) LoadStringTable(tablePath string) (d2tbl.TextDictionary, error) { func (am *AssetManager) LoadStringTable(tablePath string) (*d2tbl.TextDictionary, error) {
data, err := am.LoadFile(tablePath) data, err := am.LoadFile(tablePath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -304,7 +305,7 @@ func (am *AssetManager) TranslateString(input interface{}) string {
} }
for idx := range am.tables { for idx := range am.tables {
if value, found := am.tables[idx][key]; found { if value, found := am.tables[idx].Entries[key]; found {
return value return value
} }
} }

View File

@ -27,7 +27,7 @@ func NewAssetManager(logLevel d2util.LogLevel) (*AssetManager, error) {
manager := &AssetManager{ manager := &AssetManager{
Logger: logger, Logger: logger,
Loader: loader, Loader: loader,
tables: make([]d2tbl.TextDictionary, 0), tables: make([]*d2tbl.TextDictionary, 0),
animations: d2cache.CreateCache(animationBudget), animations: d2cache.CreateCache(animationBudget),
fonts: d2cache.CreateCache(fontBudget), fonts: d2cache.CreateCache(fontBudget),
palettes: d2cache.CreateCache(paletteBudget), palettes: d2cache.CreateCache(paletteBudget),