676 lines
18 KiB
Go
676 lines
18 KiB
Go
package gff
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
)
|
|
|
|
const (
|
|
headerSize = 56
|
|
)
|
|
|
|
type header struct {
|
|
FileType [4]byte
|
|
FileVersion [4]byte
|
|
StructOffset uint32
|
|
StructCount uint32
|
|
FieldOffset uint32
|
|
FieldCount uint32
|
|
LabelOffset uint32
|
|
LabelCount uint32
|
|
FieldDataOffset uint32
|
|
FieldDataCount uint32
|
|
FieldIndicesOffset uint32
|
|
FieldIndicesCount uint32
|
|
ListIndicesOffset uint32
|
|
ListIndicesCount uint32
|
|
}
|
|
|
|
type rawStruct struct {
|
|
Type uint32
|
|
DataOrOffset uint32
|
|
FieldCount uint32
|
|
}
|
|
|
|
type rawField struct {
|
|
Type uint32
|
|
LabelIndex uint32
|
|
DataOrOffset uint32
|
|
}
|
|
|
|
func Read(r io.Reader) (Document, error) {
|
|
data, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return Document{}, fmt.Errorf("read gff: %w", err)
|
|
}
|
|
if len(data) < headerSize {
|
|
return Document{}, fmt.Errorf("gff file too small: %d bytes", len(data))
|
|
}
|
|
|
|
var hdr header
|
|
if err := binary.Read(bytes.NewReader(data[:headerSize]), binary.LittleEndian, &hdr); err != nil {
|
|
return Document{}, fmt.Errorf("decode header: %w", err)
|
|
}
|
|
|
|
reader := binaryReader{data: data, hdr: hdr}
|
|
rawStructs, err := reader.readStructs()
|
|
if err != nil {
|
|
return Document{}, err
|
|
}
|
|
rawFields, err := reader.readFields()
|
|
if err != nil {
|
|
return Document{}, err
|
|
}
|
|
labels, err := reader.readLabels()
|
|
if err != nil {
|
|
return Document{}, err
|
|
}
|
|
|
|
root, err := reader.decodeStruct(0, rawStructs, rawFields, labels)
|
|
if err != nil {
|
|
return Document{}, err
|
|
}
|
|
|
|
return Document{
|
|
FileType: string(hdr.FileType[:]),
|
|
FileVersion: string(hdr.FileVersion[:]),
|
|
Root: root,
|
|
}, nil
|
|
}
|
|
|
|
func Write(w io.Writer, doc Document) error {
|
|
encoder := newBinaryEncoder(doc)
|
|
data, err := encoder.encode()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = w.Write(data)
|
|
return err
|
|
}
|
|
|
|
type binaryReader struct {
|
|
data []byte
|
|
hdr header
|
|
}
|
|
|
|
func (r binaryReader) readStructs() ([]rawStruct, error) {
|
|
return readTable[rawStruct](r.data, r.hdr.StructOffset, r.hdr.StructCount)
|
|
}
|
|
|
|
func (r binaryReader) readFields() ([]rawField, error) {
|
|
return readTable[rawField](r.data, r.hdr.FieldOffset, r.hdr.FieldCount)
|
|
}
|
|
|
|
func (r binaryReader) readLabels() ([]string, error) {
|
|
start := int(r.hdr.LabelOffset)
|
|
end := start + int(r.hdr.LabelCount)*16
|
|
if end > len(r.data) {
|
|
return nil, fmt.Errorf("label table exceeds file bounds")
|
|
}
|
|
|
|
labels := make([]string, 0, r.hdr.LabelCount)
|
|
for offset := start; offset < end; offset += 16 {
|
|
chunk := r.data[offset : offset+16]
|
|
n := bytes.IndexByte(chunk, 0)
|
|
if n == -1 {
|
|
n = len(chunk)
|
|
}
|
|
labels = append(labels, string(chunk[:n]))
|
|
}
|
|
return labels, nil
|
|
}
|
|
|
|
func (r binaryReader) decodeStruct(index uint32, structs []rawStruct, fields []rawField, labels []string) (Struct, error) {
|
|
if index >= uint32(len(structs)) {
|
|
return Struct{}, fmt.Errorf("struct index %d out of range", index)
|
|
}
|
|
|
|
raw := structs[index]
|
|
fieldIndices, err := r.fieldIndices(raw)
|
|
if err != nil {
|
|
return Struct{}, err
|
|
}
|
|
|
|
out := Struct{
|
|
Type: raw.Type,
|
|
Fields: make([]Field, 0, len(fieldIndices)),
|
|
}
|
|
for _, fieldIndex := range fieldIndices {
|
|
if fieldIndex >= uint32(len(fields)) {
|
|
return Struct{}, fmt.Errorf("field index %d out of range", fieldIndex)
|
|
}
|
|
field, err := r.decodeField(fields[fieldIndex], structs, fields, labels)
|
|
if err != nil {
|
|
return Struct{}, err
|
|
}
|
|
out.Fields = append(out.Fields, field)
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (r binaryReader) fieldIndices(s rawStruct) ([]uint32, error) {
|
|
switch s.FieldCount {
|
|
case 0:
|
|
return nil, nil
|
|
case 1:
|
|
return []uint32{s.DataOrOffset}, nil
|
|
default:
|
|
start := int(r.hdr.FieldIndicesOffset + s.DataOrOffset)
|
|
end := start + int(s.FieldCount)*4
|
|
if end > len(r.data) {
|
|
return nil, fmt.Errorf("field indices exceed file bounds")
|
|
}
|
|
|
|
out := make([]uint32, s.FieldCount)
|
|
reader := bytes.NewReader(r.data[start:end])
|
|
if err := binary.Read(reader, binary.LittleEndian, &out); err != nil {
|
|
return nil, fmt.Errorf("decode field indices: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
}
|
|
|
|
func (r binaryReader) decodeField(raw rawField, structs []rawStruct, fields []rawField, labels []string) (Field, error) {
|
|
if raw.LabelIndex >= uint32(len(labels)) {
|
|
return Field{}, fmt.Errorf("label index %d out of range", raw.LabelIndex)
|
|
}
|
|
fieldType := FieldType(raw.Type)
|
|
value, err := r.decodeValue(fieldType, raw.DataOrOffset, structs, fields, labels)
|
|
if err != nil {
|
|
return Field{}, fmt.Errorf("decode field %q: %w", labels[raw.LabelIndex], err)
|
|
}
|
|
|
|
return Field{
|
|
Label: labels[raw.LabelIndex],
|
|
Type: fieldType,
|
|
Value: value,
|
|
}, nil
|
|
}
|
|
|
|
func (r binaryReader) decodeValue(fieldType FieldType, data uint32, structs []rawStruct, fields []rawField, labels []string) (Value, error) {
|
|
switch fieldType {
|
|
case TypeByte:
|
|
return ByteValue(uint8(data)), nil
|
|
case TypeChar:
|
|
return CharValue(int8(data)), nil
|
|
case TypeWord:
|
|
return WordValue(uint16(data)), nil
|
|
case TypeShort:
|
|
return ShortValue(int16(data)), nil
|
|
case TypeDWord:
|
|
return DWordValue(data), nil
|
|
case TypeInt:
|
|
return IntValue(int32(data)), nil
|
|
case TypeFloat:
|
|
return FloatValue(math.Float32frombits(data)), nil
|
|
case TypeDWord64:
|
|
raw, err := r.sliceFieldData(data, 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return DWord64Value(binary.LittleEndian.Uint64(raw)), nil
|
|
case TypeInt64:
|
|
raw, err := r.sliceFieldData(data, 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Int64Value(int64(binary.LittleEndian.Uint64(raw))), nil
|
|
case TypeDouble:
|
|
raw, err := r.sliceFieldData(data, 8)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return DoubleValue(math.Float64frombits(binary.LittleEndian.Uint64(raw))), nil
|
|
case TypeCExoString:
|
|
raw, err := r.prefixedFieldData(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return StringValue(string(raw)), nil
|
|
case TypeResRef:
|
|
raw, err := r.prefixedByteFieldData(data, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ResRefValue(string(raw)), nil
|
|
case TypeCExoLocString:
|
|
raw, err := r.locStringFieldData(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decodeLocString(raw)
|
|
case TypeVoid:
|
|
raw, err := r.prefixedFieldData(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return VoidValue(raw), nil
|
|
case TypeStruct:
|
|
return r.decodeStruct(data, structs, fields, labels)
|
|
case TypeList:
|
|
return r.decodeList(data, structs, fields, labels)
|
|
case TypeOrientation:
|
|
raw, err := r.sliceFieldData(data, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Orientation{
|
|
X: math.Float32frombits(binary.LittleEndian.Uint32(raw[0:4])),
|
|
Y: math.Float32frombits(binary.LittleEndian.Uint32(raw[4:8])),
|
|
Z: math.Float32frombits(binary.LittleEndian.Uint32(raw[8:12])),
|
|
W: math.Float32frombits(binary.LittleEndian.Uint32(raw[12:16])),
|
|
}, nil
|
|
case TypeVector:
|
|
raw, err := r.sliceFieldData(data, 12)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Vector{
|
|
X: math.Float32frombits(binary.LittleEndian.Uint32(raw[0:4])),
|
|
Y: math.Float32frombits(binary.LittleEndian.Uint32(raw[4:8])),
|
|
Z: math.Float32frombits(binary.LittleEndian.Uint32(raw[8:12])),
|
|
}, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported field type %d", fieldType)
|
|
}
|
|
}
|
|
|
|
func (r binaryReader) decodeList(offset uint32, structs []rawStruct, fields []rawField, labels []string) (ListValue, error) {
|
|
start := int(r.hdr.ListIndicesOffset + offset)
|
|
if start+4 > len(r.data) {
|
|
return nil, fmt.Errorf("list data exceeds file bounds")
|
|
}
|
|
count := binary.LittleEndian.Uint32(r.data[start : start+4])
|
|
start += 4
|
|
end := start + int(count)*4
|
|
if end > len(r.data) {
|
|
return nil, fmt.Errorf("list indices exceed file bounds")
|
|
}
|
|
|
|
list := make(ListValue, 0, count)
|
|
for pos := start; pos < end; pos += 4 {
|
|
index := binary.LittleEndian.Uint32(r.data[pos : pos+4])
|
|
child, err := r.decodeStruct(index, structs, fields, labels)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
list = append(list, child)
|
|
}
|
|
return list, nil
|
|
}
|
|
|
|
func (r binaryReader) sliceFieldData(offset uint32, size int) ([]byte, error) {
|
|
start := int(r.hdr.FieldDataOffset + offset)
|
|
end := start + size
|
|
if end > len(r.data) {
|
|
return nil, fmt.Errorf("field data exceeds file bounds")
|
|
}
|
|
return r.data[start:end], nil
|
|
}
|
|
|
|
func (r binaryReader) prefixedFieldData(offset uint32) ([]byte, error) {
|
|
sizeRaw, err := r.sliceFieldData(offset, 4)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
size := int(binary.LittleEndian.Uint32(sizeRaw))
|
|
return r.sliceFieldData(offset+4, size)
|
|
}
|
|
|
|
func (r binaryReader) prefixedByteFieldData(offset uint32, prefixBytes uint32) ([]byte, error) {
|
|
header, err := r.sliceFieldData(offset, int(prefixBytes))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
size := int(header[0])
|
|
return r.sliceFieldData(offset+prefixBytes, size)
|
|
}
|
|
|
|
func (r binaryReader) locStringFieldData(offset uint32) ([]byte, error) {
|
|
sizeRaw, err := r.sliceFieldData(offset, 4)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
size := int(binary.LittleEndian.Uint32(sizeRaw))
|
|
return r.sliceFieldData(offset, size+4)
|
|
}
|
|
|
|
func readTable[T any](data []byte, offset uint32, count uint32) ([]T, error) {
|
|
if count == 0 {
|
|
return nil, nil
|
|
}
|
|
var row T
|
|
size := binary.Size(row)
|
|
start := int(offset)
|
|
end := start + int(count)*size
|
|
if end > len(data) {
|
|
return nil, fmt.Errorf("table exceeds file bounds")
|
|
}
|
|
|
|
out := make([]T, count)
|
|
reader := bytes.NewReader(data[start:end])
|
|
if err := binary.Read(reader, binary.LittleEndian, &out); err != nil {
|
|
return nil, fmt.Errorf("decode table: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func decodeLocString(data []byte) (LocString, error) {
|
|
if len(data) < 12 {
|
|
return LocString{}, fmt.Errorf("locstring too small")
|
|
}
|
|
totalSize := binary.LittleEndian.Uint32(data[0:4])
|
|
if totalSize+4 != uint32(len(data)) {
|
|
return LocString{}, fmt.Errorf("locstring size mismatch")
|
|
}
|
|
stringRef := binary.LittleEndian.Uint32(data[4:8])
|
|
count := binary.LittleEndian.Uint32(data[8:12])
|
|
pos := 12
|
|
entries := make([]LocStringEntry, 0, count)
|
|
for i := uint32(0); i < count; i++ {
|
|
if pos+8 > len(data) {
|
|
return LocString{}, fmt.Errorf("locstring entry header truncated")
|
|
}
|
|
id := binary.LittleEndian.Uint32(data[pos : pos+4])
|
|
length := binary.LittleEndian.Uint32(data[pos+4 : pos+8])
|
|
pos += 8
|
|
if pos+int(length) > len(data) {
|
|
return LocString{}, fmt.Errorf("locstring entry %d truncated", i)
|
|
}
|
|
entries = append(entries, LocStringEntry{
|
|
ID: id,
|
|
Value: string(data[pos : pos+int(length)]),
|
|
})
|
|
pos += int(length)
|
|
}
|
|
|
|
return LocString{
|
|
StringRef: stringRef,
|
|
Entries: entries,
|
|
}, nil
|
|
}
|
|
|
|
type binaryEncoder struct {
|
|
document Document
|
|
structs []rawStruct
|
|
fields []rawField
|
|
labels []string
|
|
labelIndex map[string]uint32
|
|
fieldData bytes.Buffer
|
|
fieldIndices []uint32
|
|
listIndices []uint32
|
|
}
|
|
|
|
func newBinaryEncoder(doc Document) *binaryEncoder {
|
|
return &binaryEncoder{
|
|
document: doc,
|
|
labelIndex: map[string]uint32{},
|
|
}
|
|
}
|
|
|
|
func (e *binaryEncoder) encode() ([]byte, error) {
|
|
if err := e.addStruct(e.document.Root); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
hdr := header{}
|
|
copy(hdr.FileType[:], []byte(padFour(e.document.FileType)))
|
|
copy(hdr.FileVersion[:], []byte(padFour(defaultString(e.document.FileVersion, "V3.2"))))
|
|
|
|
hdr.StructOffset = headerSize
|
|
hdr.StructCount = uint32(len(e.structs))
|
|
hdr.FieldOffset = hdr.StructOffset + uint32(len(e.structs))*12
|
|
hdr.FieldCount = uint32(len(e.fields))
|
|
hdr.LabelOffset = hdr.FieldOffset + uint32(len(e.fields))*12
|
|
hdr.LabelCount = uint32(len(e.labels))
|
|
hdr.FieldDataOffset = hdr.LabelOffset + uint32(len(e.labels))*16
|
|
hdr.FieldDataCount = uint32(e.fieldData.Len())
|
|
hdr.FieldIndicesOffset = hdr.FieldDataOffset + uint32(e.fieldData.Len())
|
|
hdr.FieldIndicesCount = uint32(len(e.fieldIndices) * 4)
|
|
hdr.ListIndicesOffset = hdr.FieldIndicesOffset + uint32(len(e.fieldIndices))*4
|
|
hdr.ListIndicesCount = uint32(len(e.listIndices) * 4)
|
|
|
|
if err := binary.Write(&buf, binary.LittleEndian, hdr); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Write(&buf, binary.LittleEndian, e.structs); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Write(&buf, binary.LittleEndian, e.fields); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, label := range e.labels {
|
|
var raw [16]byte
|
|
copy(raw[:], []byte(label))
|
|
if _, err := buf.Write(raw[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err := buf.Write(e.fieldData.Bytes()); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Write(&buf, binary.LittleEndian, e.fieldIndices); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := binary.Write(&buf, binary.LittleEndian, e.listIndices); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (e *binaryEncoder) addStruct(s Struct) error {
|
|
index := uint32(len(e.structs))
|
|
e.structs = append(e.structs, rawStruct{Type: s.Type})
|
|
|
|
fieldIndices := make([]uint32, 0, len(s.Fields))
|
|
for _, field := range s.Fields {
|
|
fieldIndex, err := e.addField(field)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldIndices = append(fieldIndices, fieldIndex)
|
|
}
|
|
|
|
entry := &e.structs[index]
|
|
entry.FieldCount = uint32(len(fieldIndices))
|
|
switch len(fieldIndices) {
|
|
case 0:
|
|
entry.DataOrOffset = 0
|
|
case 1:
|
|
entry.DataOrOffset = fieldIndices[0]
|
|
default:
|
|
entry.DataOrOffset = uint32(len(e.fieldIndices) * 4)
|
|
e.fieldIndices = append(e.fieldIndices, fieldIndices...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *binaryEncoder) addField(field Field) (uint32, error) {
|
|
labelIndex, err := e.indexLabel(field.Label)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
data, err := e.encodeValue(field.Value)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("encode field %q: %w", field.Label, err)
|
|
}
|
|
|
|
entry := rawField{
|
|
Type: uint32(field.Type),
|
|
LabelIndex: labelIndex,
|
|
DataOrOffset: data,
|
|
}
|
|
index := uint32(len(e.fields))
|
|
e.fields = append(e.fields, entry)
|
|
return index, nil
|
|
}
|
|
|
|
func (e *binaryEncoder) encodeValue(value Value) (uint32, error) {
|
|
switch typed := value.(type) {
|
|
case ByteValue:
|
|
return uint32(uint8(typed)), nil
|
|
case CharValue:
|
|
return uint32(uint8(typed)), nil
|
|
case WordValue:
|
|
return uint32(uint16(typed)), nil
|
|
case ShortValue:
|
|
return uint32(uint16(typed)), nil
|
|
case DWordValue:
|
|
return uint32(typed), nil
|
|
case IntValue:
|
|
return uint32(int32(typed)), nil
|
|
case FloatValue:
|
|
return math.Float32bits(float32(typed)), nil
|
|
case DWord64Value:
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
return binary.Write(buf, binary.LittleEndian, uint64(typed))
|
|
}), nil
|
|
case Int64Value:
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
return binary.Write(buf, binary.LittleEndian, int64(typed))
|
|
}), nil
|
|
case DoubleValue:
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
return binary.Write(buf, binary.LittleEndian, math.Float64bits(float64(typed)))
|
|
}), nil
|
|
case StringValue:
|
|
return e.writeLengthPrefixed([]byte(typed)), nil
|
|
case ResRefValue:
|
|
if len(typed) > 255 {
|
|
return 0, fmt.Errorf("resref exceeds 255 bytes")
|
|
}
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
if err := buf.WriteByte(byte(len(typed))); err != nil {
|
|
return err
|
|
}
|
|
_, err := buf.Write([]byte(typed))
|
|
return err
|
|
}), nil
|
|
case LocString:
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
payload := bytes.Buffer{}
|
|
if err := binary.Write(&payload, binary.LittleEndian, typed.StringRef); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(&payload, binary.LittleEndian, uint32(len(typed.Entries))); err != nil {
|
|
return err
|
|
}
|
|
for _, entry := range typed.Entries {
|
|
if err := binary.Write(&payload, binary.LittleEndian, entry.ID); err != nil {
|
|
return err
|
|
}
|
|
if err := binary.Write(&payload, binary.LittleEndian, uint32(len(entry.Value))); err != nil {
|
|
return err
|
|
}
|
|
if _, err := payload.Write([]byte(entry.Value)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := binary.Write(buf, binary.LittleEndian, uint32(payload.Len())); err != nil {
|
|
return err
|
|
}
|
|
if _, err := buf.Write(payload.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}), nil
|
|
case VoidValue:
|
|
return e.writeLengthPrefixed([]byte(typed)), nil
|
|
case Struct:
|
|
index := uint32(len(e.structs))
|
|
if err := e.addStruct(typed); err != nil {
|
|
return 0, err
|
|
}
|
|
return index, nil
|
|
case ListValue:
|
|
return e.writeList(typed)
|
|
case Orientation:
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
for _, component := range []float32{typed.X, typed.Y, typed.Z, typed.W} {
|
|
if err := binary.Write(buf, binary.LittleEndian, math.Float32bits(component)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}), nil
|
|
case Vector:
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
for _, component := range []float32{typed.X, typed.Y, typed.Z} {
|
|
if err := binary.Write(buf, binary.LittleEndian, math.Float32bits(component)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}), nil
|
|
default:
|
|
return 0, fmt.Errorf("unsupported value type %T", value)
|
|
}
|
|
}
|
|
|
|
func (e *binaryEncoder) writeList(list ListValue) (uint32, error) {
|
|
indices := make([]uint32, 0, len(list))
|
|
for _, item := range list {
|
|
index := uint32(len(e.structs))
|
|
if err := e.addStruct(item); err != nil {
|
|
return 0, err
|
|
}
|
|
indices = append(indices, index)
|
|
}
|
|
|
|
offset := uint32(len(e.listIndices) * 4)
|
|
e.listIndices = append(e.listIndices, uint32(len(list)))
|
|
e.listIndices = append(e.listIndices, indices...)
|
|
return offset, nil
|
|
}
|
|
|
|
func (e *binaryEncoder) writeFieldData(write func(*bytes.Buffer) error) uint32 {
|
|
offset := uint32(e.fieldData.Len())
|
|
if err := write(&e.fieldData); err != nil {
|
|
panic(err)
|
|
}
|
|
return offset
|
|
}
|
|
|
|
func (e *binaryEncoder) writeLengthPrefixed(data []byte) uint32 {
|
|
return e.writeFieldData(func(buf *bytes.Buffer) error {
|
|
if err := binary.Write(buf, binary.LittleEndian, uint32(len(data))); err != nil {
|
|
return err
|
|
}
|
|
_, err := buf.Write(data)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (e *binaryEncoder) indexLabel(label string) (uint32, error) {
|
|
if len(label) > 16 {
|
|
return 0, fmt.Errorf("label %q exceeds 16 bytes", label)
|
|
}
|
|
if index, ok := e.labelIndex[label]; ok {
|
|
return index, nil
|
|
}
|
|
index := uint32(len(e.labels))
|
|
e.labels = append(e.labels, label)
|
|
e.labelIndex[label] = index
|
|
return index, nil
|
|
}
|
|
|
|
func padFour(value string) string {
|
|
if len(value) >= 4 {
|
|
return value[:4]
|
|
}
|
|
return value + string(bytes.Repeat([]byte(" "), 4-len(value)))
|
|
}
|
|
|
|
func defaultString(value, fallback string) string {
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|