Move LSF and LSB decoding into their own packages

Adapt format.go from the image package
Change the only package level state variable to a function parameter
Load entire files into memory for performance
This commit is contained in:
lordwelch 2021-01-06 01:08:20 -08:00
parent e8c7e22293
commit 2f87df40d6
12 changed files with 474 additions and 331 deletions

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Norbyte
Copyright (c) 2020 lordwelch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -385,9 +385,7 @@ func (na *NodeAttribute) FromString(str string) error {
}
}
var (
err error
)
var err error
switch na.Type {
case DTNone:

View File

@ -406,3 +406,142 @@ func (l *LimitedReadSeeker) Seek(offset int64, whence int) (int64, error) {
return -1, io.ErrNoProgress
}
}
func ReadTranslatedString(r io.ReadSeeker, version FileVersion, engineVersion uint32) (TranslatedString, error) {
var (
str TranslatedString
err error
)
if version >= VerBG3 || engineVersion == 0x4000001d {
// logger.Println("decoding bg3 data")
var version uint16
err = binary.Read(r, binary.LittleEndian, &version)
if err != nil {
return str, err
}
str.Version = version
err = binary.Read(r, binary.LittleEndian, &version)
if err != nil {
return str, err
}
if version == 0 {
str.Value, err = ReadCString(r, int(str.Version))
if err != nil {
return str, err
}
str.Version = 0
} else {
_, err = r.Seek(-2, io.SeekCurrent)
}
} else {
str.Version = 0
var (
vlength int32
v []byte
// n int
)
err = binary.Read(r, binary.LittleEndian, &vlength)
if err != nil {
return str, err
}
v = make([]byte, vlength)
_, err = r.Read(v)
if err != nil {
return str, err
}
str.Value = string(v)
}
var handleLength int32
err = binary.Read(r, binary.LittleEndian, &handleLength)
if err != nil {
return str, err
}
str.Handle, err = ReadCString(r, int(handleLength))
if err != nil {
return str, err
}
// logger.Printf("handle %s; %v", str.Handle, err)
return str, nil
}
func ReadTranslatedFSString(r io.ReadSeeker, version FileVersion) (TranslatedFSString, error) {
var (
str = TranslatedFSString{}
err error
)
if version >= VerBG3 {
var version uint16
err = binary.Read(r, binary.LittleEndian, &version)
if err != nil {
return str, err
}
str.Version = version
} else {
str.Version = 0
var length int32
err = binary.Read(r, binary.LittleEndian, &length)
if err != nil {
return str, err
}
str.Value, err = ReadCString(r, int(length))
if err != nil {
return str, err
}
}
var handleLength int32
err = binary.Read(r, binary.LittleEndian, &handleLength)
if err != nil {
return str, err
}
str.Handle, err = ReadCString(r, int(handleLength))
if err != nil {
return str, err
}
var arguments int32
err = binary.Read(r, binary.LittleEndian, &arguments)
if err != nil {
return str, err
}
str.Arguments = make([]TranslatedFSStringArgument, 0, arguments)
for i := 0; i < int(arguments); i++ {
arg := TranslatedFSStringArgument{}
var argKeyLength int32
err = binary.Read(r, binary.LittleEndian, &argKeyLength)
if err != nil {
return str, err
}
arg.Key, err = ReadCString(r, int(argKeyLength))
if err != nil {
return str, err
}
arg.String, err = ReadTranslatedFSString(r, version)
if err != nil {
return str, err
}
var argValueLength int32
err = binary.Read(r, binary.LittleEndian, &argValueLength)
if err != nil {
return str, err
}
arg.Value, err = ReadCString(r, int(argValueLength))
if err != nil {
return str, err
}
str.Arguments = append(str.Arguments, arg)
}
return str, nil
}

View File

