Example #1
0
        public void Load(BinaryRobloxFileReader reader)
        {
            BinaryRobloxFile file = reader.File;

            int format    = reader.ReadInt32();
            int numHashes = reader.ReadInt32();

            if (format != FORMAT)
            {
                throw new Exception($"Unexpected SSTR format: {format} (expected {FORMAT}!)");
            }

            for (uint id = 0; id < numHashes; id++)
            {
                byte[] hash = reader.ReadBytes(16);
                string key  = Convert.ToBase64String(hash);

                byte[]       data  = reader.ReadBuffer();
                SharedString value = SharedString.FromBuffer(data);

                Lookup[key] = id;
                Strings[id] = value;
            }

            file.SSTR = this;
        }
Example #2
0
        public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
        {
            var    value = prop.CastValue <SharedString>();
            string key   = value.Key;

            if (value.ComputedKey == null)
            {
                var newShared = SharedString.FromBuffer(value.SharedValue);
                key = newShared.ComputedKey;
            }

            node.InnerText = key;
        }
Example #3
0
        protected override void ReadFile(byte[] buffer)
        {
            try
            {
                string xml      = Encoding.UTF8.GetString(buffer);
                var    settings = new XmlReaderSettings()
                {
                    XmlResolver = null
                };

                using (StringReader reader = new StringReader(xml))
                {
                    XmlReader xmlReader = XmlReader.Create(reader, settings);
                    XmlDocument.Load(xmlReader);
                    xmlReader.Dispose();
                }
            }
            catch
            {
                throw new Exception("XmlRobloxFile: Could not read provided buffer as XML!");
            }

            XmlNode roblox = XmlDocument.FirstChild;

            if (roblox != null && roblox.Name == "roblox")
            {
                // Verify the version we are using.
                XmlNode version = roblox.Attributes.GetNamedItem("version");

                if (version == null || !int.TryParse(version.Value, out int schemaVersion))
                {
                    throw new Exception("XmlRobloxFile: No version number defined!");
                }
                else if (schemaVersion < 4)
                {
                    throw new Exception("XmlRobloxFile: Provided version must be at least 4!");
                }

                // Process the instances.
                foreach (XmlNode child in roblox.ChildNodes)
                {
                    if (child.Name == "Item")
                    {
                        Instance item = XmlRobloxFileReader.ReadInstance(child, this);
                        item.Parent = this;
                    }
                    else if (child.Name == "SharedStrings")
                    {
                        XmlRobloxFileReader.ReadSharedStrings(child, this);
                    }
                    else if (child.Name == "Meta")
                    {
                        XmlRobloxFileReader.ReadMetadata(child, this);
                    }
                }

                // Query the properties.
                var allProps = Instances.Values
                               .SelectMany(inst => inst.Properties)
                               .Select(pair => pair.Value);

                // Resolve referent properties.
                var refProps = allProps.Where(prop => prop.Type == PropertyType.Ref);

                foreach (Property refProp in refProps)
                {
                    string refId = refProp.XmlToken;
                    refProp.XmlToken = "Ref";

                    if (Instances.ContainsKey(refId))
                    {
                        Instance refInst = Instances[refId];
                        refProp.Value = refInst;
                    }
                    else if (refId != "null")
                    {
                        LogError($"XmlRobloxFile: Could not resolve reference for {refProp.GetFullName()}");
                        refProp.Value = null;
                    }
                }

                // Record shared strings.
                var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString);

                foreach (Property sharedProp in sharedProps)
                {
                    SharedString shared = sharedProp.CastValue <SharedString>();

                    if (shared == null)
                    {
                        var nullBuffer = Array.Empty <byte>();
                        shared           = SharedString.FromBuffer(nullBuffer);
                        sharedProp.Value = shared;
                    }

                    SharedStrings.Add(shared.Key);
                }
            }
            else
            {
                throw new Exception("XmlRobloxFile: No 'roblox' element found!");
            }
        }
        public void Save(BinaryRobloxFileWriter writer)
        {
            BinaryRobloxFile file = writer.File;

            File = file;

            INST inst  = file.Classes[ClassIndex];
            var  props = new List <Property>();

            foreach (int instId in inst.InstanceIds)
            {
                Instance instance  = file.Instances[instId];
                var      instProps = instance.Properties;

                if (!instProps.TryGetValue(Name, out Property prop))
                {
                    throw new Exception($"Property {Name} must be defined in {instance.GetFullName()}!");
                }
                else if (prop.Type != Type)
                {
                    throw new Exception($"Property {Name} is not using the correct type in {instance.GetFullName()}!");
                }

                props.Add(prop);
            }

            writer.Write(ClassIndex);
            writer.WriteString(Name);
            writer.Write(TypeId);

            switch (Type)
            {
            case PropertyType.String:
                props.ForEach(prop =>
                {
                    byte[] buffer = prop.HasRawBuffer ? prop.RawBuffer : null;

                    if (buffer == null)
                    {
                        string value = prop.CastValue <string>();
                        buffer       = Encoding.UTF8.GetBytes(value);
                    }

                    writer.Write(buffer.Length);
                    writer.Write(buffer);
                });

                break;

            case PropertyType.Bool:
            {
                props.ForEach(prop =>
                    {
                        bool value = prop.CastValue <bool>();
                        writer.Write(value);
                    });

                break;
            }

            case PropertyType.Int:
            {
                var ints = props
                           .Select(prop => prop.CastValue <int>())
                           .ToList();

                writer.WriteInts(ints);
                break;
            }

            case PropertyType.Float:
            {
                var floats = props
                             .Select(prop => prop.CastValue <float>())
                             .ToList();

                writer.WriteFloats(floats);
                break;
            }

            case PropertyType.Double:
            {
                props.ForEach(prop =>
                    {
                        double value = prop.CastValue <double>();
                        writer.Write(BinaryRobloxFileWriter.GetBytes(value));
                    });

                break;
            }

            case PropertyType.UDim:
            {
                var UDim_Scales  = new List <float>();
                var UDim_Offsets = new List <int>();

                props.ForEach(prop =>
                    {
                        UDim value = prop.CastValue <UDim>();
                        UDim_Scales.Add(value.Scale);
                        UDim_Offsets.Add(value.Offset);
                    });

                writer.WriteFloats(UDim_Scales);
                writer.WriteInts(UDim_Offsets);

                break;
            }

            case PropertyType.UDim2:
            {
                var UDim2_Scales_X = new List <float>();
                var UDim2_Scales_Y = new List <float>();

                var UDim2_Offsets_X = new List <int>();
                var UDim2_Offsets_Y = new List <int>();

                props.ForEach(prop =>
                    {
                        UDim2 value = prop.CastValue <UDim2>();

                        UDim2_Scales_X.Add(value.X.Scale);
                        UDim2_Scales_Y.Add(value.Y.Scale);

                        UDim2_Offsets_X.Add(value.X.Offset);
                        UDim2_Offsets_Y.Add(value.Y.Offset);
                    });

                writer.WriteFloats(UDim2_Scales_X);
                writer.WriteFloats(UDim2_Scales_Y);

                writer.WriteInts(UDim2_Offsets_X);
                writer.WriteInts(UDim2_Offsets_Y);

                break;
            }

            case PropertyType.Ray:
            {
                props.ForEach(prop =>
                    {
                        Ray ray = prop.CastValue <Ray>();

                        Vector3 pos = ray.Origin;
                        writer.Write(pos.X);
                        writer.Write(pos.Y);
                        writer.Write(pos.Z);

                        Vector3 dir = ray.Direction;
                        writer.Write(dir.X);
                        writer.Write(dir.Y);
                        writer.Write(dir.Z);
                    });

                break;
            }

            case PropertyType.Faces:
            case PropertyType.Axes:
            {
                props.ForEach(prop =>
                    {
                        byte value = prop.CastValue <byte>();
                        writer.Write(value);
                    });

                break;
            }

            case PropertyType.BrickColor:
            {
                var brickColorIds = props
                                    .Select(prop => prop.CastValue <BrickColor>())
                                    .Select(value => value.Number)
                                    .ToList();

                writer.WriteInts(brickColorIds);
                break;
            }

            case PropertyType.Color3:
            {
                var Color3_R = new List <float>();
                var Color3_G = new List <float>();
                var Color3_B = new List <float>();

                props.ForEach(prop =>
                    {
                        Color3 value = prop.CastValue <Color3>();
                        Color3_R.Add(value.R);
                        Color3_G.Add(value.G);
                        Color3_B.Add(value.B);
                    });

                writer.WriteFloats(Color3_R);
                writer.WriteFloats(Color3_G);
                writer.WriteFloats(Color3_B);

                break;
            }

            case PropertyType.Vector2:
            {
                var Vector2_X = new List <float>();
                var Vector2_Y = new List <float>();

                props.ForEach(prop =>
                    {
                        Vector2 value = prop.CastValue <Vector2>();
                        Vector2_X.Add(value.X);
                        Vector2_Y.Add(value.Y);
                    });

                writer.WriteFloats(Vector2_X);
                writer.WriteFloats(Vector2_Y);

                break;
            }

            case PropertyType.Vector3:
            {
                var Vector3_X = new List <float>();
                var Vector3_Y = new List <float>();
                var Vector3_Z = new List <float>();

                props.ForEach(prop =>
                    {
                        Vector3 value = prop.CastValue <Vector3>();
                        Vector3_X.Add(value.X);
                        Vector3_Y.Add(value.Y);
                        Vector3_Z.Add(value.Z);
                    });

                writer.WriteFloats(Vector3_X);
                writer.WriteFloats(Vector3_Y);
                writer.WriteFloats(Vector3_Z);

                break;
            }

            case PropertyType.CFrame:
            case PropertyType.Quaternion:
            case PropertyType.OptionalCFrame:
            {
                var CFrame_X = new List <float>();
                var CFrame_Y = new List <float>();
                var CFrame_Z = new List <float>();

                if (Type == PropertyType.OptionalCFrame)
                {
                    writer.Write((byte)PropertyType.CFrame);
                }

                props.ForEach(prop =>
                    {
                        CFrame value = null;

                        if (prop.Value is Quaternion q)
                        {
                            value = q.ToCFrame();
                        }
                        else
                        {
                            value = prop.CastValue <CFrame>();
                        }

                        if (value == null)
                        {
                            value = new CFrame();
                        }

                        Vector3 pos = value.Position;
                        CFrame_X.Add(pos.X);
                        CFrame_Y.Add(pos.Y);
                        CFrame_Z.Add(pos.Z);

                        int orientId = value.GetOrientId();
                        writer.Write((byte)(orientId + 1));

                        if (orientId == -1)
                        {
                            if (Type == PropertyType.Quaternion)
                            {
                                Quaternion quat = new Quaternion(value);
                                writer.Write(quat.X);
                                writer.Write(quat.Y);
                                writer.Write(quat.Z);
                                writer.Write(quat.W);
                            }
                            else
                            {
                                float[] components = value.GetComponents();

                                for (int i = 3; i < 12; i++)
                                {
                                    float component = components[i];
                                    writer.Write(component);
                                }
                            }
                        }
                    });

                writer.WriteFloats(CFrame_X);
                writer.WriteFloats(CFrame_Y);
                writer.WriteFloats(CFrame_Z);

                if (Type == PropertyType.OptionalCFrame)
                {
                    writer.Write((byte)PropertyType.Bool);

                    props.ForEach(prop =>
                        {
                            if (prop.Value is null)
                            {
                                writer.Write(false);
                                return;
                            }

                            if (prop.Value is Optional <CFrame> optional)
                            {
                                writer.Write(optional.HasValue);
                                return;
                            }

                            var cf = prop.Value as CFrame;
                            writer.Write(cf != null);
                        });
                }

                break;
            }

            case PropertyType.Enum:
            {
                var enums = new List <uint>();

                props.ForEach(prop =>
                    {
                        if (prop.Value is uint raw)
                        {
                            enums.Add(raw);
                            return;
                        }

                        int signed = (int)prop.Value;
                        uint value = (uint)signed;

                        enums.Add(value);
                    });

                writer.WriteInterleaved(enums);
                break;
            }

            case PropertyType.Ref:
            {
                var InstanceIds = new List <int>();

                props.ForEach(prop =>
                    {
                        int referent = -1;

                        if (prop.Value != null)
                        {
                            Instance value = prop.CastValue <Instance>();

                            if (value.IsDescendantOf(File))
                            {
                                string refValue = value.Referent;
                                int.TryParse(refValue, out referent);
                            }
                        }

                        InstanceIds.Add(referent);
                    });

                writer.WriteInstanceIds(InstanceIds);
                break;
            }

            case PropertyType.Vector3int16:
            {
                props.ForEach(prop =>
                    {
                        Vector3int16 value = prop.CastValue <Vector3int16>();
                        writer.Write(value.X);
                        writer.Write(value.Y);
                        writer.Write(value.Z);
                    });

                break;
            }

            case PropertyType.NumberSequence:
            {
                props.ForEach(prop =>
                    {
                        NumberSequence value = prop.CastValue <NumberSequence>();

                        var keyPoints = value.Keypoints;
                        writer.Write(keyPoints.Length);

                        foreach (var keyPoint in keyPoints)
                        {
                            writer.Write(keyPoint.Time);
                            writer.Write(keyPoint.Value);
                            writer.Write(keyPoint.Envelope);
                        }
                    });

                break;
            }

            case PropertyType.ColorSequence:
            {
                props.ForEach(prop =>
                    {
                        ColorSequence value = prop.CastValue <ColorSequence>();

                        var keyPoints = value.Keypoints;
                        writer.Write(keyPoints.Length);

                        foreach (var keyPoint in keyPoints)
                        {
                            Color3 color = keyPoint.Value;
                            writer.Write(keyPoint.Time);

                            writer.Write(color.R);
                            writer.Write(color.G);
                            writer.Write(color.B);

                            writer.Write(keyPoint.Envelope);
                        }
                    });

                break;
            }

            case PropertyType.NumberRange:
            {
                props.ForEach(prop =>
                    {
                        NumberRange value = prop.CastValue <NumberRange>();
                        writer.Write(value.Min);
                        writer.Write(value.Max);
                    });

                break;
            }

            case PropertyType.Rect:
            {
                var Rect_X0 = new List <float>();
                var Rect_Y0 = new List <float>();

                var Rect_X1 = new List <float>();
                var Rect_Y1 = new List <float>();

                props.ForEach(prop =>
                    {
                        Rect value = prop.CastValue <Rect>();

                        Vector2 min = value.Min;
                        Rect_X0.Add(min.X);
                        Rect_Y0.Add(min.Y);

                        Vector2 max = value.Max;
                        Rect_X1.Add(max.X);
                        Rect_Y1.Add(max.Y);
                    });

                writer.WriteFloats(Rect_X0);
                writer.WriteFloats(Rect_Y0);

                writer.WriteFloats(Rect_X1);
                writer.WriteFloats(Rect_Y1);

                break;
            }

            case PropertyType.PhysicalProperties:
            {
                props.ForEach(prop =>
                    {
                        bool custom = (prop.Value != null);
                        writer.Write(custom);

                        if (custom)
                        {
                            PhysicalProperties value = prop.CastValue <PhysicalProperties>();

                            writer.Write(value.Density);
                            writer.Write(value.Friction);
                            writer.Write(value.Elasticity);

                            writer.Write(value.FrictionWeight);
                            writer.Write(value.ElasticityWeight);
                        }
                    });

                break;
            }

            case PropertyType.Color3uint8:
            {
                var Color3uint8_R = new List <byte>();
                var Color3uint8_G = new List <byte>();
                var Color3uint8_B = new List <byte>();

                props.ForEach(prop =>
                    {
                        Color3uint8 value = prop.CastValue <Color3uint8>();
                        Color3uint8_R.Add(value.R);
                        Color3uint8_G.Add(value.G);
                        Color3uint8_B.Add(value.B);
                    });

                byte[] rBuffer = Color3uint8_R.ToArray();
                writer.Write(rBuffer);

                byte[] gBuffer = Color3uint8_G.ToArray();
                writer.Write(gBuffer);

                byte[] bBuffer = Color3uint8_B.ToArray();
                writer.Write(bBuffer);

                break;
            }

            case PropertyType.Int64:
            {
                var longs = new List <long>();

                props.ForEach(prop =>
                    {
                        long value = prop.CastValue <long>();
                        longs.Add(value);
                    });

                writer.WriteInterleaved(longs, value =>
                    {
                        // Move the sign bit to the front.
                        return((value << 1) ^ (value >> 63));
                    });

                break;
            }

            case PropertyType.SharedString:
            {
                var  sharedKeys = new List <uint>();
                SSTR sstr       = file.SSTR;

                if (sstr == null)
                {
                    sstr      = new SSTR();
                    file.SSTR = sstr;
                }

                props.ForEach(prop =>
                    {
                        var shared = prop.CastValue <SharedString>();

                        if (shared == null)
                        {
                            byte[] empty = Array.Empty <byte>();
                            shared       = SharedString.FromBuffer(empty);
                        }

                        string key = shared.Key;

                        if (!sstr.Lookup.ContainsKey(key))
                        {
                            uint id = (uint)sstr.Lookup.Count;
                            sstr.Strings.Add(id, shared);
                            sstr.Lookup.Add(key, id);
                        }

                        uint hashId = sstr.Lookup[key];
                        sharedKeys.Add(hashId);
                    });

                writer.WriteInterleaved(sharedKeys);
                break;
            }

            case PropertyType.ProtectedString:
            {
                props.ForEach(prop =>
                    {
                        var protect   = prop.CastValue <ProtectedString>();
                        byte[] buffer = protect.RawBuffer;

                        writer.Write(buffer.Length);
                        writer.Write(buffer);
                    });

                break;
            }

            case PropertyType.UniqueId:
            {
                props.ForEach(prop =>
                    {
                        var guid      = prop.CastValue <Guid>();
                        byte[] buffer = guid.ToByteArray();
                        writer.Write(buffer);
                    });

                break;
            }

            default:
            {
                RobloxFile.LogError($"Unhandled property type: {Type} in {this}!");
                break;
            }
            }
        }
        public static XmlNode WriteProperty(Property prop, XmlDocument doc, XmlRobloxFile file)
        {
            if (prop.Name == "Archivable")
            {
                return(null);
            }

            string propType = prop.XmlToken;

            if (propType == null)
            {
                propType = "";
            }

            if (propType.Length == 0)
            {
                propType = GetEnumName(prop.Type);

                switch (prop.Type)
                {
                case PropertyType.CFrame:
                case PropertyType.Quaternion:
                    propType = "CoordinateFrame";
                    break;

                case PropertyType.Enum:
                    propType = "token";
                    break;

                case PropertyType.Rect:
                    propType = "Rect2D";
                    break;

                case PropertyType.Int:
                case PropertyType.Bool:
                case PropertyType.Float:
                case PropertyType.Int64:
                case PropertyType.Double:
                    propType = propType.ToLower(CultureInfo.InvariantCulture);
                    break;

                case PropertyType.String:
                    propType = (prop.HasRawBuffer ? "BinaryString" : "string");
                    break;

                default: break;
                }
            }

            IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType);

            if (handler == null)
            {
                if (RobloxFile.LogErrors)
                {
                    Console.Error.WriteLine("XmlDataWriter.WriteProperty: No token handler found for property type: {0}", propType);
                }

                return(null);
            }

            if (prop.Type == PropertyType.SharedString)
            {
                SharedString str = prop.CastValue <SharedString>();

                if (str == null)
                {
                    byte[] value = prop.CastValue <byte[]>();
                    str = SharedString.FromBuffer(value);
                }

                if (str.ComputedKey == null)
                {
                    var newShared = SharedString.FromBuffer(str.SharedValue);
                    str.Key = newShared.ComputedKey;
                }

                file.SharedStrings.Add(str.Key);
            }

            XmlElement propElement = doc.CreateElement(propType);

            propElement.SetAttribute("name", prop.Name);

            XmlNode propNode = propElement;

            handler.WriteProperty(prop, doc, propNode);

            if (propNode.ParentNode != null)
            {
                XmlNode newNode = propNode.ParentNode;
                newNode.RemoveChild(propNode);
                propNode = newNode;
            }

            return(propNode);
        }