Implements parsing LSB files enough to read all LSB files inside of BG3
This commit is contained in:
parent
969d36744c
commit
5de8626c72
355
lsb.go
355
lsb.go
@ -2,7 +2,10 @@ package lslib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/go-kit/kit/log"
|
"github.com/go-kit/kit/log"
|
||||||
)
|
)
|
||||||
@ -12,123 +15,326 @@ type LSBHeader struct {
|
|||||||
Size uint32
|
Size uint32
|
||||||
Endianness uint32
|
Endianness uint32
|
||||||
Unknown uint32
|
Unknown uint32
|
||||||
Version struct {
|
Version LSMetadata
|
||||||
Major uint32
|
|
||||||
Minor uint32
|
|
||||||
Build uint32
|
|
||||||
Revision uint32
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LSBRegion struct {
|
func (lsbh *LSBHeader) Read(r io.ReadSeeker) error {
|
||||||
name string
|
var (
|
||||||
offset uint32
|
l log.Logger
|
||||||
|
pos int64
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "header")
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
n, err = r.Read(lsbh.Signature[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Signature", "read", n, "start position", pos, "value", fmt.Sprintf("%#x", lsbh.Signature[:]))
|
||||||
|
pos += int64(n)
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Size)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Size", "read", n, "start position", pos, "value", lsbh.Size)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Endianness)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Endianness", "read", n, "start position", pos, "value", lsbh.Endianness)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Unknown)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Unknown", "read", n, "start position", pos, "value", lsbh.Unknown)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Version.Timestamp)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Version.Timestamp", "read", n, "start position", pos, "value", lsbh.Version.Timestamp)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Version.Major)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Version.Major", "read", n, "start position", pos, "value", lsbh.Version.Major)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Version.Minor)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Version.Minor", "read", n, "start position", pos, "value", lsbh.Version.Minor)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Version.Revision)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Version.Revision", "read", n, "start position", pos, "value", lsbh.Version.Revision)
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &lsbh.Version.Build)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.Log("member", "Version.Build", "read", n, "start position", pos, "value", lsbh.Version.Build)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type IdentifierDictionary map[int]string
|
type IdentifierDictionary map[int]string
|
||||||
|
|
||||||
|
func ReadLSB(r io.ReadSeeker) (Resource, error) {
|
||||||
|
var (
|
||||||
|
hdr = &LSBHeader{}
|
||||||
|
h = [4]byte{0x00, 0x00, 0x00, 0x40}
|
||||||
|
err error
|
||||||
|
d IdentifierDictionary
|
||||||
|
res Resource
|
||||||
|
|
||||||
|
l log.Logger
|
||||||
|
pos int64
|
||||||
|
)
|
||||||
|
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "file")
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
l.Log("member", "header", "start position", pos)
|
||||||
|
|
||||||
|
err = hdr.Read(r)
|
||||||
|
if err != nil {
|
||||||
|
return Resource{}, err
|
||||||
|
}
|
||||||
|
if !(hdr.Signature == [4]byte{'L', 'S', 'F', 'M'} || hdr.Signature == h) {
|
||||||
|
return Resource{}, HeaderError{
|
||||||
|
Expected: []byte("LSFM"),
|
||||||
|
Got: hdr.Signature[:],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
l.Log("member", "string dictionary", "start position", pos)
|
||||||
|
d, err = ReadLSBDictionary(r, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return Resource{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
l.Log("member", "Regions", "start position", pos)
|
||||||
|
|
||||||
|
res, err = ReadLSBRegions(r, d, binary.LittleEndian, FileVersion(hdr.Version.Major))
|
||||||
|
res.Metadata = hdr.Version
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
func ReadLSBDictionary(r io.ReadSeeker, endianness binary.ByteOrder) (IdentifierDictionary, error) {
|
func ReadLSBDictionary(r io.ReadSeeker, endianness binary.ByteOrder) (IdentifierDictionary, error) {
|
||||||
var (
|
var (
|
||||||
dict IdentifierDictionary
|
dict IdentifierDictionary
|
||||||
size uint32
|
length uint32
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
l log.Logger
|
||||||
|
pos int64
|
||||||
|
n int
|
||||||
)
|
)
|
||||||
err = binary.Read(r, endianness, &size)
|
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "dictionary")
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
err = binary.Read(r, endianness, &length)
|
||||||
|
n = 4
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dict = make(IdentifierDictionary, size)
|
l.Log("member", "length", "read", n, "start position", pos, "value", length)
|
||||||
for i := 0; i < int(size); i++ {
|
pos += int64(n)
|
||||||
|
|
||||||
|
dict = make(IdentifierDictionary, length)
|
||||||
|
for i := 0; i < int(length); i++ {
|
||||||
var (
|
var (
|
||||||
stringlength uint32
|
stringLength uint32
|
||||||
key uint32
|
key uint32
|
||||||
str string
|
str string
|
||||||
)
|
)
|
||||||
err = binary.Read(r, endianness, &stringlength)
|
err = binary.Read(r, endianness, &stringLength)
|
||||||
|
n = 4
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dict, err
|
return dict, err
|
||||||
}
|
}
|
||||||
str, err = ReadCString(r, int(stringlength))
|
l.Log("member", "stringLength", "read", n, "start position", pos, "value", stringLength)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
str, err = ReadCString(r, int(stringLength))
|
||||||
|
n += int(stringLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dict, err
|
return dict, err
|
||||||
}
|
}
|
||||||
|
l.Log("member", "str", "read", n, "start position", pos, "value", str)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
err = binary.Read(r, endianness, &key)
|
err = binary.Read(r, endianness, &key)
|
||||||
|
n = 4
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dict, err
|
return dict, err
|
||||||
}
|
}
|
||||||
dict[int(key)] = str
|
dict[int(key)] = str
|
||||||
|
l.Log("member", "key", "read", n, "start position", pos, "value", key)
|
||||||
|
pos += int64(n)
|
||||||
}
|
}
|
||||||
return dict, nil
|
return dict, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder) (Resource, error) {
|
func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version FileVersion) (Resource, error) {
|
||||||
var (
|
var (
|
||||||
nodes []struct {
|
regions []struct {
|
||||||
node *Node
|
name string
|
||||||
offset uint32
|
offset uint32
|
||||||
}
|
}
|
||||||
nodeCount uint32
|
regionCount uint32
|
||||||
err error
|
err error
|
||||||
)
|
|
||||||
|
|
||||||
err = binary.Read(r, endianness, &nodeCount)
|
l log.Logger
|
||||||
|
pos int64
|
||||||
|
n int
|
||||||
|
)
|
||||||
|
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "region")
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
err = binary.Read(r, endianness, ®ionCount)
|
||||||
|
n = 4
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Resource{}, err
|
return Resource{}, err
|
||||||
}
|
}
|
||||||
nodes = make([]struct {
|
l.Log("member", "regionCount", "read", n, "start position", pos, "value", regionCount)
|
||||||
node *Node
|
pos += int64(n)
|
||||||
|
|
||||||
|
regions = make([]struct {
|
||||||
|
name string
|
||||||
offset uint32
|
offset uint32
|
||||||
}, nodeCount)
|
}, regionCount)
|
||||||
for _, n := range nodes {
|
for i := range regions {
|
||||||
var (
|
var (
|
||||||
key uint32
|
key uint32
|
||||||
ok bool
|
ok bool
|
||||||
)
|
)
|
||||||
err = binary.Read(r, endianness, &key)
|
err = binary.Read(r, endianness, &key)
|
||||||
|
n = 4
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Resource{}, err
|
return Resource{}, err
|
||||||
}
|
}
|
||||||
n.node = new(Node)
|
l.Log("member", "key", "read", n, "start position", pos, "value", d[int(key)], "key", key)
|
||||||
if n.node.Name, ok = d[int(key)]; !ok {
|
pos += int64(n)
|
||||||
|
if regions[i].name, ok = d[int(key)]; !ok {
|
||||||
return Resource{}, ErrInvalidNameKey
|
return Resource{}, ErrInvalidNameKey
|
||||||
}
|
}
|
||||||
err = binary.Read(r, endianness, &n.offset)
|
err = binary.Read(r, endianness, ®ions[i].offset)
|
||||||
|
n = 4
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Resource{}, err
|
return Resource{}, err
|
||||||
}
|
}
|
||||||
|
l.Log("member", "offset", "read", n, "start position", pos, "value", regions[i].offset)
|
||||||
|
pos += int64(n)
|
||||||
}
|
}
|
||||||
// TODO: Sort
|
sort.Slice(regions, func(i, j int) bool {
|
||||||
for _, n := range nodes {
|
return regions[i].offset < regions[j].offset
|
||||||
var (
|
})
|
||||||
key uint32
|
res := Resource{
|
||||||
attrCount uint32
|
Regions: make([]*Node, 0, regionCount),
|
||||||
// childCount uint32
|
|
||||||
)
|
|
||||||
// TODO: Check offset
|
|
||||||
|
|
||||||
err = binary.Read(r, endianness, &key)
|
|
||||||
if err != nil {
|
|
||||||
return Resource{}, err
|
|
||||||
}
|
|
||||||
// if keyV, ok := d[int(key)]; !ok {
|
|
||||||
// return Resource{}, ErrKeyDoesNotMatch
|
|
||||||
// }
|
|
||||||
|
|
||||||
err = binary.Read(r, endianness, &attrCount)
|
|
||||||
if err != nil {
|
|
||||||
return Resource{}, err
|
|
||||||
}
|
|
||||||
n.node.Attributes = make([]NodeAttribute, int(attrCount))
|
|
||||||
err = binary.Read(r, endianness, &nodeCount)
|
|
||||||
if err != nil {
|
|
||||||
return Resource{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return Resource{}, nil
|
for _, re := range regions {
|
||||||
|
var node *Node
|
||||||
|
node, err = readLSBNode(r, d, endianness, version, re.offset)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
node.RegionName = re.name
|
||||||
|
res.Regions = append(res.Regions, node)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readLSBAttribute(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder) (NodeAttribute, error) {
|
func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version FileVersion, offset uint32) (*Node, error) {
|
||||||
|
var (
|
||||||
|
key uint32
|
||||||
|
attrCount uint32
|
||||||
|
childCount uint32
|
||||||
|
node = new(Node)
|
||||||
|
err error
|
||||||
|
ok bool
|
||||||
|
|
||||||
|
l log.Logger
|
||||||
|
pos int64
|
||||||
|
n int
|
||||||
|
)
|
||||||
|
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "node")
|
||||||
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
|
if pos != int64(offset) && offset != 0 {
|
||||||
|
panic("shit")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(r, endianness, &key)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.Log("member", "key", "read", n, "start position", pos, "value", d[int(key)], "key", key)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
if node.Name, ok = d[int(key)]; !ok {
|
||||||
|
return nil, errors.New("node id key is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(r, endianness, &attrCount)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
l.Log("member", "attrCount", "read", n, "start position", pos, "value", attrCount)
|
||||||
|
pos += int64(n)
|
||||||
|
|
||||||
|
err = binary.Read(r, endianness, &childCount)
|
||||||
|
n = 4
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
l.Log("member", "childCount", "read", n, "start position", pos, "value", childCount)
|
||||||
|
|
||||||
|
node.Attributes = make([]NodeAttribute, int(attrCount))
|
||||||
|
|
||||||
|
for i := range node.Attributes {
|
||||||
|
node.Attributes[i], err = readLSBAttribute(r, d, endianness, version)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Children = make([]*Node, int(childCount))
|
||||||
|
for i := range node.Children {
|
||||||
|
node.Children[i], err = readLSBNode(r, d, endianness, version, 0)
|
||||||
|
if err != nil {
|
||||||
|
return node, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLSBAttribute(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version FileVersion) (NodeAttribute, error) {
|
||||||
var (
|
var (
|
||||||
key uint32
|
key uint32
|
||||||
name string
|
name string
|
||||||
@ -148,16 +354,16 @@ func readLSBAttribute(r io.ReadSeeker, d IdentifierDictionary, endianness binary
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return attr, err
|
return attr, err
|
||||||
}
|
}
|
||||||
return ReadLSBAttr(r, name, DataType(attrType))
|
return ReadLSBAttr(r, name, DataType(attrType), endianness, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadLSBAttr(r io.ReadSeeker, name string, DT DataType) (NodeAttribute, error) {
|
func ReadLSBAttr(r io.ReadSeeker, name string, dt DataType, endianness binary.ByteOrder, version FileVersion) (NodeAttribute, error) {
|
||||||
// LSF and LSB serialize the buffer types differently, so specialized
|
// LSF and LSB serialize the buffer types differently, so specialized
|
||||||
// code is added to the LSB and LSf serializers, and the common code is
|
// code is added to the LSB and LSf serializers, and the common code is
|
||||||
// available in BinUtils.ReadAttribute()
|
// available in BinUtils.ReadAttribute()
|
||||||
var (
|
var (
|
||||||
attr = NodeAttribute{
|
attr = NodeAttribute{
|
||||||
Type: DT,
|
Type: dt,
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
err error
|
err error
|
||||||
@ -169,49 +375,54 @@ func ReadLSBAttr(r io.ReadSeeker, name string, DT DataType) (NodeAttribute, erro
|
|||||||
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "attribute")
|
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "attribute")
|
||||||
pos, err = r.Seek(0, io.SeekCurrent)
|
pos, err = r.Seek(0, io.SeekCurrent)
|
||||||
|
|
||||||
switch DT {
|
switch dt {
|
||||||
case DTString, DTPath, DTFixedString, DTLSString, DTWString, DTLSWString:
|
case DTString, DTPath, DTFixedString, DTLSString: //, DTLSWString:
|
||||||
var v string
|
var v string
|
||||||
|
err = binary.Read(r, endianness, &length)
|
||||||
|
if err != nil {
|
||||||
|
return attr, err
|
||||||
|
}
|
||||||
v, err = ReadCString(r, int(length))
|
v, err = ReadCString(r, int(length))
|
||||||
attr.Value = v
|
attr.Value = v
|
||||||
|
|
||||||
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
||||||
pos += int64(length)
|
|
||||||
|
|
||||||
return attr, err
|
return attr, err
|
||||||
|
|
||||||
|
case DTWString:
|
||||||
|
panic("Not implemented")
|
||||||
|
|
||||||
case DTTranslatedString:
|
case DTTranslatedString:
|
||||||
var v TranslatedString
|
var v TranslatedString
|
||||||
// v, err = ReadTranslatedString(r, Version, EngineVersion)
|
v, err = ReadTranslatedString(r, version, 0)
|
||||||
attr.Value = v
|
attr.Value = v
|
||||||
|
|
||||||
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
||||||
pos += int64(length)
|
|
||||||
|
|
||||||
return attr, err
|
return attr, err
|
||||||
|
|
||||||
case DTTranslatedFSString:
|
case DTTranslatedFSString:
|
||||||
|
panic("Not implemented")
|
||||||
var v TranslatedFSString
|
var v TranslatedFSString
|
||||||
// v, err = ReadTranslatedFSString(r, Version)
|
// v, err = ReadTranslatedFSString(r, Version)
|
||||||
attr.Value = v
|
attr.Value = v
|
||||||
|
|
||||||
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
||||||
pos += int64(length)
|
|
||||||
|
|
||||||
return attr, err
|
return attr, err
|
||||||
|
|
||||||
case DTScratchBuffer:
|
case DTScratchBuffer:
|
||||||
|
panic("Not implemented")
|
||||||
|
|
||||||
v := make([]byte, length)
|
v := make([]byte, length)
|
||||||
_, err = r.Read(v)
|
_, err = r.Read(v)
|
||||||
attr.Value = v
|
attr.Value = v
|
||||||
|
|
||||||
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
|
||||||
pos += int64(length)
|
|
||||||
|
|
||||||
return attr, err
|
return attr, err
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ReadAttribute(r, name, DT, uint(length), l)
|
return ReadAttribute(r, name, dt, uint(length), l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user