Files

318 lines
7.1 KiB
Go

package gff
import (
"encoding/base64"
"encoding/json"
"fmt"
)
type jsonDocument struct {
FileType string `json:"file_type"`
FileVersion string `json:"file_version"`
Root jsonStruct `json:"root"`
}
type jsonStruct struct {
StructType uint32 `json:"struct_type"`
Fields []jsonField `json:"fields"`
}
type jsonField struct {
Label string `json:"label"`
Type string `json:"type"`
Value json.RawMessage `json:"value"`
}
type jsonLocString struct {
StringRef uint32 `json:"string_ref"`
Entries []LocStringEntry `json:"entries"`
}
func (s Struct) MarshalJSON() ([]byte, error) {
payload, err := marshalStruct(s)
if err != nil {
return nil, err
}
return json.Marshal(payload)
}
func (s *Struct) UnmarshalJSON(data []byte) error {
var payload jsonStruct
if err := json.Unmarshal(data, &payload); err != nil {
return err
}
decoded, err := unmarshalStruct(payload)
if err != nil {
return err
}
*s = decoded
return nil
}
func (d Document) MarshalJSON() ([]byte, error) {
root, err := marshalStruct(d.Root)
if err != nil {
return nil, err
}
return json.Marshal(jsonDocument{
FileType: d.FileType,
FileVersion: d.FileVersion,
Root: root,
})
}
func (d *Document) UnmarshalJSON(data []byte) error {
var payload jsonDocument
if err := json.Unmarshal(data, &payload); err != nil {
return err
}
root, err := unmarshalStruct(payload.Root)
if err != nil {
return err
}
d.FileType = payload.FileType
d.FileVersion = payload.FileVersion
d.Root = root
return nil
}
func marshalStruct(in Struct) (jsonStruct, error) {
fields := make([]jsonField, 0, len(in.Fields))
for _, field := range in.Fields {
payload, err := marshalValue(field.Value)
if err != nil {
return jsonStruct{}, fmt.Errorf("marshal field %q: %w", field.Label, err)
}
fields = append(fields, jsonField{
Label: field.Label,
Type: field.Type.String(),
Value: payload,
})
}
return jsonStruct{
StructType: in.Type,
Fields: fields,
}, nil
}
func unmarshalStruct(in jsonStruct) (Struct, error) {
fields := make([]Field, 0, len(in.Fields))
for _, field := range in.Fields {
ft, err := parseFieldType(field.Type)
if err != nil {
return Struct{}, fmt.Errorf("field %q: %w", field.Label, err)
}
value, err := unmarshalValue(ft, field.Value)
if err != nil {
return Struct{}, fmt.Errorf("field %q: %w", field.Label, err)
}
fields = append(fields, Field{
Label: field.Label,
Type: ft,
Value: value,
})
}
return Struct{
Type: in.StructType,
Fields: fields,
}, nil
}
func marshalValue(value Value) (json.RawMessage, error) {
switch typed := value.(type) {
case ByteValue:
return json.Marshal(uint8(typed))
case CharValue:
return json.Marshal(int8(typed))
case WordValue:
return json.Marshal(uint16(typed))
case ShortValue:
return json.Marshal(int16(typed))
case DWordValue:
return json.Marshal(uint32(typed))
case IntValue:
return json.Marshal(int32(typed))
case DWord64Value:
return json.Marshal(uint64(typed))
case Int64Value:
return json.Marshal(int64(typed))
case FloatValue:
return json.Marshal(float32(typed))
case DoubleValue:
return json.Marshal(float64(typed))
case StringValue:
return json.Marshal(string(typed))
case ResRefValue:
return json.Marshal(string(typed))
case LocString:
return json.Marshal(jsonLocString(typed))
case VoidValue:
return json.Marshal(base64.StdEncoding.EncodeToString([]byte(typed)))
case Struct:
payload, err := marshalStruct(typed)
if err != nil {
return nil, err
}
return json.Marshal(payload)
case ListValue:
payload := make([]jsonStruct, 0, len(typed))
for _, item := range typed {
entry, err := marshalStruct(item)
if err != nil {
return nil, err
}
payload = append(payload, entry)
}
return json.Marshal(payload)
case Orientation:
return json.Marshal(typed)
case Vector:
return json.Marshal(typed)
default:
return nil, fmt.Errorf("unsupported value type %T", value)
}
}
func unmarshalValue(ft FieldType, data []byte) (Value, error) {
switch ft {
case TypeByte:
var out uint8
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return ByteValue(out), nil
case TypeChar:
var out int8
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return CharValue(out), nil
case TypeWord:
var out uint16
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return WordValue(out), nil
case TypeShort:
var out int16
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return ShortValue(out), nil
case TypeDWord:
var out uint32
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return DWordValue(out), nil
case TypeInt:
var out int32
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return IntValue(out), nil
case TypeDWord64:
var out uint64
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return DWord64Value(out), nil
case TypeInt64:
var out int64
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return Int64Value(out), nil
case TypeFloat:
var out float32
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return FloatValue(out), nil
case TypeDouble:
var out float64
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return DoubleValue(out), nil
case TypeCExoString:
var out string
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return StringValue(out), nil
case TypeResRef:
var out string
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return ResRefValue(out), nil
case TypeCExoLocString:
var out jsonLocString
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return LocString(out), nil
case TypeVoid:
var out string
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
decoded, err := base64.StdEncoding.DecodeString(out)
if err != nil {
return nil, fmt.Errorf("decode base64 void: %w", err)
}
return VoidValue(decoded), nil
case TypeStruct:
var payload jsonStruct
if err := json.Unmarshal(data, &payload); err != nil {
return nil, err
}
return unmarshalStruct(payload)
case TypeList:
var payload []jsonStruct
if err := json.Unmarshal(data, &payload); err != nil {
return nil, err
}
out := make(ListValue, 0, len(payload))
for _, item := range payload {
decoded, err := unmarshalStruct(item)
if err != nil {
return nil, err
}
out = append(out, decoded)
}
return out, nil
case TypeOrientation:
var out Orientation
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return out, nil
case TypeVector:
var out Vector
if err := json.Unmarshal(data, &out); err != nil {
return nil, err
}
return out, nil
default:
return nil, fmt.Errorf("unsupported field type %d", ft)
}
}
func parseFieldType(name string) (FieldType, error) {
for kind, candidate := range fieldTypeNames {
if candidate == name {
return kind, nil
}
}
return 0, fmt.Errorf("unknown field type %q", name)
}