func decodeValue(
	valueType rbxapi.Type,
	refs map[int32]*rbxfile.Instance,
	sharedStrings []SharedString,
	bvalue Value,
) (value rbxfile.Value) {
	switch bvalue := bvalue.(type) {
	case *ValueString:
		v := make([]byte, len(*bvalue))
		copy(v, *bvalue)
		if valueType == nil {
			value = rbxfile.ValueString(v)
			break
		}
		switch valueType.GetName() {
		case rbxfile.TypeBinaryString.String():
			value = rbxfile.ValueBinaryString(v)
		case rbxfile.TypeProtectedString.String():
			value = rbxfile.ValueProtectedString(v)
		case rbxfile.TypeContent.String():
			value = rbxfile.ValueContent(v)
		case rbxfile.TypeString.String():
			fallthrough
		default:
			value = rbxfile.ValueString(v)
		}
	case *ValueBool:
		value = rbxfile.ValueBool(*bvalue)
	case *ValueInt:
		value = rbxfile.ValueInt(*bvalue)
	case *ValueFloat:
		value = rbxfile.ValueFloat(*bvalue)
	case *ValueDouble:
		value = rbxfile.ValueDouble(*bvalue)
	case *ValueUDim:
		value = rbxfile.ValueUDim{
			Scale:  float32(bvalue.Scale),
			Offset: int32(bvalue.Offset),
		}
	case *ValueUDim2:
		value = rbxfile.ValueUDim2{
			X: rbxfile.ValueUDim{
				Scale:  float32(bvalue.ScaleX),
				Offset: int32(bvalue.OffsetX),
			},
			Y: rbxfile.ValueUDim{
				Scale:  float32(bvalue.ScaleY),
				Offset: int32(bvalue.OffsetY),
			},
		}
	case *ValueRay:
		value = rbxfile.ValueRay{
			Origin: rbxfile.ValueVector3{
				X: bvalue.OriginX,
				Y: bvalue.OriginY,
				Z: bvalue.OriginZ,
			},
			Direction: rbxfile.ValueVector3{
				X: bvalue.DirectionX,
				Y: bvalue.DirectionY,
				Z: bvalue.DirectionZ,
			},
		}
	case *ValueFaces:
		value = rbxfile.ValueFaces(*bvalue)
	case *ValueAxes:
		value = rbxfile.ValueAxes(*bvalue)
	case *ValueBrickColor:
		value = rbxfile.ValueBrickColor(*bvalue)
	case *ValueColor3:
		value = rbxfile.ValueColor3{
			R: float32(bvalue.R),
			G: float32(bvalue.G),
			B: float32(bvalue.B),
		}
	case *ValueVector2:
		value = rbxfile.ValueVector2{
			X: float32(bvalue.X),
			Y: float32(bvalue.Y),
		}
	case *ValueVector3:
		value = rbxfile.ValueVector3{
			X: float32(bvalue.X),
			Y: float32(bvalue.Y),
			Z: float32(bvalue.Z),
		}
	case *ValueVector2int16:
		value = rbxfile.ValueVector2int16(*bvalue)
	case *ValueCFrame:
		cf := rbxfile.ValueCFrame{
			Position: rbxfile.ValueVector3{
				X: float32(bvalue.Position.X),
				Y: float32(bvalue.Position.Y),
				Z: float32(bvalue.Position.Z),
			},
			Rotation: bvalue.Rotation,
		}
		if bvalue.Special != 0 {
			cf.Rotation = cframeSpecialMatrix[bvalue.Special]
		}
		value = cf
	case *ValueToken:
		value = rbxfile.ValueToken(*bvalue)
	case *ValueReference:
		value = rbxfile.ValueReference{Instance: refs[int32(*bvalue)]}
	case *ValueVector3int16:
		value = rbxfile.ValueVector3int16(*bvalue)
	case *ValueNumberSequence:
		v := make(rbxfile.ValueNumberSequence, len(*bvalue))
		for i, nsk := range *bvalue {
			v[i] = rbxfile.ValueNumberSequenceKeypoint{
				Time:     nsk.Time,
				Value:    nsk.Value,
				Envelope: nsk.Envelope,
			}
		}
		value = v
	case *ValueColorSequence:
		v := make(rbxfile.ValueColorSequence, len(*bvalue))
		for i, nsk := range *bvalue {
			v[i] = rbxfile.ValueColorSequenceKeypoint{
				Time: nsk.Time,
				Value: rbxfile.ValueColor3{
					R: float32(nsk.Value.R),
					G: float32(nsk.Value.G),
					B: float32(nsk.Value.B),
				},
				Envelope: nsk.Envelope,
			}
		}
		value = v
	case *ValueNumberRange:
		value = rbxfile.ValueNumberRange(*bvalue)
	case *ValueRect2D:
		value = rbxfile.ValueRect2D{
			Min: rbxfile.ValueVector2{
				X: float32(bvalue.Min.X),
				Y: float32(bvalue.Min.Y),
			},
			Max: rbxfile.ValueVector2{
				X: float32(bvalue.Max.X),
				Y: float32(bvalue.Max.Y),
			},
		}
	case *ValuePhysicalProperties:
		value = rbxfile.ValuePhysicalProperties{
			CustomPhysics:    bvalue.CustomPhysics != 0,
			Density:          bvalue.Density,
			Friction:         bvalue.Friction,
			Elasticity:       bvalue.Elasticity,
			FrictionWeight:   bvalue.FrictionWeight,
			ElasticityWeight: bvalue.ElasticityWeight,
		}
	case *ValueColor3uint8:
		value = rbxfile.ValueColor3uint8{
			R: bvalue.R,
			G: bvalue.G,
			B: bvalue.B,
		}
	case *ValueInt64:
		value = rbxfile.ValueInt64(*bvalue)
	case *ValueSharedString:
		i := int(*bvalue)
		if i < 0 || i >= len(sharedStrings) {
			value = rbxfile.ValueSharedString("")
			break
		}
		value = rbxfile.ValueSharedString(sharedStrings[i].Value)
	}
	return
}
Decode a bin.value to a rbxfile.Value based on a given value type.
package bin
import (
	"crypto/md5"
	"errors"
	"fmt"
	"github.com/robloxapi/rbxapi"
	"github.com/robloxapi/rbxfile"
	"sort"
)
type Mode uint8
const (
	ModePlace Mode = iota  
	ModeModel              
)
type RobloxCodec struct {
	API rbxapi.Root
	Mode Mode
	ExcludeInvalidAPI bool
}
func (c RobloxCodec) Decode(model *FormatModel) (root *rbxfile.Root, err error) {
	if model == nil {
		return nil, fmt.Errorf("FormatModel is nil")
	}
	model.Warnings = model.Warnings[:0]
	root = new(rbxfile.Root)
	groupLookup := make(map[int32]*ChunkInstance, model.TypeCount)
	instLookup := make(map[int32]*rbxfile.Instance, model.InstanceCount+1)
	instLookup[-1] = nil
	propTypes := map[string]map[string]rbxapi.Type{}
	var sharedStrings []SharedString
	enumCache := map[string]enumItems{}
	var chunkType string
	var chunkNum int
	addWarn := func(format string, v ...interface{}) {
		q := make([]interface{}, 0, len(v)+2)
		q = append(q, chunkType)
		q = append(q, chunkNum)
		q = append(q, v...)
		model.Warnings = append(model.Warnings, fmt.Errorf("%s chunk (#%d): "+format, q...))
	}
loop:
	for ic, chunk := range model.Chunks {
		chunkNum = ic
		switch chunk := chunk.(type) {
		case *ChunkInstance:
			chunkType = "instance"
			if chunk.TypeID < 0 || uint32(chunk.TypeID) >= model.TypeCount {
				err = fmt.Errorf("type index out of bounds: %d", model.TypeCount)
				goto chunkErr
			}
			if c.API != nil {
				class := c.API.GetClass(chunk.ClassName)
				if class == nil {
					addWarn("invalid ClassName `%s`", chunk.ClassName)
					if c.ExcludeInvalidAPI {
						continue
					}
				}
				if _, ok := propTypes[chunk.ClassName]; !ok {
					props := map[string]rbxapi.Type{}
					for _, member := range class.GetMembers() {
						if member, ok := member.(rbxapi.Property); ok {
							props[member.GetName()] = member.GetValueType()
							enum := c.API.GetEnum(member.GetValueType().GetName())
							if enum == nil {
								continue
							}
							items, ok := enumCache[member.GetValueType().GetName()]
							if !ok {
								itemList := enum.GetEnumItems()
								items = enumItems{
									first:  itemList[0].GetValue(),
									values: make(map[int]bool, len(itemList)),
								}
								for _, item := range itemList {
									items.values[item.GetValue()] = true
								}
								enumCache[member.GetValueType().GetName()] = items
							}
						}
					}
					propTypes[chunk.ClassName] = props
				}
			}
			if chunk.IsService && len(chunk.InstanceIDs) != len(chunk.GetService) {
				err = fmt.Errorf("malformed instance chunk (type ID %d): GetService array length does not equal InstanceIDs array length", chunk.TypeID)
				goto chunkErr
			}
			for i, ref := range chunk.InstanceIDs {
				if ref < 0 || uint32(ref) >= model.InstanceCount {
					err = fmt.Errorf("invalid id %d", ref)
					goto chunkErr
				}
				inst := rbxfile.NewInstance(chunk.ClassName, nil)
				if _, ok := instLookup[ref]; ok {
					err = fmt.Errorf("duplicate id: %d", ref)
					goto chunkErr
				}
				if chunk.IsService && chunk.GetService[i] == 1 {
					inst.IsService = true
				}
				instLookup[ref] = inst
			}
			if _, ok := groupLookup[chunk.TypeID]; ok {
				err = fmt.Errorf("duplicate type index: %d", chunk.TypeID)
				goto chunkErr
			}
			groupLookup[chunk.TypeID] = chunk
		case *ChunkProperty:
			chunkType = "property"
			if chunk.TypeID < 0 || uint32(chunk.TypeID) >= model.TypeCount {
				err = fmt.Errorf("type index out of bounds: %d", model.TypeCount)
				goto chunkErr
			}
			instChunk, ok := groupLookup[chunk.TypeID]
			if !ok {
				addWarn("type `%d` of property group is invalid or unknown", chunk.TypeID)
				continue
			}
			if len(chunk.Properties) != len(instChunk.InstanceIDs) {
				err = fmt.Errorf("length of properties array (%d) does not equal length of type array (%d)", len(chunk.Properties), len(instChunk.InstanceIDs))
				goto chunkErr
			}
			var propType rbxapi.Type
			if c.API != nil {
				var ok bool
				if propType, ok = propTypes[instChunk.ClassName][chunk.PropertyName]; !ok {
					addWarn("chunk name `%s` is not a valid property of the group class `%s`", chunk.PropertyName, instChunk.ClassName)
					if c.ExcludeInvalidAPI {
						continue
					}
				}
			}
			for i, bvalue := range chunk.Properties {
				if c.API != nil && bvalue.Type() == TypeToken {
					if items, ok := enumCache[propType.GetName()]; ok {
						token := bvalue.(*ValueToken)
						if !items.values[int(*token)] {
							v := items.first
							*token = ValueToken(v)
						}
					}
				}
				inst := instLookup[instChunk.InstanceIDs[i]]
				inst.Properties[chunk.PropertyName] = decodeValue(propType, instLookup, sharedStrings, bvalue)
			}
		case *ChunkParent:
			chunkType = "parent"
			if chunk.Version != 0 {
				err = fmt.Errorf("unrecognized parent link format %d", chunk.Version)
				goto chunkErr
			}
			if len(chunk.Parents) != len(chunk.Children) {
				err = fmt.Errorf("length of Parents array does not equal length of Children array")
				goto chunkErr
			}
			for i, ref := range chunk.Children {
				if ref < 0 || uint32(ref) >= model.InstanceCount {
					err = fmt.Errorf("invalid id %d", ref)
					goto chunkErr
				}
				child := instLookup[ref]
				if child == nil {
					addWarn("referent #%d `%d` does not exist", i, ref)
					continue
				}
				if chunk.Parents[i] == -1 {
					root.Instances = append(root.Instances, child)
					continue
				}
				parent, ok := instLookup[chunk.Parents[i]]
				if !ok {
					continue
				}
				if err = parent.AddChild(child); err != nil {
					goto chunkErr
				}
			}
		case *ChunkMeta:
			chunkType = "meta"
			if root.Metadata == nil {
				root.Metadata = make(map[string]string, len(chunk.Values))
			}
			for _, pair := range chunk.Values {
				root.Metadata[pair[0]] = pair[1]
			}
		case *ChunkSharedStrings:
			chunkType = "sharedstring"
			sharedStrings = chunk.Values
		case *ChunkEnd:
			chunkType = "end"
			break loop
		}
	}
	return
chunkErr:
	err = fmt.Errorf("%s chunk (#%d): %s", chunkType, chunkNum, err)
	return nil, err
}