@ -1,16 +1,21 @@
package main
import (
"bytes"
"encoding/xml"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"git.narnian.us/lordwelch/lsgo"
_ "git.narnian.us/lordwelch/lsgo/lsb"
_ "git.narnian.us/lordwelch/lsgo/lsf"
"github.com/go-kit/kit/log"
"github.com/kr/pretty"
@ -75,6 +80,7 @@ func main() {
}
}
}
func openLSF(filename string) error {
var (
l *lsgo.Resource
@ -118,17 +124,65 @@ func openLSF(filename string) error {
func readLSF(filename string) (*lsgo.Resource, error) {
var (
l lsgo.Resource
f *os.File
err error
l lsgo.Resource
r io.ReadSeeker
file *os.File
fi os.FileInfo
err error
)
f, err = os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
switch filepath.Ext(filename) {
l, err = lsgo.ReadLSF(f)
case ".lsf", ".lsb":
var b []byte
fi, err = os.Stat(filename)
if err != nil {
return nil, err
}
// Arbitrary size, no lsf file should reach 100 MB (I haven't found one over 90 KB) and if you don't have 100 MB of ram free you shouldn't be using this
if fi.Size() <= 100*1024*1024 {
b, err = ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
r = bytes.NewReader(b)
break
}
fallthrough
default:
b := make([]byte, 4)
file, err = os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
_, err = file.Read(b)
if err != nil {
return nil, err
}
if !lsgo.SupportedFormat(b) {
return nil, lsgo.ErrFormat
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
fi, _ = os.Stat(filename)
// I have never seen a valid "ls*" file over 90 KB
if fi.Size() < 1*1024*1024 {
b, err = ioutil.ReadAll(file)
if err != nil {
return nil, err
}
r = bytes.NewReader(b)
} else {
r = file
}
}
l, _, err = lsgo.Decode(r)
if err != nil {
return nil, err
}
@ -159,9 +213,7 @@ func marshalXML(l *lsgo.Resource) (string, error) {
}
func writeXML(f io.StringWriter, n string) error {
var (
err error
)
var err error
_, err = f.WriteString(strings.ToLower(xml.Header))
if err != nil {
return err

View File

@ -1,6 +1,9 @@
package lsgo
import "errors"
import (
"errors"
"fmt"
)
type FileVersion uint32
@ -43,3 +46,12 @@ var (
ErrInvalidNameKey = errors.New("invalid name key")
ErrKeyDoesNotMatch = errors.New("key for this node does not match")
)
type HeaderError struct {
Expected []byte
Got []byte
}
func (he HeaderError) Error() string {
return fmt.Sprintf("Invalid LSF signature; expected %v, got %v", he.Expected, he.Got)
}

92
format.go Normal file
View File

@ -0,0 +1,92 @@
// Adapted from the image package
package lsgo
import (
"errors"
"fmt"
"io"
"os"
"sync"
"sync/atomic"
)
// ErrFormat indicates that decoding encountered an unknown format.
var ErrFormat = errors.New("lsgo: unknown format")
// A format holds an image format's name, magic header and how to decode it.
type format struct {
name, magic string
decode func(io.ReadSeeker) (Resource, error)
}
// Formats is the list of registered formats.
var (
formatsMu sync.Mutex
atomicFormats atomic.Value
)
// RegisterFormat registers an image format for use by Decode.
// Name is the name of the format, like "jpeg" or "png".
// Magic is the magic prefix that identifies the format's encoding. The magic
// string can contain "?" wildcards that each match any one byte.
// Decode is the function that decodes the encoded image.
// DecodeConfig is the function that decodes just its configuration.
func RegisterFormat(name, magic string, decode func(io.ReadSeeker) (Resource, error)) {
formatsMu.Lock()
formats, _ := atomicFormats.Load().([]format)
atomicFormats.Store(append(formats, format{name, magic, decode}))
formatsMu.Unlock()
}
// Match reports whether magic matches b. Magic may contain "?" wildcards.
func match(magic string, b []byte) bool {
if len(magic) != len(b) {
return false
}
for i, c := range b {
if magic[i] != c && magic[i] != '?' {
return false
}
}
return true
}
// Sniff determines the format of r's data.
func sniff(r io.ReadSeeker) format {
var (
b []byte = make([]byte, 4)
err error
)
formats, _ := atomicFormats.Load().([]format)
for _, f := range formats {
if len(b) < len(f.magic) {
fmt.Fprintln(os.Stderr, f.magic)
b = make([]byte, len(f.magic))
}
_, err = r.Read(b)
r.Seek(0, io.SeekStart)
if err == nil && match(f.magic, b) {
return f
}
}
return format{}
}
func Decode(r io.ReadSeeker) (Resource, string, error) {
f := sniff(r)
if f.decode == nil {
return Resource{}, "", ErrFormat
}
m, err := f.decode(r)
return m, f.name, err
}
func SupportedFormat(signature []byte) bool {
formats, _ := atomicFormats.Load().([]format)
for _, f := range formats {
if match(f.magic, signature) {
return true
}
}
return false
}

8
go.mod
View File

@ -2,12 +2,12 @@ module git.narnian.us/lordwelch/lsgo
go 1.15
replace github.com/pierrec/lz4/v4 v4.1.1 => ./lz4
replace github.com/pierrec/lz4/v4 v4.1.3 => ./lz4
require (
github.com/go-kit/kit v0.10.0
github.com/google/uuid v1.1.2
github.com/google/uuid v1.1.4
github.com/kr/pretty v0.2.1
github.com/pierrec/lz4/v4 v4.1.1
gonum.org/v1/gonum v0.8.1
github.com/pierrec/lz4/v4 v4.1.3
gonum.org/v1/gonum v0.8.2
)

8
go.sum
View File

@ -86,8 +86,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.4 h1:0ecGp3skIrHWPNGPJDaBIghfA6Sp7Ruo2Io8eLKzWm0=
github.com/google/uuid v1.1.4/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@ -329,8 +329,8 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.8.1 h1:wGtP3yGpc5mCLOLeTeBdjeui9oZSz5De0eOjMLC/QuQ=
gonum.org/v1/gonum v0.8.1/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=

View File

@ -1,4 +1,4 @@
package lsgo
package lsb
import (
"encoding/binary"
@ -7,6 +7,8 @@ import (
"io"
"sort"
"git.narnian.us/lordwelch/lsgo"
"github.com/go-kit/kit/log"
)
@ -15,7 +17,7 @@ type LSBHeader struct {
Size uint32
Endianness uint32
Unknown uint32
Version LSMetadata
Version lsgo.LSMetadata
}
func (lsbh *LSBHeader) Read(r io.ReadSeeker) error {
@ -25,7 +27,7 @@ func (lsbh *LSBHeader) Read(r io.ReadSeeker) error {
n int
err error
)
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "header")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsb", "part", "header")
pos, err = r.Seek(0, io.SeekCurrent)
n, err = r.Read(lsbh.Signature[:])
@ -102,27 +104,27 @@ func (lsbh *LSBHeader) Read(r io.ReadSeeker) error {
type IdentifierDictionary map[int]string
func ReadLSB(r io.ReadSeeker) (Resource, error) {
func ReadLSB(r io.ReadSeeker) (lsgo.Resource, error) {
var (
hdr = &LSBHeader{}
h = [4]byte{0x00, 0x00, 0x00, 0x40}
err error
d IdentifierDictionary
res Resource
res lsgo.Resource
l log.Logger
pos int64
)
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "file")
l = log.With(lsgo.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
return lsgo.Resource{}, err
}
if !(hdr.Signature == [4]byte{'L', 'S', 'F', 'M'} || hdr.Signature == h) {
return Resource{}, HeaderError{
return lsgo.Resource{}, lsgo.HeaderError{
Expected: []byte("LSFM"),
Got: hdr.Signature[:],
}
@ -132,13 +134,13 @@ func ReadLSB(r io.ReadSeeker) (Resource, error) {
l.Log("member", "string dictionary", "start position", pos)
d, err = ReadLSBDictionary(r, binary.LittleEndian)
if err != nil {
return Resource{}, err
return lsgo.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, err = ReadLSBRegions(r, d, binary.LittleEndian, lsgo.FileVersion(hdr.Version.Major))
res.Metadata = hdr.Version
return res, err
}
@ -153,7 +155,7 @@ func ReadLSBDictionary(r io.ReadSeeker, endianness binary.ByteOrder) (Identifier
pos int64
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "dictionary")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsb", "part", "dictionary")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, endianness, &length)
@ -179,7 +181,7 @@ func ReadLSBDictionary(r io.ReadSeeker, endianness binary.ByteOrder) (Identifier
l.Log("member", "stringLength", "read", n, "start position", pos, "value", stringLength)
pos += int64(n)
str, err = ReadCString(r, int(stringLength))
str, err = lsgo.ReadCString(r, int(stringLength))
n += int(stringLength)
if err != nil {
return dict, err
@ -199,7 +201,7 @@ func ReadLSBDictionary(r io.ReadSeeker, endianness binary.ByteOrder) (Identifier
return dict, nil
}
func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version FileVersion) (Resource, error) {
func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version lsgo.FileVersion) (lsgo.Resource, error) {
var (
regions []struct {
name string
@ -212,13 +214,13 @@ func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.B
pos int64
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "region")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsb", "part", "region")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, endianness, &regionCount)
n = 4
if err != nil {
return Resource{}, err
return lsgo.Resource{}, err
}
l.Log("member", "regionCount", "read", n, "start position", pos, "value", regionCount)
pos += int64(n)
@ -235,17 +237,17 @@ func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.B
err = binary.Read(r, endianness, &key)
n = 4
if err != nil {
return Resource{}, err
return lsgo.Resource{}, err
}
l.Log("member", "key", "read", n, "start position", pos, "value", d[int(key)], "key", key)
pos += int64(n)
if regions[i].name, ok = d[int(key)]; !ok {
return Resource{}, ErrInvalidNameKey
return lsgo.Resource{}, lsgo.ErrInvalidNameKey
}
err = binary.Read(r, endianness, &regions[i].offset)
n = 4
if err != nil {
return Resource{}, err
return lsgo.Resource{}, err
}
l.Log("member", "offset", "read", n, "start position", pos, "value", regions[i].offset)
pos += int64(n)
@ -253,11 +255,11 @@ func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.B
sort.Slice(regions, func(i, j int) bool {
return regions[i].offset < regions[j].offset
})
res := Resource{
Regions: make([]*Node, 0, regionCount),
res := lsgo.Resource{
Regions: make([]*lsgo.Node, 0, regionCount),
}
for _, re := range regions {
var node *Node
var node *lsgo.Node
node, err = readLSBNode(r, d, endianness, version, re.offset)
if err != nil {
return res, err
@ -268,12 +270,12 @@ func ReadLSBRegions(r io.ReadSeeker, d IdentifierDictionary, endianness binary.B
return res, nil
}
func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version FileVersion, offset uint32) (*Node, error) {
func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version lsgo.FileVersion, offset uint32) (*lsgo.Node, error) {
var (
key uint32
attrCount uint32
childCount uint32
node = new(Node)
node = new(lsgo.Node)
err error
ok bool
@ -281,7 +283,7 @@ func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.Byte
pos int64
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "node")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsb", "part", "node")
pos, err = r.Seek(0, io.SeekCurrent)
if pos != int64(offset) && offset != 0 {
@ -315,7 +317,7 @@ func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.Byte
}
l.Log("member", "childCount", "read", n, "start position", pos, "value", childCount)
node.Attributes = make([]NodeAttribute, int(attrCount))
node.Attributes = make([]lsgo.NodeAttribute, int(attrCount))
for i := range node.Attributes {
node.Attributes[i], err = readLSBAttribute(r, d, endianness, version)
@ -324,7 +326,7 @@ func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.Byte
}
}
node.Children = make([]*Node, int(childCount))
node.Children = make([]*lsgo.Node, int(childCount))
for i := range node.Children {
node.Children[i], err = readLSBNode(r, d, endianness, version, 0)
if err != nil {
@ -334,12 +336,12 @@ func readLSBNode(r io.ReadSeeker, d IdentifierDictionary, endianness binary.Byte
return node, nil
}
func readLSBAttribute(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version FileVersion) (NodeAttribute, error) {
func readLSBAttribute(r io.ReadSeeker, d IdentifierDictionary, endianness binary.ByteOrder, version lsgo.FileVersion) (lsgo.NodeAttribute, error) {
var (
key uint32
name string
attrType uint32
attr NodeAttribute
attr lsgo.NodeAttribute
err error
ok bool
)
@ -348,21 +350,21 @@ func readLSBAttribute(r io.ReadSeeker, d IdentifierDictionary, endianness binary
return attr, err
}
if name, ok = d[int(key)]; !ok {
return attr, ErrInvalidNameKey
return attr, lsgo.ErrInvalidNameKey
}
err = binary.Read(r, endianness, &attrType)
if err != nil {
return attr, err
}
return ReadLSBAttr(r, name, DataType(attrType), endianness, version)
return ReadLSBAttr(r, name, lsgo.DataType(attrType), endianness, version)
}
func ReadLSBAttr(r io.ReadSeeker, name string, dt DataType, endianness binary.ByteOrder, version FileVersion) (NodeAttribute, error) {
func ReadLSBAttr(r io.ReadSeeker, name string, dt lsgo.DataType, endianness binary.ByteOrder, version lsgo.FileVersion) (lsgo.NodeAttribute, error) {
// LSF and LSB serialize the buffer types differently, so specialized
// code is added to the LSB and LSf serializers, and the common code is
// available in BinUtils.ReadAttribute()
var (
attr = NodeAttribute{
attr = lsgo.NodeAttribute{
Type: dt,
Name: name,
}
@ -372,38 +374,38 @@ func ReadLSBAttr(r io.ReadSeeker, name string, dt DataType, endianness binary.By
l log.Logger
pos int64
)
l = log.With(Logger, "component", "LS converter", "file type", "lsb", "part", "attribute")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsb", "part", "attribute")
pos, err = r.Seek(0, io.SeekCurrent)
switch dt {
case DTString, DTPath, DTFixedString, DTLSString: //, DTLSWString:
case lsgo.DTString, lsgo.DTPath, lsgo.DTFixedString, lsgo.DTLSString: //, DTLSWString:
var v string
err = binary.Read(r, endianness, &length)
if err != nil {
return attr, err
}
v, err = ReadCString(r, int(length))
v, err = lsgo.ReadCString(r, int(length))
attr.Value = v
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
return attr, err
case DTWString:
case lsgo.DTWString:
panic("Not implemented")
case DTTranslatedString:
var v TranslatedString
v, err = ReadTranslatedString(r, version, 0)
case lsgo.DTTranslatedString:
var v lsgo.TranslatedString
v, err = lsgo.ReadTranslatedString(r, version, 0)
attr.Value = v
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
return attr, err
case DTTranslatedFSString:
case lsgo.DTTranslatedFSString:
panic("Not implemented")
var v TranslatedFSString
var v lsgo.TranslatedFSString
// v, err = ReadTranslatedFSString(r, Version)
attr.Value = v
@ -411,7 +413,7 @@ func ReadLSBAttr(r io.ReadSeeker, name string, dt DataType, endianness binary.By
return attr, err
case DTScratchBuffer:
case lsgo.DTScratchBuffer:
panic("Not implemented")
v := make([]byte, length)
@ -423,6 +425,11 @@ func ReadLSBAttr(r io.ReadSeeker, name string, dt DataType, endianness binary.By
return attr, err
default:
return ReadAttribute(r, name, dt, uint(length), l)
return lsgo.ReadAttribute(r, name, dt, uint(length), l)
}
}
func init() {
lsgo.RegisterFormat("lsb", "LSFM", ReadLSB)
lsgo.RegisterFormat("lsb", "\x00\x00\x00\x40", ReadLSB)
}

View File

@ -1,61 +1,24 @@
package lsgo
package lsf
import (
"encoding/binary"
"fmt"
"io"
"strconv"
"strings"
"git.narnian.us/lordwelch/lsgo"
"github.com/go-kit/kit/log"
)
var (
LSFSignature = [4]byte{0x4C, 0x53, 0x4F, 0x46}
Logger log.Logger = log.NewNopLogger()
)
// NewFilter allows filtering of l
func NewFilter(f map[string][]string, l log.Logger) log.Logger {
return filter{
filter: f,
next: l,
}
}
type filter struct {
next log.Logger
filter map[string][]string
}
func (f filter) Log(keyvals ...interface{}) error {
var allowed = true // allow everything
for i := 0; i < len(keyvals)-1; i += 2 {
if v, ok := keyvals[i].(string); ok { // key
if fil, ok := f.filter[v]; ok { // key has a filter
if v, ok = keyvals[i+1].(string); ok { // value is a string
allowed = false // this key has a filter deny everything except what the filter allows
for _, fi := range fil {
if strings.Contains(v, fi) {
allowed = true
}
}
}
}
}
}
if allowed {
return f.next.Log(keyvals...)
}
return nil
}
var LSFSignature = [4]byte{0x4C, 0x53, 0x4F, 0x46}
type LSFHeader struct {
// LSOF file signature
Signature [4]byte
// Version of the LSOF file D:OS EE is version 1/2, D:OS 2 is version 3
Version FileVersion
Version lsgo.FileVersion
// Possibly version number? (major, minor, rev, build)
EngineVersion uint32
@ -103,7 +66,7 @@ func (lsfh *LSFHeader) Read(r io.ReadSeeker) error {
n int
err error
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "header")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "header")
pos, err = r.Seek(0, io.SeekCurrent)
n, err = r.Read(lsfh.Signature[:])
if err != nil {
@ -233,7 +196,7 @@ func (lsfh *LSFHeader) Read(r io.ReadSeeker) error {
}
func (lsfh LSFHeader) IsCompressed() bool {
return CompressionFlagsToMethod(lsfh.CompressionFlags) != CMNone && CompressionFlagsToMethod(lsfh.CompressionFlags) != CMInvalid
return lsgo.CompressionFlagsToMethod(lsfh.CompressionFlags) != lsgo.CMNone && lsgo.CompressionFlagsToMethod(lsfh.CompressionFlags) != lsgo.CMInvalid
}
type NodeEntry struct {
@ -274,7 +237,7 @@ func (ne *NodeEntry) readShort(r io.ReadSeeker) error {
err error
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "short node")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "short node")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, binary.LittleEndian, &ne.NameHashTableIndex)
n = 4
@ -312,7 +275,7 @@ func (ne *NodeEntry) readLong(r io.ReadSeeker) error {
err error
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "long node")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "long node")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, binary.LittleEndian, &ne.NameHashTableIndex)
n = 4
@ -417,7 +380,7 @@ func (ae *AttributeEntry) readShort(r io.ReadSeeker) error {
err error
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "short attribute")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "short attribute")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, binary.LittleEndian, &ae.NameHashTableIndex)
@ -454,7 +417,7 @@ func (ae *AttributeEntry) readLong(r io.ReadSeeker) error {
err error
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "long attribute")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "long attribute")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, binary.LittleEndian, &ae.NameHashTableIndex)
@ -503,8 +466,8 @@ func (ae AttributeEntry) NameOffset() int {
}
// Type of this attribute (see NodeAttribute.DataType)
func (ae AttributeEntry) TypeID() DataType {
return DataType(ae.TypeAndLength & 0x3f)
func (ae AttributeEntry) TypeID() lsgo.DataType {
return lsgo.DataType(ae.TypeAndLength & 0x3f)
}
// Length of this attribute
@ -522,7 +485,7 @@ type AttributeInfo struct {
NameOffset int
// Type of this attribute (see NodeAttribute.DataType)
TypeID DataType
TypeID lsgo.DataType
// Length of this attribute
Length uint
@ -546,7 +509,7 @@ func ReadNames(r io.ReadSeeker) ([][]string, error) {
pos int64
n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "names")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "names")
pos, err = r.Seek(0, io.SeekCurrent)
err = binary.Read(r, binary.LittleEndian, &numHashEntries)
@ -567,7 +530,7 @@ func ReadNames(r io.ReadSeeker) ([][]string, error) {
l.Log("member", "numStrings", "read", n, "start position", pos, "value", numStrings)
pos += int64(n)
var hash = make([]string, int(numStrings))
hash := make([]string, int(numStrings))
for x := range hash {
var (
nameLen uint16
@ -702,19 +665,9 @@ func readAttributeInfo(r io.ReadSeeker, long bool) []AttributeInfo {
// );
// Console.WriteLine(debug);
// }
}
type HeaderError struct {
Expected []byte
Got []byte
}
func (he HeaderError) Error() string {
return fmt.Sprintf("Invalid LSF signature; expected %v, got %v", he.Expected, he.Got)
}
func ReadLSF(r io.ReadSeeker) (Resource, error) {
func ReadLSF(r io.ReadSeeker) (lsgo.Resource, error) {
var (
err error
@ -728,38 +681,36 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
attributeInfo []AttributeInfo
// Node instances
nodeInstances []*Node
nodeInstances []*lsgo.Node
)
var (
l log.Logger
pos, npos int64
// n int
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "file")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "file")
pos, err = r.Seek(0, io.SeekCurrent)
l.Log("member", "header", "start position", pos)
hdr := &LSFHeader{}
err = hdr.Read(r)
if err != nil || (hdr.Signature != LSFSignature) {
return Resource{}, HeaderError{LSFSignature[:], hdr.Signature[:]}
return lsgo.Resource{}, lsgo.HeaderError{Expected: LSFSignature[:], Got: hdr.Signature[:]}
}
if hdr.Version < VerInitial || hdr.Version > MaxVersion {
return Resource{}, fmt.Errorf("LSF version %v is not supported", hdr.Version)
if hdr.Version < lsgo.VerInitial || hdr.Version > lsgo.MaxVersion {
return lsgo.Resource{}, fmt.Errorf("LSF version %v is not supported", hdr.Version)
}
isCompressed := CompressionFlagsToMethod(hdr.CompressionFlags) != CMNone && CompressionFlagsToMethod(hdr.CompressionFlags) != CMInvalid
isCompressed := lsgo.CompressionFlagsToMethod(hdr.CompressionFlags) != lsgo.CMNone && lsgo.CompressionFlagsToMethod(hdr.CompressionFlags) != lsgo.CMInvalid
pos, err = r.Seek(0, io.SeekCurrent)
l.Log("member", "LSF names", "start position", pos)
if hdr.StringsSizeOnDisk > 0 || hdr.StringsUncompressedSize > 0 {
var (
uncompressed = LimitReadSeeker(r, int64(hdr.StringsSizeOnDisk))
)
uncompressed := lsgo.LimitReadSeeker(r, int64(hdr.StringsSizeOnDisk))
if isCompressed {
uncompressed = Decompress(uncompressed, int(hdr.StringsUncompressedSize), hdr.CompressionFlags, false)
uncompressed = lsgo.Decompress(uncompressed, int(hdr.StringsUncompressedSize), hdr.CompressionFlags, false)
}
// using (var nodesFile = new FileStream("names.bin", FileMode.Create, FileAccess.Write))
@ -770,7 +721,7 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
names, err = ReadNames(uncompressed)
// pretty.Log(len(names), names)
if err != nil && err != io.EOF {
return Resource{}, err
return lsgo.Resource{}, err
}
}
@ -783,11 +734,9 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
pos = npos
}
if hdr.NodesSizeOnDisk > 0 || hdr.NodesUncompressedSize > 0 {
var (
uncompressed = LimitReadSeeker(r, int64(hdr.NodesSizeOnDisk))
)
uncompressed := lsgo.LimitReadSeeker(r, int64(hdr.NodesSizeOnDisk))
if isCompressed {
uncompressed = Decompress(uncompressed, int(hdr.NodesUncompressedSize), hdr.CompressionFlags, hdr.Version >= VerChunkedCompress)
uncompressed = lsgo.Decompress(uncompressed, int(hdr.NodesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
}
// using (var nodesFile = new FileStream("nodes.bin", FileMode.Create, FileAccess.Write))
@ -795,12 +744,12 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
// nodesFile.Write(uncompressed, 0, uncompressed.Length);
// }
longNodes := hdr.Version >= VerExtendedNodes && hdr.Extended == 1
longNodes := hdr.Version >= lsgo.VerExtendedNodes && hdr.Extended == 1
nodeInfo, err = readNodeInfo(uncompressed, longNodes)
// pretty.Log(err, nodeInfo)
// logger.Printf("region 1 name: %v", names[nodeInfo[0].NameIndex])
if err != nil && err != io.EOF {
return Resource{}, err
return lsgo.Resource{}, err
}
}
@ -813,11 +762,9 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
pos = npos
}
if hdr.AttributesSizeOnDisk > 0 || hdr.AttributesUncompressedSize > 0 {
var (
uncompressed io.ReadSeeker = LimitReadSeeker(r, int64(hdr.AttributesSizeOnDisk))
)
var uncompressed io.ReadSeeker = lsgo.LimitReadSeeker(r, int64(hdr.AttributesSizeOnDisk))
if isCompressed {
uncompressed = Decompress(uncompressed, int(hdr.AttributesUncompressedSize), hdr.CompressionFlags, hdr.Version >= VerChunkedCompress)
uncompressed = lsgo.Decompress(uncompressed, int(hdr.AttributesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
}
// using (var attributesFile = new FileStream("attributes.bin", FileMode.Create, FileAccess.Write))
@ -825,7 +772,7 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
// attributesFile.Write(uncompressed, 0, uncompressed.Length);
// }
longAttributes := hdr.Version >= VerExtendedNodes && hdr.Extended == 1
longAttributes := hdr.Version >= lsgo.VerExtendedNodes && hdr.Extended == 1
attributeInfo = readAttributeInfo(uncompressed, longAttributes)
// logger.Printf("attribute 1 name: %v", names[attributeInfo[0].NameIndex])
// pretty.Log(attributeInfo)
@ -839,18 +786,16 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
} else {
pos = npos
}
var (
uncompressed io.ReadSeeker = LimitReadSeeker(r, int64(hdr.ValuesSizeOnDisk))
)
var uncompressed io.ReadSeeker = lsgo.LimitReadSeeker(r, int64(hdr.ValuesSizeOnDisk))
if hdr.ValuesSizeOnDisk > 0 || hdr.ValuesUncompressedSize > 0 {
if isCompressed {
uncompressed = Decompress(r, int(hdr.ValuesUncompressedSize), hdr.CompressionFlags, hdr.Version >= VerChunkedCompress)
uncompressed = lsgo.Decompress(r, int(hdr.ValuesUncompressedSize), hdr.CompressionFlags, hdr.Version >= lsgo.VerChunkedCompress)
}
}
res := Resource{}
valueStart, _ = uncompressed.Seek(0, io.SeekCurrent)
nodeInstances, err = ReadRegions(uncompressed, names, nodeInfo, attributeInfo, hdr.Version, hdr.EngineVersion)
res := lsgo.Resource{}
valueStart, _ := uncompressed.Seek(0, io.SeekCurrent)
nodeInstances, err = ReadRegions(uncompressed, valueStart, names, nodeInfo, attributeInfo, hdr.Version, hdr.EngineVersion)
if err != nil {
return res, err
}
@ -867,16 +812,13 @@ func ReadLSF(r io.ReadSeeker) (Resource, error) {
// pretty.Log(res)
return res, nil
}
var valueStart int64
func ReadRegions(r io.ReadSeeker, names [][]string, nodeInfo []NodeInfo, attributeInfo []AttributeInfo, version FileVersion, engineVersion uint32) ([]*Node, error) {
NodeInstances := make([]*Node, 0, len(nodeInfo))
func ReadRegions(r io.ReadSeeker, valueStart int64, names [][]string, nodeInfo []NodeInfo, attributeInfo []AttributeInfo, version lsgo.FileVersion, engineVersion uint32) ([]*lsgo.Node, error) {
NodeInstances := make([]*lsgo.Node, 0, len(nodeInfo))
for _, nodeInfo := range nodeInfo {
if nodeInfo.ParentIndex == -1 {
region, err := ReadNode(r, nodeInfo, names, attributeInfo, version, engineVersion)
region, err := ReadNode(r, valueStart, nodeInfo, names, attributeInfo, version, engineVersion)
// pretty.Log(err, region)
@ -887,7 +829,7 @@ func ReadRegions(r io.ReadSeeker, names [][]string, nodeInfo []NodeInfo, attribu
return NodeInstances, err
}
} else {
node, err := ReadNode(r, nodeInfo, names, attributeInfo, version, engineVersion)
node, err := ReadNode(r, valueStart, nodeInfo, names, attributeInfo, version, engineVersion)
// pretty.Log(err, node)
@ -903,16 +845,16 @@ func ReadRegions(r io.ReadSeeker, names [][]string, nodeInfo []NodeInfo, attribu
return NodeInstances, nil
}
func ReadNode(r io.ReadSeeker, ni NodeInfo, names [][]string, attributeInfo []AttributeInfo, version FileVersion, engineVersion uint32) (Node, error) {
func ReadNode(r io.ReadSeeker, valueStart int64, ni NodeInfo, names [][]string, attributeInfo []AttributeInfo, version lsgo.FileVersion, engineVersion uint32) (lsgo.Node, error) {
var (
node = Node{}
node = lsgo.Node{}
index = ni.FirstAttributeIndex
err error
l log.Logger
pos int64
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "node")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "node")
pos, err = r.Seek(0, io.SeekCurrent)
node.Name = names[ni.NameIndex][ni.NameOffset]
@ -922,7 +864,7 @@ func ReadNode(r io.ReadSeeker, ni NodeInfo, names [][]string, attributeInfo []At
for index != -1 {
var (
attribute = attributeInfo[index]
v NodeAttribute
v lsgo.NodeAttribute
)
if valueStart+int64(attribute.DataOffset) != pos {
@ -943,12 +885,12 @@ func ReadNode(r io.ReadSeeker, ni NodeInfo, names [][]string, attributeInfo []At
return node, nil
}
func ReadLSFAttribute(r io.ReadSeeker, name string, dt DataType, length uint, version FileVersion, engineVersion uint32) (NodeAttribute, error) {
func ReadLSFAttribute(r io.ReadSeeker, name string, dt lsgo.DataType, length uint, version lsgo.FileVersion, engineVersion uint32) (lsgo.NodeAttribute, error) {
// LSF and LSB serialize the buffer types differently, so specialized
// code is added to the LSB and LSf serializers, and the common code is
// available in BinUtils.ReadAttribute()
var (
attr = NodeAttribute{
attr = lsgo.NodeAttribute{
Type: dt,
Name: name,
}
@ -957,13 +899,13 @@ func ReadLSFAttribute(r io.ReadSeeker, name string, dt DataType, length uint, ve
l log.Logger
pos int64
)
l = log.With(Logger, "component", "LS converter", "file type", "lsf", "part", "attribute")
l = log.With(lsgo.Logger, "component", "LS converter", "file type", "lsf", "part", "attribute")
pos, err = r.Seek(0, io.SeekCurrent)
switch dt {
case DTString, DTPath, DTFixedString, DTLSString, DTWString, DTLSWString:
case lsgo.DTString, lsgo.DTPath, lsgo.DTFixedString, lsgo.DTLSString, lsgo.DTWString, lsgo.DTLSWString:
var v string
v, err = ReadCString(r, int(length))
v, err = lsgo.ReadCString(r, int(length))
attr.Value = v
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
@ -971,9 +913,9 @@ func ReadLSFAttribute(r io.ReadSeeker, name string, dt DataType, length uint, ve
return attr, err
case DTTranslatedString:
var v TranslatedString
v, err = ReadTranslatedString(r, version, engineVersion)
case lsgo.DTTranslatedString:
var v lsgo.TranslatedString
v, err = lsgo.ReadTranslatedString(r, version, engineVersion)
attr.Value = v
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
@ -981,9 +923,9 @@ func ReadLSFAttribute(r io.ReadSeeker, name string, dt DataType, length uint, ve
return attr, err
case DTTranslatedFSString:
var v TranslatedFSString
v, err = ReadTranslatedFSString(r, version)
case lsgo.DTTranslatedFSString:
var v lsgo.TranslatedFSString
v, err = lsgo.ReadTranslatedFSString(r, version)
attr.Value = v
l.Log("member", name, "read", length, "start position", pos, "value", attr.Value)
@ -991,7 +933,7 @@ func ReadLSFAttribute(r io.ReadSeeker, name string, dt DataType, length uint, ve
return attr, err
case DTScratchBuffer:
case lsgo.DTScratchBuffer:
v := make([]byte, length)
_, err = r.Read(v)
@ -1003,147 +945,10 @@ func ReadLSFAttribute(r io.ReadSeeker, name string, dt DataType, length uint, ve
return attr, err
default:
return ReadAttribute(r, name, dt, length, l)
return lsgo.ReadAttribute(r, name, dt, length, l)
}
}
func ReadTranslatedString(r io.ReadSeeker, version FileVersion, engineVersion uint32) (TranslatedString, error) {
var (
str TranslatedString
err error
)
if version >= VerBG3 || engineVersion == 0x4000001d {
// logger.Println("decoding bg3 data")
var version uint16
err = binary.Read(r, binary.LittleEndian, &version)
if err != nil {
return str, err
}
str.Version = version
err = binary.Read(r, binary.LittleEndian, &version)
if err != nil {
return str, err
}
if version == 0 {
str.Value, err = ReadCString(r, int(str.Version))
if err != nil {
return str, err
}
str.Version = 0
} else {
_, err = r.Seek(-2, io.SeekCurrent)
}
} else {
str.Version = 0
var (
vlength int32
v []byte
// n int
)
err = binary.Read(r, binary.LittleEndian, &vlength)
if err != nil {
return str, err
}
v = make([]byte, vlength)
_, err = r.Read(v)
if err != nil {
return str, err
}
str.Value = string(v)
}
var handleLength int32
err = binary.Read(r, binary.LittleEndian, &handleLength)
if err != nil {
return str, err
}
str.Handle, err = ReadCString(r, int(handleLength))
if err != nil {
return str, err
}
// logger.Printf("handle %s; %v", str.Handle, err)
return str, nil
}
func ReadTranslatedFSString(r io.ReadSeeker, version FileVersion) (TranslatedFSString, error) {
var (
str = TranslatedFSString{}
err error
)
if version >= VerBG3 {
var version uint16
err = binary.Read(r, binary.LittleEndian, &version)
if err != nil {
return str, err
}
str.Version = version
} else {
str.Version = 0
var (
length int32
)
err = binary.Read(r, binary.LittleEndian, &length)
if err != nil {
return str, err
}
str.Value, err = ReadCString(r, int(length))
if err != nil {
return str, err
}
}
var handleLength int32
err = binary.Read(r, binary.LittleEndian, &handleLength)
if err != nil {
return str, err
}
str.Handle, err = ReadCString(r, int(handleLength))
if err != nil {
return str, err
}
var arguments int32
err = binary.Read(r, binary.LittleEndian, &arguments)
if err != nil {
return str, err
}
str.Arguments = make([]TranslatedFSStringArgument, 0, arguments)
for i := 0; i < int(arguments); i++ {
arg := TranslatedFSStringArgument{}
var argKeyLength int32
err = binary.Read(r, binary.LittleEndian, &argKeyLength)
if err != nil {
return str, err
}
arg.Key, err = ReadCString(r, int(argKeyLength))
if err != nil {
return str, err
}
arg.String, err = ReadTranslatedFSString(r, version)
if err != nil {
return str, err
}
var argValueLength int32
err = binary.Read(r, binary.LittleEndian, &argValueLength)
if err != nil {
return str, err
}
arg.Value, err = ReadCString(r, int(argValueLength))
if err != nil {
return str, err
}
str.Arguments = append(str.Arguments, arg)
}
return str, nil
func init() {
lsgo.RegisterFormat("lsf", "LSOF", ReadLSF)
}

44
lsgo.go Normal file
View File

@ -0,0 +1,44 @@
package lsgo
import (
"strings"
"github.com/go-kit/kit/log"
)
var Logger log.Logger = log.NewNopLogger()
// NewFilter allows filtering of l
func NewFilter(f map[string][]string, l log.Logger) log.Logger {
return filter{
filter: f,
next: l,
}
}
type filter struct {
next log.Logger
filter map[string][]string
}
func (f filter) Log(keyvals ...interface{}) error {
allowed := true // allow everything
for i := 0; i < len(keyvals)-1; i += 2 {
if v, ok := keyvals[i].(string); ok { // key
if fil, ok := f.filter[v]; ok { // key has a filter
if v, ok = keyvals[i+1].(string); ok { // value is a string
allowed = false // this key has a filter deny everything except what the filter allows
for _, fi := range fil {
if strings.Contains(v, fi) {
allowed = true
}
}
}
}
}
}
if allowed {
return f.next.Log(keyvals...)
}
return nil
}

View File

@ -6,7 +6,7 @@ import (
)
type LSMetadata struct {
//public const uint CurrentMajorVersion = 33;
// public const uint CurrentMajorVersion = 33;
Timestamp uint64 `xml:"-"`
Major uint32 `xml:"major,attr"`
@ -15,18 +15,12 @@ type LSMetadata struct {
Build uint32 `xml:"build,attr"`
}
type format struct {
name, magic string
read func(io.Reader) (Resource, error)
}
type Resource struct {
Metadata LSMetadata `xml:"version"`
Regions []*Node `xml:"region"`
}
func (r *Resource) Read(io.Reader) {
}
// public Resource()