rbxcompiler/vendor/git.itzana.me/itzaname/rbxfile/file.go
2023-03-05 19:58:54 -05:00

432 lines
13 KiB
Go

// The rbxfile package handles the decoding, encoding, and manipulation of
// Roblox instance data structures.
//
// This package can be used to manipulate Roblox instance trees outside of the
// Roblox client. Such data structures begin with a Root struct. A Root
// contains a list of child Instances, which in turn contain more child
// Instances, and so on, forming a tree of Instances. These Instances can be
// accessed and manipulated using an API similar to that of Roblox.
//
// Each Instance also has a set of "properties". Each property has a specific
// value of a certain type. Every available type implements the Value
// interface, and is prefixed with "Value".
//
// Root structures can be decoded from and encoded to various formats,
// including Roblox's native file formats. The two sub-packages "bin" and
// "xml" provide formats for Roblox's binary and XML formats. Root structures
// can also be encoded and decoded with the "json" package.
//
// Besides decoding from a format, root structures can also be created
// manually. The best way to do this is through the "declare" sub-package,
// which provides an easy way to generate root structures.
package rbxfile
import (
"errors"
)
////////////////////////////////////////////////////////////////
// Root represents the root of an instance tree. Root is not itself an
// instance, but a container for multiple root instances.
type Root struct {
// Instances contains root instances contained in the tree.
Instances []*Instance
// Metadata contains metadata about the tree.
Metadata map[string]string
}
// NewRoot returns a new initialized Root.
func NewRoot() *Root {
return &Root{
Instances: []*Instance{},
Metadata: map[string]string{},
}
}
// Copy creates a copy of the root and its contents.
//
// A copied reference within the tree is resolved so that it points to the
// corresponding copy of the original referent. Copied references that point
// to an instance which isn't being copied will still point to the same
// instance.
func (root *Root) Copy() *Root {
clone := &Root{
Instances: make([]*Instance, len(root.Instances)),
}
refs := make(References)
crefs := make(References)
propRefs := make([]PropRef, 0, 8)
for i, inst := range root.Instances {
clone.Instances[i] = inst.clone(refs, crefs, &propRefs)
}
for _, propRef := range propRefs {
if !crefs.Resolve(propRef) {
// Refers to an instance outside the tree, try getting the
// original referent.
refs.Resolve(propRef)
}
}
return clone
}
// Instance represents a single Roblox instance.
type Instance struct {
// ClassName indicates the instance's type.
ClassName string
// Reference is a unique string used to refer to the instance from
// elsewhere in the tree.
Reference string
// IsService indicates whether the instance should be treated as a
// service.
IsService bool
// Properties is a map of properties of the instance. It maps the name of
// the property to its current value.
Properties map[string]Value
// Children contains instances that are the children of the current
// instance. If this field is set directly, then FixTree should be called
// afterwards to ensure the correctness of the tree.
Children []*Instance
// The parent of the instance. Can be nil.
parent *Instance
}
// NewInstance creates a new Instance of a given class, and an optional
// parent.
func NewInstance(className string, parent *Instance) *Instance {
inst := &Instance{
ClassName: className,
Reference: GenerateReference(),
Properties: make(map[string]Value, 0),
}
if parent != nil {
parent.Children = append(parent.Children, inst)
inst.parent = parent
}
return inst
}
// assertLoop returns an error if an instance being the child of a parent would
// create a circular reference.
func assertLoop(child, parent *Instance) error {
if parent == child {
return errors.New("attempt to set instance as its own parent")
}
if parent != nil && parent.IsDescendantOf(child) {
return errors.New("attempt to set parent would result in circular reference")
}
return nil
}
// addChild appends a child to the instance, and sets its parent. If the child
// is already the child of another instance, it is first removed.
func (inst *Instance) addChild(child *Instance) {
if child.parent != nil {
child.parent.RemoveChild(child)
}
inst.Children = append(inst.Children, child)
child.parent = inst
}
// AddChild appends a child instance to the instance's list of children. If
// the child has a parent, it is first removed. The parent of the child is set
// to the instance. An error is returned if the instance is a descendant of
// the child, or if the child is the instance itself.
func (inst *Instance) AddChild(child *Instance) error {
if err := assertLoop(child, inst); err != nil {
return err
}
inst.addChild(child)
return nil
}
// AddChildAt inserts a child instance into the instance's list of children at
// a given position. If the child has a parent, it is first removed. The
// parent of the child is set to the instance. If the index is outside the
// bounds of the list, then it is constrained. An error is returned if the
// instance is a descendant of the child, or if the child is the instance
// itself.
func (inst *Instance) AddChildAt(index int, child *Instance) error {
if err := assertLoop(child, inst); err != nil {
return err
}
if index < 0 {
index = 0
} else if index >= len(inst.Children) {
inst.addChild(child)
return nil
}
if child.parent != nil {
child.parent.RemoveChild(child)
}
inst.Children = append(inst.Children, nil)
copy(inst.Children[index+1:], inst.Children[index:])
inst.Children[index] = child
child.parent = inst
return nil
}
// removeChildAt removes the child at the given index, which is assumed to be
// within bounds.
func (inst *Instance) removeChildAt(index int) (child *Instance) {
child = inst.Children[index]
child.parent = nil
copy(inst.Children[index:], inst.Children[index+1:])
inst.Children[len(inst.Children)-1] = nil
inst.Children = inst.Children[:len(inst.Children)-1]
return child
}
// RemoveChild removes a child instance from the instance's list of children.
// The parent of the child is set to nil. Returns the removed child.
func (inst *Instance) RemoveChild(child *Instance) *Instance {
for index, c := range inst.Children {
if c == child {
return inst.removeChildAt(index)
}
}
return nil
}
// RemoveChildAt removes the child at a given position from the instance's
// list of children. The parent of the child is set to nil. If the index is
// outside the bounds of the list, then no children are removed. Returns the
// removed child.
func (inst *Instance) RemoveChildAt(index int) *Instance {
if index < 0 || index >= len(inst.Children) {
return nil
}
return inst.removeChildAt(index)
}
// RemoveAll remove every child from the instance. The parent of each child is
// set to nil.
func (inst *Instance) RemoveAll() {
for i, child := range inst.Children {
child.parent = nil
inst.Children[i] = nil
}
inst.Children = inst.Children[:0]
}
// Parent returns the parent of the instance. Can return nil if the instance
// has no parent.
func (inst *Instance) Parent() *Instance {
return inst.parent
}
// SetParent sets the parent of the instance, removing itself from the
// children of the old parent, and adding itself as a child of the new parent.
// The parent can be set to nil. An error is returned if the parent is a
// descendant of the instance, or if the parent is the instance itself. If the
// new parent is the same as the old parent, then the position of the instance
// in the parent's children is unchanged.
func (inst *Instance) SetParent(parent *Instance) error {
if inst.parent == parent {
return nil
}
if err := assertLoop(inst, parent); err != nil {
return err
}
if inst.parent != nil {
inst.parent.RemoveChild(inst)
}
if parent != nil {
parent.addChild(inst)
}
return nil
}
// FixTree walks through the descendants of the instance tree top-down and
// ensures that the parental references are correct. Any descendants that cause
// a circular reference are removed and not traversed.
func (inst *Instance) FixTree() {
for i := 0; i < len(inst.Children); {
child := inst.Children[i]
if err := assertLoop(child, inst); err != nil {
inst.removeChildAt(i)
continue
}
child.parent = inst
child.FixTree()
i++
}
}
// clone returns a deep copy of the instance while managing references.
func (inst *Instance) clone(refs, crefs References, propRefs *[]PropRef) *Instance {
clone := &Instance{
ClassName: inst.ClassName,
Reference: refs.Get(inst),
IsService: inst.IsService,
Children: make([]*Instance, len(inst.Children)),
Properties: make(map[string]Value, len(inst.Properties)),
}
crefs[clone.Reference] = clone
for name, value := range inst.Properties {
if value, ok := value.(ValueReference); ok {
*propRefs = append(*propRefs, PropRef{
Instance: clone,
Property: name,
Reference: refs.Get(value.Instance),
})
continue
}
clone.Properties[name] = value.Copy()
}
for i, child := range inst.Children {
c := child.clone(refs, crefs, propRefs)
clone.Children[i] = c
c.parent = clone
}
return clone
}
// Clone returns a copy of the instance. Each property and all descendants are
// copied as well. Unlike Roblox's implementation, the Archivable property is
// ignored.
//
// A copied reference within the tree is resolved so that it points to the
// corresponding copy of the original referent. Copied references that point
// to an instance which isn't being copied will still point to the same
// instance.
func (inst *Instance) Clone() *Instance {
refs := make(References)
crefs := make(References)
propRefs := make([]PropRef, 0, 8)
clone := inst.clone(refs, crefs, &propRefs)
for _, propRef := range propRefs {
if !crefs.Resolve(propRef) {
// Refers to an instance outside the tree, try getting the
// original referent.
refs.Resolve(propRef)
}
}
return clone
}
// FindFirstChild returns the first found child whose Name property matches
// the given name. Returns nil if no child was found. If recursive is true,
// then FindFirstChild will be called on descendants as well.
func (inst *Instance) FindFirstChild(name string, recursive bool) *Instance {
for _, child := range inst.Children {
if child.Name() == name {
return child
}
}
if recursive {
for _, child := range inst.Children {
if desc := child.FindFirstChild(name, true); desc != nil {
return desc
}
}
}
return nil
}
// GetFullName returns the "full" name of the instance, which is the combined
// names of the instance and every ancestor, separated by a `.` character.
func (inst *Instance) GetFullName() string {
// Note: Roblox's GetFullName stops at the first ancestor that is a
// ServiceProvider. Since recreating this behavior would require
// information about the class hierarchy, this implementation simply
// includes every ancestor.
names := make([]string, 0, 8)
object := inst
for object != nil {
names = append(names, object.Name())
object = object.Parent()
}
full := make([]byte, 0, 64)
for i := len(names) - 1; i > 0; i-- {
full = append(full, []byte(names[i])...)
full = append(full, '.')
}
full = append(full, []byte(names[0])...)
return string(full)
}
// IsAncestorOf returns whether the instance is the ancestor of another
// instance.
func (inst *Instance) IsAncestorOf(descendant *Instance) bool {
if descendant != nil {
return descendant.IsDescendantOf(inst)
}
return false
}
// IsDescendantOf returns whether the instance is the descendant of another
// instance.
func (inst *Instance) IsDescendantOf(ancestor *Instance) bool {
parent := inst.Parent()
for parent != nil {
if parent == ancestor {
return true
}
parent = parent.Parent()
}
return false
}
// Name returns the Name property of the instance, or an empty string if it is
// invalid or not defined.
func (inst *Instance) Name() string {
iname, ok := inst.Properties["Name"]
if !ok {
return ""
}
name, _ := iname.(ValueString)
return string(name)
}
// String implements the fmt.Stringer interface by returning the Name of the
// instance, or the ClassName if Name isn't defined.
func (inst *Instance) String() string {
iname, ok := inst.Properties["Name"]
if !ok {
return inst.ClassName
}
name, _ := iname.(ValueString)
if string(name) == "" {
return inst.ClassName
}
return string(name)
}
// SetName sets the Name property of the instance.
func (inst *Instance) SetName(name string) {
inst.Properties["Name"] = ValueString(name)
}
// Get returns the value of a property in the instance. The value will be nil
// if the property is not defined.
func (inst *Instance) Get(property string) (value Value) {
return inst.Properties[property]
}
// Set sets the value of a property in the instance. If value is nil, then the
// value will be deleted from the Properties map.
func (inst *Instance) Set(property string, value Value) {
if value == nil {
delete(inst.Properties, property)
} else {
inst.Properties[property] = value
}
}