mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-16 17:35:57 -05:00
Merge branch 'master' into master
This commit is contained in:
commit
a85a7a18c1
95
d2common/d2datautils/bitmuncher_test.go
Normal file
95
d2common/d2datautils/bitmuncher_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user