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) }