diff --git a/BinaryFormat/BinaryFileChunk.cs b/BinaryFormat/BinaryFileChunk.cs index 400cb12..60d5a0a 100644 --- a/BinaryFormat/BinaryFileChunk.cs +++ b/BinaryFormat/BinaryFileChunk.cs @@ -35,9 +35,8 @@ namespace RobloxFiles.BinaryFormat public override string ToString() { string chunkType = ChunkType.Replace('\0', ' '); - int bytes = (HasCompressedData ? CompressedSize : Size); - return $"'{chunkType}' Chunk ({bytes} bytes)"; + return $"'{chunkType}' Chunk ({Size} bytes) [{Handler?.ToString()}]"; } public BinaryRobloxFileChunk(BinaryRobloxFileReader reader) diff --git a/BinaryFormat/BinaryRobloxFile.cs b/BinaryFormat/BinaryRobloxFile.cs index ac43750..ec278fa 100644 --- a/BinaryFormat/BinaryRobloxFile.cs +++ b/BinaryFormat/BinaryRobloxFile.cs @@ -4,9 +4,11 @@ using System.IO; using System.Linq; using System.Text; +using RobloxFiles.BinaryFormat; using RobloxFiles.BinaryFormat.Chunks; +using RobloxFiles.DataTypes; -namespace RobloxFiles.BinaryFormat +namespace RobloxFiles { public class BinaryRobloxFile : RobloxFile { @@ -14,7 +16,7 @@ namespace RobloxFiles.BinaryFormat public const string MagicHeader = " GetType().Name; public Instance[] Instances; - public INST[] Types; + public INST[] Classes; internal META META = null; internal SSTR SSTR = null; @@ -32,9 +34,9 @@ namespace RobloxFiles.BinaryFormat public Dictionary Metadata => META?.Data; public bool HasSharedStrings => (SSTR != null); - public IReadOnlyDictionary SharedStrings => SSTR?.Strings; + public IReadOnlyDictionary SharedStrings => SSTR?.Strings; - internal BinaryRobloxFile() + public BinaryRobloxFile() { Name = "BinaryRobloxFile"; ParentLocked = true; @@ -55,14 +57,14 @@ namespace RobloxFiles.BinaryFormat // Read header data. Version = reader.ReadUInt16(); - NumTypes = reader.ReadUInt32(); + NumClasses = reader.ReadUInt32(); NumInstances = reader.ReadUInt32(); Reserved = reader.ReadInt64(); // Begin reading the file chunks. bool reading = true; - Types = new INST[NumTypes]; + Classes = new INST[NumClasses]; Instances = new Instance[NumInstances]; while (reading) @@ -92,6 +94,7 @@ namespace RobloxFiles.BinaryFormat handler = new SSTR(); break; case "END\0": + Chunks.Add(chunk); reading = false; break; default: @@ -129,42 +132,35 @@ namespace RobloxFiles.BinaryFormat Chunks.Clear(); NumInstances = 0; - NumTypes = 0; + NumClasses = 0; SSTR = null; - // Record all instances and types. + // Recursively capture all instances and classes. writer.RecordInstances(Children); - // Apply the type values. - INST.ApplyTypeMap(writer); + // Apply the recorded instances and classes. + writer.ApplyClassMap(); // Write the INST chunks. - foreach (INST type in Types) - { - var instChunk = type.SaveAsChunk(writer); - Chunks.Add(instChunk); - } + foreach (INST inst in Classes) + writer.SaveChunk(inst); // Write the PROP chunks. - foreach (INST type in Types) + foreach (INST inst in Classes) { - Dictionary props = PROP.CollectProperties(writer, type); + Dictionary props = PROP.CollectProperties(writer, inst); foreach (string propName in props.Keys) { PROP prop = props[propName]; - - var chunk = prop.SaveAsChunk(writer); - Chunks.Add(chunk); + writer.SaveChunk(prop); } } // Write the PRNT chunk. PRNT parents = new PRNT(); - - var parentChunk = parents.SaveAsChunk(writer); - Chunks.Add(parentChunk); - + writer.SaveChunk(parents); + // Write the SSTR chunk. if (HasSharedStrings) { @@ -180,15 +176,12 @@ namespace RobloxFiles.BinaryFormat } // Write the END_ chunk. - writer.StartWritingChunk("END\0"); - writer.WriteString("", true); - - var endChunk = writer.FinishWritingChunk(false); + var endChunk = writer.WriteEndChunk(); Chunks.Add(endChunk); } ////////////////////////////////////////////////////////////////////////// - // Write the chunks with the header & footer data + // Write the chunk buffers with the header data ////////////////////////////////////////////////////////////////////////// using (BinaryWriter writer = new BinaryWriter(stream)) @@ -196,20 +189,15 @@ namespace RobloxFiles.BinaryFormat stream.Position = 0; stream.SetLength(0); - byte[] magicHeader = MagicHeader + writer.Write(MagicHeader .Select(ch => (byte)ch) - .ToArray(); - - writer.Write(magicHeader); + .ToArray()); writer.Write(Version); - writer.Write(NumTypes); + writer.Write(NumClasses); writer.Write(NumInstances); + writer.Write(Reserved); - // Write the 8 reserved-bytes. - writer.Write(0L); - - // Write all of the chunks. foreach (BinaryRobloxFileChunk chunk in Chunks) { if (chunk.HasWriteBuffer) diff --git a/BinaryFormat/Chunks/INST.cs b/BinaryFormat/Chunks/INST.cs index fff8620..f89535b 100644 --- a/BinaryFormat/Chunks/INST.cs +++ b/BinaryFormat/Chunks/INST.cs @@ -1,12 +1,12 @@ -using System.Collections.Generic; -using System.Linq; +using System; +using System.Collections.Generic; namespace RobloxFiles.BinaryFormat.Chunks { public class INST : IBinaryFileChunk { - public int TypeIndex { get; internal set; } - public string TypeName { get; internal set; } + public int ClassIndex { get; internal set; } + public string ClassName { get; internal set; } public bool IsService { get; internal set; } public List RootedServices { get; internal set; } @@ -14,17 +14,14 @@ namespace RobloxFiles.BinaryFormat.Chunks public int NumInstances { get; internal set; } public List InstanceIds { get; internal set; } - public override string ToString() - { - return TypeName; - } - + public override string ToString() => ClassName; + public void LoadFromReader(BinaryRobloxFileReader reader) { BinaryRobloxFile file = reader.File; - TypeIndex = reader.ReadInt32(); - TypeName = reader.ReadString(); + ClassIndex = reader.ReadInt32(); + ClassName = reader.ReadString(); IsService = reader.ReadBoolean(); NumInstances = reader.ReadInt32(); @@ -44,32 +41,30 @@ namespace RobloxFiles.BinaryFormat.Chunks for (int i = 0; i < NumInstances; i++) { int instId = InstanceIds[i]; + Type instType = Type.GetType($"RobloxFiles.{ClassName}") ?? typeof(Instance); - var inst = new Instance() - { - ClassName = TypeName, - IsService = IsService, - Referent = instId.ToString() - }; - + var inst = Activator.CreateInstance(instType) as Instance; + inst.Referent = instId.ToString(); + inst.IsService = IsService; + if (IsService) { - bool rooted = RootedServices[i]; - inst.IsRootedService = rooted; + bool isRooted = RootedServices[i]; + inst.Parent = (isRooted ? file : null); } file.Instances[instId] = inst; } - file.Types[TypeIndex] = this; + file.Classes[ClassIndex] = this; } public BinaryRobloxFileChunk SaveAsChunk(BinaryRobloxFileWriter writer) { writer.StartWritingChunk(this); - writer.Write(TypeIndex); - writer.WriteString(TypeName); + writer.Write(ClassIndex); + writer.WriteString(ClassName); writer.Write(IsService); writer.Write(NumInstances); @@ -82,30 +77,13 @@ namespace RobloxFiles.BinaryFormat.Chunks foreach (int instId in InstanceIds) { Instance service = file.Instances[instId]; - writer.Write(service.IsRootedService); + bool isRooted = (service.Parent == file); + + writer.Write(isRooted); } } return writer.FinishWritingChunk(); } - - internal static void ApplyTypeMap(BinaryRobloxFileWriter writer) - { - BinaryRobloxFile file = writer.File; - file.Instances = writer.Instances.ToArray(); - - var types = writer.TypeMap - .OrderBy(type => type.Key) - .Select(type => type.Value) - .ToArray(); - - for (int i = 0; i < types.Length; i++, file.NumTypes++) - { - INST type = types[i]; - type.TypeIndex = i; - } - - file.Types = types; - } } } diff --git a/BinaryFormat/Chunks/PRNT.cs b/BinaryFormat/Chunks/PRNT.cs index 4b6fd4a..23ba0f6 100644 --- a/BinaryFormat/Chunks/PRNT.cs +++ b/BinaryFormat/Chunks/PRNT.cs @@ -27,21 +27,21 @@ namespace RobloxFiles.BinaryFormat.Chunks Instance child = file.Instances[childId]; child.Parent = (parentId >= 0 ? file.Instances[parentId] : file); + child.ParentLocked = child.IsService; } } public BinaryRobloxFileChunk SaveAsChunk(BinaryRobloxFileWriter writer) { - BinaryRobloxFile file = writer.File; writer.StartWritingChunk(this); Format = 0; - NumRelations = file.Instances.Length; + NumRelations = 0; ChildrenIds = new List(); ParentIds = new List(); - foreach (Instance inst in file.Instances) + foreach (Instance inst in writer.PostInstances) { Instance parent = inst.Parent; @@ -53,6 +53,8 @@ namespace RobloxFiles.BinaryFormat.Chunks ChildrenIds.Add(childId); ParentIds.Add(parentId); + + NumRelations++; } writer.Write(Format); diff --git a/BinaryFormat/Chunks/PROP.cs b/BinaryFormat/Chunks/PROP.cs index 9021c02..4aa0102 100644 --- a/BinaryFormat/Chunks/PROP.cs +++ b/BinaryFormat/Chunks/PROP.cs @@ -1,50 +1,62 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Cryptography; +using System.Reflection; +using System.Text; using RobloxFiles.Enums; using RobloxFiles.DataTypes; using RobloxFiles.Utility; -using System.Text; namespace RobloxFiles.BinaryFormat.Chunks { public class PROP : IBinaryFileChunk { public string Name { get; internal set; } - public int TypeIndex { get; internal set; } + + public int ClassIndex { get; internal set; } + public string ClassName { get; private set; } public PropertyType Type { get; internal set; } + public byte TypeId { get { return (byte)Type; } internal set { Type = (PropertyType)value; } } - + + public override string ToString() + { + return $"{Type} {ClassName}.{Name}"; + } + public void LoadFromReader(BinaryRobloxFileReader reader) { BinaryRobloxFile file = reader.File; - TypeIndex = reader.ReadInt32(); + ClassIndex = reader.ReadInt32(); Name = reader.ReadString(); - TypeId = reader.ReadByte(); - INST type = file.Types[TypeIndex]; - Property[] props = new Property[type.NumInstances]; + byte propType = reader.ReadByte(); + Type = (PropertyType)propType; + + INST inst = file.Classes[ClassIndex]; + ClassName = inst.ClassName; - var ids = type.InstanceIds; - int instCount = type.NumInstances; + Property[] props = new Property[inst.NumInstances]; + + var ids = inst.InstanceIds; + int instCount = inst.NumInstances; for (int i = 0; i < instCount; i++) { int id = ids[i]; - Instance inst = file.Instances[id]; + Instance instance = file.Instances[id]; - Property prop = new Property(inst, this); + Property prop = new Property(instance, this); props[i] = prop; - inst.AddProperty(ref prop); + instance.AddProperty(ref prop); } // Setup some short-hand functions for actions used during the read procedure. @@ -66,14 +78,36 @@ namespace RobloxFiles.BinaryFormat.Chunks case PropertyType.String: readProperties(i => { - string result = reader.ReadString(); - + string value = reader.ReadString(); + // Leave an access point for the original byte sequence, in case this is a BinaryString. // This will allow the developer to read the sequence without any mangling from C# strings. byte[] buffer = reader.GetLastStringBuffer(); props[i].RawBuffer = buffer; - return result; + // Check if this is going to be casted as a BinaryString. + // BinaryStrings should use a type of byte[] instead. + + Property prop = props[i]; + Instance instance = prop.Instance; + + Type instType = instance.GetType(); + FieldInfo field = instType.GetField(Name); + + if (field != null) + { + object result = value; + Type fieldType = field.FieldType; + + if (fieldType == typeof(byte[])) + result = buffer; + + return result; + } + else + { + return value; + } }); break; @@ -213,15 +247,16 @@ namespace RobloxFiles.BinaryFormat.Chunks case PropertyType.Quaternion: // Temporarily load the rotation matrices into their properties. // We'll update them to CFrames once we iterate over the position data. + float[][] matrices = new float[instCount][]; - readProperties(i => + for (int i = 0; i < instCount; i++) { - byte b_OrientId = reader.ReadByte(); + byte rawOrientId = reader.ReadByte(); - if (b_OrientId > 0) + if (rawOrientId > 0) { // Make sure this value is in a safe range. - int orientId = (b_OrientId - 1) % 36; + int orientId = (rawOrientId - 1) % 36; NormalId xColumn = (NormalId)(orientId / 6); Vector3 R0 = Vector3.FromNormalId(xColumn); @@ -232,8 +267,8 @@ namespace RobloxFiles.BinaryFormat.Chunks // Compute R2 using the cross product of R0 and R1. Vector3 R2 = R0.Cross(R1); - // Generate the rotation matrix and return it. - return new float[9] + // Generate the rotation matrix. + matrices[i] = new float[9] { R0.X, R0.Y, R0.Z, R1.X, R1.Y, R1.Z, @@ -247,8 +282,7 @@ namespace RobloxFiles.BinaryFormat.Chunks Quaternion quaternion = new Quaternion(qx, qy, qz, qw); var rotation = quaternion.ToCFrame(); - - return rotation.GetComponents(); + matrices[i] = rotation.GetComponents(); } else { @@ -260,9 +294,9 @@ namespace RobloxFiles.BinaryFormat.Chunks matrix[m] = value; } - return matrix; + matrices[i] = matrix; } - }); + } float[] CFrame_X = readFloats(), CFrame_Y = readFloats(), @@ -270,25 +304,54 @@ namespace RobloxFiles.BinaryFormat.Chunks readProperties(i => { - float[] matrix = props[i].Value as float[]; + float[] matrix = matrices[i]; float x = CFrame_X[i], y = CFrame_Y[i], z = CFrame_Z[i]; - float[] position = new float[3] { x, y, z }; - float[] components = position.Concat(matrix).ToArray(); + float[] components; + + if (matrix.Length == 12) + { + matrix[0] = x; + matrix[1] = y; + matrix[2] = z; + + components = matrix; + } + else + { + float[] position = new float[3] { x, y, z }; + components = position.Concat(matrix).ToArray(); + } return new CFrame(components); }); break; case PropertyType.Enum: - // TODO: I want to map these values to actual Roblox enums, but I'll have to add an - // interpreter for the JSON API Dump to do it properly. - uint[] enums = reader.ReadUInts(instCount); - readProperties(i => enums[i]); + + readProperties(i => + { + Property prop = props[i]; + Instance instance = prop.Instance; + + Type instType = instance.GetType(); + uint value = enums[i]; + + try + { + FieldInfo info = instType.GetField(Name, Property.BindingFlags); + return Enum.Parse(info.FieldType, value.ToInvariantString()); + } + catch + { + Console.WriteLine($"Enum cast failed for {inst.ClassName}.{Name} using value {value}!"); + return value; + } + }); break; case PropertyType.Ref: @@ -345,9 +408,9 @@ namespace RobloxFiles.BinaryFormat.Chunks B = reader.ReadFloat(); Color3 Value = new Color3(R, G, B); - byte[] Reserved = reader.ReadBytes(4); + int Envelope = reader.ReadInt32(); - keypoints[key] = new ColorSequenceKeypoint(Time, Value, Reserved); + keypoints[key] = new ColorSequenceKeypoint(Time, Value, Envelope); } return new ColorSequence(keypoints); @@ -415,7 +478,8 @@ namespace RobloxFiles.BinaryFormat.Chunks g = Color3uint8_G[i], b = Color3uint8_B[i]; - return Color3.FromRGB(r, g, b); + Color3uint8 result = Color3.FromRGB(r, g, b); + return result; }); break; @@ -434,6 +498,7 @@ namespace RobloxFiles.BinaryFormat.Chunks readProperties(i => { uint key = SharedKeys[i]; + return file.SharedStrings[key]; }); @@ -455,11 +520,9 @@ namespace RobloxFiles.BinaryFormat.Chunks foreach (int instId in inst.InstanceIds) { Instance instance = file.Instances[instId]; + var props = instance.RefreshProperties(); - var props = instance.Properties; - var propNames = props.Keys; - - foreach (string propName in propNames) + foreach (string propName in props.Keys) { if (!propMap.ContainsKey(propName)) { @@ -469,7 +532,8 @@ namespace RobloxFiles.BinaryFormat.Chunks { Name = prop.Name, Type = prop.Type, - TypeIndex = inst.TypeIndex + + ClassIndex = inst.ClassIndex }; propMap.Add(propName, propChunk); @@ -484,13 +548,13 @@ namespace RobloxFiles.BinaryFormat.Chunks { BinaryRobloxFile file = writer.File; - INST inst = file.Types[TypeIndex]; + INST inst = file.Classes[ClassIndex]; var props = new List(); foreach (int instId in inst.InstanceIds) { Instance instance = file.Instances[instId]; - Property prop = instance.GetProperty(Name); + Property prop = instance.Properties[Name]; if (prop == null) throw new Exception($"Property {Name} must be defined in {instance.GetFullName()}!"); @@ -502,7 +566,7 @@ namespace RobloxFiles.BinaryFormat.Chunks } writer.StartWritingChunk(this); - writer.Write(TypeIndex); + writer.Write(ClassIndex); writer.WriteString(Name); writer.Write(TypeId); @@ -526,7 +590,12 @@ namespace RobloxFiles.BinaryFormat.Chunks break; case PropertyType.Bool: - props.ForEach(prop => prop.WriteValue()); + props.ForEach(prop => + { + bool value = prop.CastValue(); + writer.Write(value); + }); + break; case PropertyType.Int: var ints = new List(); @@ -737,7 +806,15 @@ namespace RobloxFiles.BinaryFormat.Chunks props.ForEach(prop => { - uint value = prop.CastValue(); + if (prop.Value is uint) + { + uint raw = prop.CastValue(); + Enums.Add(raw); + return; + } + + int signed = (int)prop.Value; + uint value = (uint)signed; Enums.Add(value); }); @@ -805,7 +882,7 @@ namespace RobloxFiles.BinaryFormat.Chunks writer.Write(color.G); writer.Write(color.B); - writer.Write(0); + writer.Write(keyPoint.Envelope); } }); @@ -873,21 +950,20 @@ namespace RobloxFiles.BinaryFormat.Chunks props.ForEach(prop => { - Color3 value = prop.CastValue(); - - byte r = (byte)(value.R * 255); - Color3uint8_R.Add(r); - - byte g = (byte)(value.G * 255); - Color3uint8_G.Add(g); - - byte b = (byte)(value.B * 255); - Color3uint8_B.Add(b); + Color3uint8 value = prop.CastValue(); + Color3uint8_R.Add(value.R); + Color3uint8_G.Add(value.G); + Color3uint8_B.Add(value.B); }); - writer.Write(Color3uint8_R.ToArray()); - writer.Write(Color3uint8_G.ToArray()); - writer.Write(Color3uint8_B.ToArray()); + 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: @@ -918,27 +994,18 @@ namespace RobloxFiles.BinaryFormat.Chunks props.ForEach(prop => { - uint sharedKey = 0; - - string value = prop.CastValue(); - byte[] buffer = Encoding.UTF8.GetBytes(value); - - using (MD5 md5 = MD5.Create()) + SharedString shared = prop.CastValue(); + string key = shared.MD5_Key; + + if (!sstr.Lookup.ContainsKey(key)) { - byte[] hash = md5.ComputeHash(buffer); - string key = Convert.ToBase64String(hash); - - if (!sstr.Lookup.ContainsKey(key)) - { - uint id = (uint)(sstr.NumHashes++); - sstr.Strings.Add(id, value); - sstr.Lookup.Add(key, id); - } - - sharedKey = sstr.Lookup[key]; + uint id = (uint)(sstr.NumHashes++); + sstr.Strings.Add(id, shared); + sstr.Lookup.Add(key, id); } - sharedKeys.Add(sharedKey); + uint hashId = sstr.Lookup[key]; + sharedKeys.Add(hashId); }); writer.WriteInterleaved(sharedKeys); diff --git a/BinaryFormat/Chunks/SSTR.cs b/BinaryFormat/Chunks/SSTR.cs index 54f02ea..32c5bee 100644 --- a/BinaryFormat/Chunks/SSTR.cs +++ b/BinaryFormat/Chunks/SSTR.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using RobloxFiles.DataTypes; namespace RobloxFiles.BinaryFormat.Chunks { @@ -9,7 +10,7 @@ namespace RobloxFiles.BinaryFormat.Chunks public int NumHashes; public Dictionary Lookup = new Dictionary(); - public Dictionary Strings = new Dictionary(); + public Dictionary Strings = new Dictionary(); public void LoadFromReader(BinaryRobloxFileReader reader) { @@ -25,10 +26,8 @@ namespace RobloxFiles.BinaryFormat.Chunks int length = reader.ReadInt32(); byte[] data = reader.ReadBytes(length); - string key = Convert.ToBase64String(md5); - string value = Convert.ToBase64String(data); - - Lookup.Add(key, id); + SharedString value = SharedString.FromBuffer(data); + Lookup.Add(value.MD5_Key, id); Strings.Add(id, value); } @@ -49,8 +48,8 @@ namespace RobloxFiles.BinaryFormat.Chunks byte[] md5 = Convert.FromBase64String(key); writer.Write(md5); - string value = Strings[pair.Value]; - byte[] buffer = Convert.FromBase64String(value); + SharedString value = Strings[pair.Value]; + byte[] buffer = SharedString.FindRecord(value.MD5_Key); writer.Write(buffer.Length); writer.Write(buffer); diff --git a/BinaryFormat/IO/BinaryFileWriter.cs b/BinaryFormat/IO/BinaryFileWriter.cs index 6c6efbc..e350527 100644 --- a/BinaryFormat/IO/BinaryFileWriter.cs +++ b/BinaryFormat/IO/BinaryFileWriter.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -16,10 +18,15 @@ namespace RobloxFiles.BinaryFormat public string ChunkType { get; private set; } public long ChunkStart { get; private set; } - public Dictionary TypeMap; + public Dictionary ClassMap; public readonly BinaryRobloxFile File; + + // Instances in parent->child order public List Instances; + // Instances in child->parent order + public List PostInstances; + public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer = null) : base(workBuffer ?? new MemoryStream()) { File = file; @@ -28,7 +35,9 @@ namespace RobloxFiles.BinaryFormat ChunkType = ""; Instances = new List(); - TypeMap = new Dictionary(); + PostInstances = new List(); + + ClassMap = new Dictionary(); } private static byte[] GetBytes(T value, int bufferSize, IntPtr converter) @@ -144,36 +153,64 @@ namespace RobloxFiles.BinaryFormat { foreach (Instance instance in instances) { - int instId = (int)(File.NumInstances++); + if (!instance.Archivable) + continue; + int instId = (int)(File.NumInstances++); instance.Referent = instId.ToString(); Instances.Add(instance); string className = instance.ClassName; INST inst = null; - if (!TypeMap.ContainsKey(className)) + if (!ClassMap.ContainsKey(className)) { inst = new INST() { - TypeName = className, + ClassName = className, InstanceIds = new List(), IsService = instance.IsService }; - TypeMap.Add(className, inst); + ClassMap.Add(className, inst); } else { - inst = TypeMap[className]; + inst = ClassMap[className]; } inst.NumInstances++; inst.InstanceIds.Add(instId); RecordInstances(instance.GetChildren()); + PostInstances.Add(instance); } } + + internal void ApplyClassMap() + { + File.Instances = Instances.ToArray(); + + var classNames = ClassMap + .Select(type => type.Key) + .ToList(); + + classNames.Sort(StringComparer.Ordinal); + + var classes = classNames + .Select(className => ClassMap[className]) + .ToArray(); + + for (int i = 0; i < classes.Length; i++, File.NumClasses++) + { + string className = classNames[i]; + + INST inst = ClassMap[className]; + inst.ClassIndex = i; + } + + File.Classes = classes; + } // Marks that we are writing a chunk. public bool StartWritingChunk(string chunkType) @@ -236,5 +273,19 @@ namespace RobloxFiles.BinaryFormat return chunk; } + + public void SaveChunk(IBinaryFileChunk handler) + { + var chunk = handler.SaveAsChunk(this); + File.Chunks.Add(chunk); + } + + public BinaryRobloxFileChunk WriteEndChunk() + { + StartWritingChunk("END\0"); + WriteString("", true); + + return FinishWritingChunk(false); + } } } diff --git a/DataTypes/CFrame.cs b/DataTypes/CFrame.cs index bd4295b..74903f2 100644 --- a/DataTypes/CFrame.cs +++ b/DataTypes/CFrame.cs @@ -14,11 +14,23 @@ namespace RobloxFiles.DataTypes public float Y => m24; public float Z => m34; - public Vector3 Position => new Vector3(X, Y, Z); + public Vector3 Position + { + get + { + return new Vector3(X, Y, Z); + } + set + { + m14 = value.X; + m24 = value.Y; + m34 = value.Z; + } + } - public Vector3 LookVector => new Vector3(-m13, -m23, -m33); - public Vector3 RightVector => new Vector3( m11, m21, m31); - public Vector3 UpVector => new Vector3( m12, m22, m32); + public Vector3 RightVector => new Vector3( m11, m12, m13); + public Vector3 UpVector => new Vector3( m21, m22, m23); + public Vector3 LookVector => new Vector3(-m31, -m32, -m33); public CFrame() { @@ -41,7 +53,6 @@ namespace RobloxFiles.DataTypes m34 = nz; } - public CFrame(Vector3 eye, Vector3 look) { Vector3 zAxis = (eye - look).Unit; @@ -127,9 +138,9 @@ namespace RobloxFiles.DataTypes { float[] ac = a.GetComponents(); - float x = ac[0], y = ac[1], z = ac[2], - m11 = ac[3], m12 = ac[4], m13 = ac[5], - m21 = ac[6], m22 = ac[7], m23 = ac[8], + float x = ac[0], y = ac[1], z = ac[2], + m11 = ac[3], m12 = ac[4], m13 = ac[5], + m21 = ac[6], m22 = ac[7], m23 = ac[8], m31 = ac[9], m32 = ac[10], m33 = ac[11]; return new CFrame(x - b.X, y - b.Y, z - b.Z, m11, m12, m13, m21, m22, m23, m31, m32, m33); @@ -138,9 +149,10 @@ namespace RobloxFiles.DataTypes public static Vector3 operator *(CFrame a, Vector3 b) { float[] ac = a.GetComponents(); - float x = ac[0], y = ac[1], z = ac[2], - m11 = ac[3], m12 = ac[4], m13 = ac[5], - m21 = ac[6], m22 = ac[7], m23 = ac[8], + + float x = ac[0], y = ac[1], z = ac[2], + m11 = ac[3], m12 = ac[4], m13 = ac[5], + m21 = ac[6], m22 = ac[7], m23 = ac[8], m31 = ac[9], m32 = ac[10], m33 = ac[11]; Vector3 right = new Vector3(m11, m21, m31); @@ -154,14 +166,14 @@ namespace RobloxFiles.DataTypes float[] ac = a.GetComponents(); float[] bc = b.GetComponents(); - float a14 = ac[0], a24 = ac[1], a34 = ac[2], - a11 = ac[3], a12 = ac[4], a13 = ac[5], - a21 = ac[6], a22 = ac[7], a23 = ac[8], + float a14 = ac[0], a24 = ac[1], a34 = ac[2], + a11 = ac[3], a12 = ac[4], a13 = ac[5], + a21 = ac[6], a22 = ac[7], a23 = ac[8], a31 = ac[9], a32 = ac[10], a33 = ac[11]; - float b14 = bc[0], b24 = bc[1], b34 = bc[2], - b11 = bc[3], b12 = bc[4], b13 = bc[5], - b21 = bc[6], b22 = bc[7], b23 = bc[8], + float b14 = bc[0], b24 = bc[1], b34 = bc[2], + b11 = bc[3], b12 = bc[4], b13 = bc[5], + b21 = bc[6], b22 = bc[7], b23 = bc[8], b31 = bc[9], b32 = bc[10], b33 = bc[11]; float n11 = a11 * b11 + a12 * b21 + a13 * b31 + a14 * m41; @@ -345,14 +357,14 @@ namespace RobloxFiles.DataTypes for (int i = 3; i < 12; i++) { - float t = matrix[i]; + float t = Math.Abs(matrix[i]); - if (Math.Abs(t - 1f) < 10e-5f) + if (t.FuzzyEquals(1)) { // Approximately ±1 sum1++; } - else if (Math.Abs(t) < 10e-5f) + else if (t.FuzzyEquals(0)) { // Approximately ±0 sum0++; @@ -364,10 +376,10 @@ namespace RobloxFiles.DataTypes private static bool IsLegalOrientId(int orientId) { - int xNormalAbs = (orientId / 6) % 3; - int yNormalAbs = orientId % 3; + int xOrientId = (orientId / 6) % 3; + int yOrientId = orientId % 3; - return (xNormalAbs != yNormalAbs); + return (xOrientId != yOrientId); } public int GetOrientId() diff --git a/DataTypes/Color3uint8.cs b/DataTypes/Color3uint8.cs new file mode 100644 index 0000000..a436a52 --- /dev/null +++ b/DataTypes/Color3uint8.cs @@ -0,0 +1,41 @@ +namespace RobloxFiles.DataTypes +{ + /// + /// Color3uint8 functions as an interconvertible storage medium for Color3 types. + /// It is used by property types that want their Color3 value encoded with bytes instead of floats. + /// + public class Color3uint8 + { + public readonly byte R, G, B; + + public Color3uint8(byte r = 0, byte g = 0, byte b = 0) + { + R = r; + G = g; + B = b; + } + + public override string ToString() + { + return string.Join(", ", R, G, B); + } + + public static implicit operator Color3(Color3uint8 color) + { + float r = color.R / 255f; + float g = color.G / 255f; + float b = color.B / 255f; + + return new Color3(r, g, b); + } + + public static implicit operator Color3uint8(Color3 color) + { + byte r = (byte)(color.R * 255); + byte g = (byte)(color.G * 255); + byte b = (byte)(color.B * 255); + + return new Color3uint8(r, g, b); + } + } +} diff --git a/DataTypes/ColorSequence.cs b/DataTypes/ColorSequence.cs index b2575ad..9609e64 100644 --- a/DataTypes/ColorSequence.cs +++ b/DataTypes/ColorSequence.cs @@ -6,12 +6,8 @@ namespace RobloxFiles.DataTypes { public readonly ColorSequenceKeypoint[] Keypoints; - public ColorSequence(Color3 c) + public ColorSequence(Color3 c) : this(c, c) { - ColorSequenceKeypoint a = new ColorSequenceKeypoint(0, c); - ColorSequenceKeypoint b = new ColorSequenceKeypoint(1, c); - - Keypoints = new ColorSequenceKeypoint[2] { a, b }; } public ColorSequence(Color3 c0, Color3 c1) @@ -35,10 +31,13 @@ namespace RobloxFiles.DataTypes if (keypoints[key - 1].Time > keypoints[key].Time) throw new Exception("ColorSequence: all keypoints must be ordered by time"); - if (Math.Abs(keypoints[0].Time) >= 10e-5f) + var first = keypoints[0]; + var last = keypoints[numKeys - 1]; + + if (!first.Time.FuzzyEquals(0)) throw new Exception("ColorSequence must start at time=0.0"); - if (Math.Abs(keypoints[numKeys - 1].Time - 1f) >= 10e-5f) + if (!last.Time.FuzzyEquals(1)) throw new Exception("ColorSequence must end at time=1.0"); Keypoints = keypoints; diff --git a/DataTypes/ColorSequenceKeypoint.cs b/DataTypes/ColorSequenceKeypoint.cs index 4c007cd..04570d6 100644 --- a/DataTypes/ColorSequenceKeypoint.cs +++ b/DataTypes/ColorSequenceKeypoint.cs @@ -4,18 +4,18 @@ { public readonly float Time; public readonly Color3 Value; - public readonly byte[] Reserved; + public readonly int Envelope; - public ColorSequenceKeypoint(float time, Color3 value, byte[] reserved = null) + public ColorSequenceKeypoint(float time, Color3 value, int envelope = 0) { Time = time; Value = value; - Reserved = reserved; + Envelope = envelope; } public override string ToString() { - return string.Join(" ", Time, Value.R, Value.G, Value.B, 0); + return string.Join(" ", Time, Value.R, Value.G, Value.B, Envelope); } } } diff --git a/DataTypes/Content.cs b/DataTypes/Content.cs new file mode 100644 index 0000000..4923be4 --- /dev/null +++ b/DataTypes/Content.cs @@ -0,0 +1,32 @@ +namespace RobloxFiles.DataTypes +{ + /// + /// Content is a type used by most url-based XML properties. + /// Here, it only exists as a wrapper class for strings. + /// + public class Content + { + // TODO: Maybe introduce constraints to the value? + public readonly string Data; + + public override string ToString() + { + return Data; + } + + public Content(string data) + { + Data = data; + } + + public static implicit operator string(Content content) + { + return content.Data; + } + + public static implicit operator Content(string data) + { + return new Content(data); + } + } +} diff --git a/DataTypes/NumberRange.cs b/DataTypes/NumberRange.cs index 0c9d359..a95b210 100644 --- a/DataTypes/NumberRange.cs +++ b/DataTypes/NumberRange.cs @@ -7,6 +7,12 @@ namespace RobloxFiles.DataTypes public readonly float Min; public readonly float Max; + public NumberRange(float num) + { + Min = num; + Max = num; + } + public NumberRange(float min = 0, float max = 0) { if (max - min < 0) diff --git a/DataTypes/NumberSequence.cs b/DataTypes/NumberSequence.cs index 846d178..f789c0f 100644 --- a/DataTypes/NumberSequence.cs +++ b/DataTypes/NumberSequence.cs @@ -35,10 +35,13 @@ namespace RobloxFiles.DataTypes if (keypoints[key - 1].Time > keypoints[key].Time) throw new Exception("NumberSequence: all keypoints must be ordered by time"); - if (Math.Abs(keypoints[0].Time) >= 10e-5f) + var first = keypoints[0]; + var last = keypoints[numKeys - 1]; + + if (!first.Time.FuzzyEquals(0)) throw new Exception("NumberSequence must start at time=0.0"); - if (Math.Abs(keypoints[numKeys - 1].Time - 1f) >= 10e-5f) + if (!last.Time.FuzzyEquals(1)) throw new Exception("NumberSequence must end at time=1.0"); Keypoints = keypoints; diff --git a/DataTypes/ProtectedString.cs b/DataTypes/ProtectedString.cs new file mode 100644 index 0000000..de6e8cd --- /dev/null +++ b/DataTypes/ProtectedString.cs @@ -0,0 +1,31 @@ +namespace RobloxFiles.DataTypes +{ + /// + /// ProtectedString is a type used by some of the XML properties. + /// Here, it only exists as a wrapper class for strings. + /// + public class ProtectedString + { + public readonly string Value; + + public override string ToString() + { + return Value; + } + + public ProtectedString(string value) + { + Value = value; + } + + public static implicit operator string(ProtectedString protectedString) + { + return protectedString.Value; + } + + public static implicit operator ProtectedString(string value) + { + return new ProtectedString(value); + } + } +} diff --git a/DataTypes/Quaternion.cs b/DataTypes/Quaternion.cs new file mode 100644 index 0000000..608027c --- /dev/null +++ b/DataTypes/Quaternion.cs @@ -0,0 +1,208 @@ +using System; +using RobloxFiles.DataTypes; + +namespace RobloxFiles.Utility +{ + /// + /// Quaternion is a utility used by the CFrame DataType to handle rotation interpolation. + /// It can be used as an independent Quaternion implementation if you so please! + /// + public class Quaternion + { + public readonly float X, Y, Z, W; + + public float Magnitude + { + get + { + float squared = Dot(this); + double magnitude = Math.Sqrt(squared); + + return (float)magnitude; + } + } + + public Quaternion(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public Quaternion(Vector3 qv, float qw) + { + X = qv.X; + Y = qv.Y; + Z = qv.Z; + W = qw; + } + + public Quaternion(CFrame cf) + { + CFrame matrix = (cf - cf.Position); + float[] ac = cf.GetComponents(); + + float m11 = ac[3], m12 = ac[4], m13 = ac[5], + m21 = ac[6], m22 = ac[7], m23 = ac[8], + m31 = ac[9], m32 = ac[10], m33 = ac[11]; + + float trace = m11 + m22 + m33; + + if (trace > 0) + { + float s = (float)Math.Sqrt(1 + trace); + float r = 0.5f / s; + + W = s * 0.5f; + X = (m32 - m23) * r; + Y = (m13 - m31) * r; + Z = (m21 - m12) * r; + } + else + { + float big = Math.Max(Math.Max(m11, m22), m33); + + if (big == m11) + { + float s = (float)Math.Sqrt(1 + m11 - m22 - m33); + float r = 0.5f / s; + + W = (m32 - m23) * r; + X = 0.5f * s; + Y = (m21 + m12) * r; + Z = (m13 + m31) * r; + } + else if (big == m22) + { + float s = (float)Math.Sqrt(1 - m11 + m22 - m33); + float r = 0.5f / s; + + W = (m13 - m31) * r; + X = (m21 + m12) * r; + Y = 0.5f * s; + Z = (m32 + m23) * r; + } + else if (big == m33) + { + float s = (float)Math.Sqrt(1 - m11 - m22 + m33); + float r = 0.5f / s; + + W = (m21 - m12) * r; + X = (m13 + m31) * r; + Y = (m32 + m23) * r; + Z = 0.5f * s; + } + } + } + + public float Dot(Quaternion other) + { + return (X * other.X) + (Y * other.Y) + (Z * other.Z) + (W * other.W); + } + + public Quaternion Lerp(Quaternion other, float alpha) + { + Quaternion result = this * (1.0f - alpha) + other * alpha; + return result / result.Magnitude; + } + + public Quaternion Slerp(Quaternion other, float alpha) + { + float cosAng = Dot(other); + + if (cosAng < 0) + { + other = -other; + cosAng = -cosAng; + } + + double ang = Math.Acos(cosAng); + + if (ang >= 0.05f) + { + float scale0 = (float)Math.Sin((1.0f - alpha) * ang); + float scale1 = (float)Math.Sin(alpha * ang); + float denom = (float)Math.Sin(ang); + + return ((this * scale0) + (other * scale1)) / denom; + } + else + { + return Lerp(other, alpha); + } + } + + public CFrame ToCFrame() + { + float xc = X * 2f; + float yc = Y * 2f; + float zc = Z * 2f; + + float xx = X * xc; + float xy = X * yc; + float xz = X * zc; + + float wx = W * xc; + float wy = W * yc; + float wz = W * zc; + + float yy = Y * yc; + float yz = Y * zc; + float zz = Z * zc; + + return new CFrame + ( + 0, 0, 0, + + 1f - (yy + zz), + xy - wz, + xz + wy, + + xy + wz, + 1f - (xx + zz), + yz - wx, + + xz - wy, + yz + wx, + 1f - (xx + yy) + ); + } + + public static Quaternion operator +(Quaternion a, Quaternion b) + { + return new Quaternion(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); + } + + public static Quaternion operator -(Quaternion a, Quaternion b) + { + return new Quaternion(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); + } + + public static Quaternion operator *(Quaternion a, float f) + { + return new Quaternion(a.X * f, a.Y * f, a.Z * f, a.W * f); + } + + public static Quaternion operator /(Quaternion a, float f) + { + return new Quaternion(a.X / f, a.Y / f, a.Z / f, a.W / f); + } + + public static Quaternion operator -(Quaternion a) + { + return new Quaternion(-a.X, -a.Y, -a.Z, -a.W); + } + + public static Quaternion operator *(Quaternion a, Quaternion b) + { + Vector3 v1 = new Vector3(a.X, a.Y, a.Z); + float s1 = a.W; + + Vector3 v2 = new Vector3(b.X, b.Y, b.Z); + float s2 = b.W; + + return new Quaternion(s1 * v2 + s2 * v1 + v1.Cross(v2), s1 * s2 - v1.Dot(v2)); + } + } +} diff --git a/DataTypes/Ray.cs b/DataTypes/Ray.cs index 1496ee5..11ae42d 100644 --- a/DataTypes/Ray.cs +++ b/DataTypes/Ray.cs @@ -20,10 +20,10 @@ } } - public Ray(Vector3 origin, Vector3 direction) + public Ray(Vector3 origin = null, Vector3 direction = null) { - Origin = origin; - Direction = direction; + Origin = origin ?? new Vector3(); + Direction = direction ?? new Vector3(); } public override string ToString() diff --git a/DataTypes/SharedString.cs b/DataTypes/SharedString.cs new file mode 100644 index 0000000..98a17ae --- /dev/null +++ b/DataTypes/SharedString.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Security.Cryptography; + +namespace RobloxFiles.DataTypes +{ + public class SharedString + { + private static Dictionary Records = new Dictionary(); + public readonly string MD5_Key; + + public byte[] SharedValue => FindRecord(MD5_Key); + public override string ToString() => $"MD5 Key: {MD5_Key}"; + + internal SharedString(string md5) + { + MD5_Key = md5; + } + + private SharedString(byte[] buffer) + { + using (MD5 md5 = MD5.Create()) + { + byte[] hash = md5.ComputeHash(buffer); + MD5_Key = Convert.ToBase64String(hash); + } + + if (Records.ContainsKey(MD5_Key)) + return; + + Records.Add(MD5_Key, buffer); + } + + public static byte[] FindRecord(string key) + { + byte[] result = null; + + if (Records.ContainsKey(key)) + result = Records[key]; + + return result; + } + + public static SharedString FromBuffer(byte[] buffer) + { + return new SharedString(buffer); + } + + public static SharedString FromString(string value) + { + byte[] buffer = Encoding.UTF8.GetBytes(value); + return new SharedString(buffer); + } + + public static SharedString FromBase64(string base64) + { + byte[] buffer = Convert.FromBase64String(base64); + return new SharedString(buffer); + } + } +} diff --git a/DataTypes/Vector2.cs b/DataTypes/Vector2.cs index c58b5df..ddb8289 100644 --- a/DataTypes/Vector2.cs +++ b/DataTypes/Vector2.cs @@ -28,7 +28,7 @@ namespace RobloxFiles.DataTypes Y = y; } - public Vector2(float[] coords) + internal Vector2(float[] coords) { X = coords.Length > 0 ? coords[0] : 0; Y = coords.Length > 1 ? coords[1] : 0; @@ -69,6 +69,11 @@ namespace RobloxFiles.DataTypes public static Vector2 operator /(Vector2 v, float n) => upcastFloatOp(v, n, div); public static Vector2 operator /(float n, Vector2 v) => upcastFloatOp(n, v, div); + public static Vector2 operator -(Vector2 v) + { + return new Vector2(-v.X, -v.Y); + } + public static Vector2 Zero => new Vector2(0, 0); public override string ToString() diff --git a/DataTypes/Vector3.cs b/DataTypes/Vector3.cs index 7aa0d76..65fe164 100644 --- a/DataTypes/Vector3.cs +++ b/DataTypes/Vector3.cs @@ -30,7 +30,7 @@ namespace RobloxFiles.DataTypes Z = z; } - public Vector3(float[] coords) + internal Vector3(float[] coords) { X = coords.Length > 0 ? coords[0] : 0; Y = coords.Length > 1 ? coords[1] : 0; @@ -92,6 +92,11 @@ namespace RobloxFiles.DataTypes public static Vector3 operator /(Vector3 v, float n) => upcastFloatOp(v, n, div); public static Vector3 operator /(float n, Vector3 v) => upcastFloatOp(n, v, div); + public static Vector3 operator -(Vector3 v) + { + return new Vector3(-v.X, -v.Y, -v.Z); + } + public static Vector3 Zero => new Vector3(0, 0, 0); public static Vector3 Right => new Vector3(1, 0, 0); public static Vector3 Up => new Vector3(0, 1, 0); @@ -141,7 +146,7 @@ namespace RobloxFiles.DataTypes float dotProd = normal.Dot(this); - if (Math.Abs(dotProd - 1f) < 10e-5f) + if (dotProd.FuzzyEquals(1)) { result = i; break; diff --git a/Generated/Classes.cs b/Generated/Classes.cs new file mode 100644 index 0000000..d6ae6be --- /dev/null +++ b/Generated/Classes.cs @@ -0,0 +1,3130 @@ +// Auto-generated list of creatable Roblox classes. +// Updated as of 0.391.0.313677 + +using System; + +using RobloxFiles.DataTypes; +using RobloxFiles.Enums; +using RobloxFiles.Utility; + +namespace RobloxFiles +{ + public class ABTestService : Instance + { + public ABTestService() + { + IsService = true; + } + } + + public class Accoutrement : Instance + { + public CFrame AttachmentPoint = new CFrame(); + } + + public class Accessory : Accoutrement + { + } + + public class Hat : Accoutrement + { + } + + public class AdService : Instance + { + public AdService() + { + IsService = true; + } + } + + public class AdvancedDragger : Instance + { + } + + public class AnalyticsService : Instance + { + public AnalyticsService() + { + IsService = true; + } + + public string ApiKey = ""; + } + + public class Animation : Instance + { + public Content AnimationId = ""; + } + + public class AnimationController : Instance + { + } + + public class Animator : Instance + { + } + + public class AssetService : Instance + { + public AssetService() + { + IsService = true; + } + } + + public class Attachment : Instance + { + public CFrame CFrame = new CFrame(); + public bool Visible = false; + } + + public class Backpack : Instance + { + } + + public abstract class BackpackItem : Instance + { + public Content TextureId = ""; + } + + public class HopperBin : BackpackItem + { + public bool Active = false; + public BinType BinType = BinType.Script; + } + + public class Tool : BackpackItem + { + public bool CanBeDropped = true; + public bool Enabled = true; + public CFrame Grip = new CFrame(); + public bool ManualActivationOnly = false; + public bool RequiresHandle = true; + public string ToolTip = ""; + } + + public class Flag : Tool + { + public BrickColor TeamColor = BrickColor.FromNumber(194); + } + + public class BadgeService : Instance + { + public BadgeService() + { + IsService = true; + } + } + + public abstract class BasePlayerGui : Instance + { + } + + public class CoreGui : BasePlayerGui + { + public CoreGui() + { + IsService = true; + } + + public GuiObject SelectionImageObject = null; + } + + public class StarterGui : BasePlayerGui + { + public StarterGui() + { + IsService = true; + } + + [Obsolete] + public bool ResetPlayerGuiOnSpawn = true; + + public ScreenOrientation ScreenOrientation = ScreenOrientation.LandscapeSensor; + public bool ShowDevelopmentGui = true; + } + + public class Beam : Instance + { + public Attachment Attachment0 = null; + public Attachment Attachment1 = null; + public ColorSequence Color = new ColorSequence(new Color3(1, 1, 1)); + public float CurveSize0 = 0; + public float CurveSize1 = 0; + public bool Enabled = true; + public bool FaceCamera = false; + public float LightEmission = 0; + public float LightInfluence = 0; + public int Segments = 10; + public Content Texture = ""; + public float TextureLength = 1; + public TextureMode TextureMode = TextureMode.Stretch; + public float TextureSpeed = 1; + public NumberSequence Transparency = new NumberSequence(0.5f); + public float Width0 = 1; + public float Width1 = 1; + public float ZOffset = 0; + } + + public class BindableEvent : Instance + { + } + + public class BindableFunction : Instance + { + } + + public abstract class BodyMover : Instance + { + } + + public class BodyAngularVelocity : BodyMover + { + public Vector3 AngularVelocity = new Vector3(0, 2, 0); + public Vector3 MaxTorque = new Vector3(4000, 4000, 4000); + public float P = 1250; + + [Obsolete] + public Vector3 angularvelocity + { + get { return AngularVelocity; } + set { AngularVelocity = value; } + } + + [Obsolete] + public Vector3 maxTorque + { + get { return MaxTorque; } + set { MaxTorque = value; } + } + } + + public class BodyForce : BodyMover + { + public Vector3 Force = new Vector3(0, 1, 0); + + [Obsolete] + public Vector3 force + { + get { return Force; } + set { Force = value; } + } + } + + public class BodyGyro : BodyMover + { + public CFrame CFrame = new CFrame(); + public float D = 500; + public Vector3 MaxTorque = new Vector3(400000, 0, 400000); + public float P = 3000; + + [Obsolete] + public CFrame cframe + { + get { return CFrame; } + set { CFrame = value; } + } + + [Obsolete] + public Vector3 maxTorque + { + get { return MaxTorque; } + set { MaxTorque = value; } + } + } + + public class BodyPosition : BodyMover + { + public float D = 1250; + public Vector3 MaxForce = new Vector3(4000, 4000, 4000); + public float P = 10000; + public Vector3 Position = new Vector3(0, 50, 0); + + [Obsolete] + public Vector3 maxForce + { + get { return MaxForce; } + set { MaxForce = value; } + } + + [Obsolete] + public Vector3 position + { + get { return Position; } + set { Position = value; } + } + } + + public class BodyThrust : BodyMover + { + public Vector3 Force = new Vector3(0, 1, 0); + public Vector3 Location = new Vector3(); + + [Obsolete] + public Vector3 force + { + get { return Force; } + set { Force = value; } + } + + [Obsolete] + public Vector3 location + { + get { return Location; } + set { Location = value; } + } + } + + public class BodyVelocity : BodyMover + { + public Vector3 MaxForce = new Vector3(4000, 4000, 4000); + public float P = 1250; + public Vector3 Velocity = new Vector3(0, 2, 0); + + [Obsolete] + public Vector3 maxForce + { + get { return MaxForce; } + set { MaxForce = value; } + } + + [Obsolete] + public Vector3 velocity + { + get { return Velocity; } + set { Velocity = value; } + } + } + + public class RocketPropulsion : BodyMover + { + public float CartoonFactor = 0.7f; + public float MaxSpeed = 30; + public float MaxThrust = 4000; + public Vector3 MaxTorque = new Vector3(400000, 400000, 0); + public BasePart Target = null; + public Vector3 TargetOffset = new Vector3(); + public float TargetRadius = 4; + public float ThrustD = 0.001f; + public float ThrustP = 5; + public float TurnD = 500; + public float TurnP = 3000; + } + + public abstract class CacheableContentProvider : Instance + { + public CacheableContentProvider() + { + IsService = true; + } + } + + public class MeshContentProvider : CacheableContentProvider + { + public MeshContentProvider() + { + IsService = true; + } + } + + public class SolidModelContentProvider : CacheableContentProvider + { + public SolidModelContentProvider() + { + IsService = true; + } + } + + public class Camera : Instance + { + public CFrame CFrame = new CFrame(0, 20, 20, 1, 0, 0, 0, 0.70711f, 0.70711f, 0, -0.70711f, 0.70711f); + public Instance CameraSubject = null; + public CameraType CameraType = CameraType.Fixed; + + [Obsolete] + public CFrame CoordinateFrame + { + get { return CFrame; } + set { CFrame = value; } + } + + public float FieldOfView = 70; + public CFrame Focus = new CFrame(0, 0, -5); + public bool HeadLocked = true; + public float HeadScale = 1; + + [Obsolete] + public CFrame focus + { + get { return Focus; } + set { Focus = value; } + } + } + + public class ChangeHistoryService : Instance + { + public ChangeHistoryService() + { + IsService = true; + } + } + + public abstract class CharacterAppearance : Instance + { + } + + public class BodyColors : CharacterAppearance + { + public BrickColor HeadColor + { + get { return BrickColor.FromColor3(HeadColor3); } + set { HeadColor3 = value.Color; } + } + + public Color3 HeadColor3 = Color3.FromRGB(253, 234, 141); + + public BrickColor LeftArmColor + { + get { return BrickColor.FromColor3(LeftArmColor3); } + set { LeftArmColor3 = value.Color; } + } + + public Color3 LeftArmColor3 = Color3.FromRGB(253, 234, 141); + + public BrickColor LeftLegColor + { + get { return BrickColor.FromColor3(LeftLegColor3); } + set { LeftLegColor3 = value.Color; } + } + + public Color3 LeftLegColor3 = Color3.FromRGB(13, 105, 172); + + public BrickColor RightArmColor + { + get { return BrickColor.FromColor3(RightArmColor3); } + set { RightArmColor3 = value.Color; } + } + + public Color3 RightArmColor3 = Color3.FromRGB(253, 234, 141); + + public BrickColor RightLegColor + { + get { return BrickColor.FromColor3(RightLegColor3); } + set { RightLegColor3 = value.Color; } + } + + public Color3 RightLegColor3 = Color3.FromRGB(13, 105, 172); + + public BrickColor TorsoColor + { + get { return BrickColor.FromColor3(TorsoColor3); } + set { TorsoColor3 = value.Color; } + } + + public Color3 TorsoColor3 = Color3.FromRGB(40, 127, 71); + } + + public class CharacterMesh : CharacterAppearance + { + public long BaseTextureId = 0; + public BodyPart BodyPart = BodyPart.Head; + public long MeshId = 0; + public long OverlayTextureId = 0; + } + + public abstract class Clothing : CharacterAppearance + { + public Color3 Color3 = new Color3(1, 1, 1); + } + + public class Pants : Clothing + { + public Content PantsTemplate = ""; + } + + public class Shirt : Clothing + { + public Content ShirtTemplate = ""; + } + + public class ShirtGraphic : CharacterAppearance + { + public Color3 Color3 = new Color3(1, 1, 1); + public Content Graphic = ""; + } + + public class Skin : CharacterAppearance + { + public BrickColor SkinColor = BrickColor.FromNumber(226); + } + + public class Chat : Instance + { + public Chat() + { + IsService = true; + } + + public bool BubbleChatEnabled = false; + public bool LoadDefaultChat = true; + } + + public class ClickDetector : Instance + { + public Content CursorIcon = ""; + public float MaxActivationDistance = 32; + } + + public class CollectionService : Instance + { + public CollectionService() + { + IsService = true; + } + } + + public class Configuration : Instance + { + } + + public abstract class Constraint : Instance + { + public Attachment Attachment0 = null; + public Attachment Attachment1 = null; + public BrickColor Color = BrickColor.FromNumber(23); + public bool Enabled = true; + public bool Visible = false; + } + + public class AlignOrientation : Constraint + { + public AlignType AlignType = AlignType.Parallel; + public float MaxAngularVelocity = float.MaxValue; + public float MaxTorque = 10000; + public bool PrimaryAxisOnly = false; + public bool ReactionTorqueEnabled = false; + public float Responsiveness = 10; + public bool RigidityEnabled = false; + } + + public class AlignPosition : Constraint + { + public bool ApplyAtCenterOfMass = false; + public float MaxForce = 10000; + public float MaxVelocity = float.MaxValue; + public bool ReactionForceEnabled = false; + public float Responsiveness = 10; + public bool RigidityEnabled = false; + } + + public class AngularVelocity : Constraint + { + public Vector3 AngularVelocity_ = new Vector3(); + public float MaxTorque = 0; + public ActuatorRelativeTo RelativeTo = ActuatorRelativeTo.World; + } + + public class BallSocketConstraint : Constraint + { + public bool LimitsEnabled = false; + public float Radius = 0.15f; + public float Restitution = 0; + public bool TwistLimitsEnabled = false; + public float TwistLowerAngle = -45; + public float TwistUpperAngle = 45; + public float UpperAngle = 45; + } + + public class HingeConstraint : Constraint + { + public ActuatorType ActuatorType = ActuatorType.None; + public float AngularSpeed = 0; + public float AngularVelocity = 0; + public bool LimitsEnabled = false; + public float LowerAngle = -45; + public float MotorMaxAcceleration = float.MaxValue; + public float MotorMaxTorque = 0; + public float Radius = 0.15f; + public float Restitution = 0; + public float ServoMaxTorque = 0; + public float TargetAngle = 0; + public float UpperAngle = 45; + } + + public class LineForce : Constraint + { + public bool ApplyAtCenterOfMass = false; + public bool InverseSquareLaw = false; + public float Magnitude = 1000; + public float MaxForce = float.MaxValue; + public bool ReactionForceEnabled = false; + } + + public class RodConstraint : Constraint + { + public float Length = 5; + public float Thickness = 0.1f; + } + + public class RopeConstraint : Constraint + { + public float Length = 5; + public float Restitution = 0; + public float Thickness = 0.1f; + } + + public abstract class SlidingBallConstraint : Constraint + { + public ActuatorType ActuatorType = ActuatorType.None; + public bool LimitsEnabled = false; + public float LowerLimit = 0; + public float MotorMaxAcceleration = float.MaxValue; + public float MotorMaxForce = 0; + public float Restitution = 0; + public float ServoMaxForce = 0; + public float Size = 0.15f; + public float Speed = 0; + public float TargetPosition = 0; + public float UpperLimit = 5; + public float Velocity = 0; + } + + public class CylindricalConstraint : SlidingBallConstraint + { + public ActuatorType AngularActuatorType = ActuatorType.None; + public bool AngularLimitsEnabled = false; + public float AngularRestitution = 0; + public float AngularSpeed = 0; + public float AngularVelocity = 0; + public float InclinationAngle = 0; + public float LowerAngle = -45; + public float MotorMaxAngularAcceleration = float.MaxValue; + public float MotorMaxTorque = 0; + public bool RotationAxisVisible = false; + public float ServoMaxTorque = 0; + public float TargetAngle = 0; + public float UpperAngle = 45; + } + + public class PrismaticConstraint : SlidingBallConstraint + { + } + + public class SpringConstraint : Constraint + { + public float Coils = 3; + public float Damping = 0; + public float FreeLength = 1; + public bool LimitsEnabled = false; + public float MaxForce = float.MaxValue; + public float MaxLength = 5; + public float MinLength = 0; + public float Radius = 0.4f; + public float Stiffness = 0; + public float Thickness = 0.1f; + } + + public class Torque : Constraint + { + public ActuatorRelativeTo RelativeTo = ActuatorRelativeTo.Attachment0; + public Vector3 Torque_ = new Vector3(); + } + + public class VectorForce : Constraint + { + public bool ApplyAtCenterOfMass = false; + public Vector3 Force = new Vector3(1000, 0, 0); + public ActuatorRelativeTo RelativeTo = ActuatorRelativeTo.Attachment0; + } + + public class ContentProvider : Instance + { + public ContentProvider() + { + IsService = true; + } + + } + + public class ContextActionService : Instance + { + public ContextActionService() + { + IsService = true; + } + } + + public abstract class Controller : Instance + { + } + + public class HumanoidController : Controller + { + } + + public class SkateboardController : Controller + { + } + + public class VehicleController : Controller + { + } + + public class ControllerService : Instance + { + public ControllerService() + { + IsService = true; + } + } + + public class CookiesService : Instance + { + public CookiesService() + { + IsService = true; + } + } + + public class CorePackages : Instance + { + public CorePackages() + { + IsService = true; + } + } + + public class CoreScriptSyncService : Instance + { + public CoreScriptSyncService() + { + IsService = true; + } + } + + public class CustomEvent : Instance + { + } + + public class CustomEventReceiver : Instance + { + public Instance Source = null; + } + + public abstract class DataModelMesh : Instance + { + public LevelOfDetailSetting LODX = LevelOfDetailSetting.High; + public LevelOfDetailSetting LODY = LevelOfDetailSetting.High; + public Vector3 Offset = new Vector3(); + public Vector3 Scale = new Vector3(1, 1, 1); + public Vector3 VertexColor = new Vector3(1, 1, 1); + } + + public abstract class BevelMesh : DataModelMesh + { + } + + public class BlockMesh : BevelMesh + { + } + + public class CylinderMesh : BevelMesh + { + } + + public class FileMesh : DataModelMesh + { + public Content MeshId = ""; + public Content TextureId = ""; + } + + public class SpecialMesh : FileMesh + { + public MeshType MeshType = MeshType.Head; + } + + public class DataStoreService : Instance + { + public DataStoreService() + { + IsService = true; + } + + public bool AutomaticRetry = true; + + [Obsolete] + public bool LegacyNamingScheme = false; + } + + public class Debris : Instance + { + public Debris() + { + IsService = true; + } + + [Obsolete] + public int MaxItems = 1000; + } + + public class DebuggerWatch : Instance + { + public string Expression = ""; + } + + public class Dialog : Instance + { + public DialogBehaviorType BehaviorType = DialogBehaviorType.SinglePlayer; + public float ConversationDistance = 25; + public bool GoodbyeChoiceActive = true; + public string GoodbyeDialog = ""; + public string InitialPrompt = ""; + public DialogPurpose Purpose = DialogPurpose.Help; + public DialogTone Tone = DialogTone.Neutral; + public float TriggerDistance = 0; + public Vector3 TriggerOffset = new Vector3(); + } + + public class DialogChoice : Instance + { + public bool GoodbyeChoiceActive = true; + public string GoodbyeDialog = ""; + public string ResponseDialog = ""; + public string UserDialog = ""; + } + + public class Dragger : Instance + { + } + + public class Explosion : Instance + { + public float BlastPressure = 500000; + public float BlastRadius = 4; + public float DestroyJointRadiusPercent = 1; + public ExplosionType ExplosionType = ExplosionType.Craters; + public Vector3 Position = new Vector3(); + public bool Visible = true; + } + + public abstract class FaceInstance : Instance + { + public NormalId Face = NormalId.Front; + } + + public class Decal : FaceInstance + { + public Color3 Color3 = new Color3(1, 1, 1); + + [Obsolete] + public float Shiny = 20; + + [Obsolete] + public float Specular = 0; + + public Content Texture = ""; + public float Transparency = 0; + } + + public class Texture : Decal + { + public float OffsetStudsU = 0; + public float OffsetStudsV = 0; + public float StudsPerTileU = 2; + public float StudsPerTileV = 2; + } + + public abstract class Feature : Instance + { + public NormalId FaceId = NormalId.Right; + public InOut InOut = InOut.Center; + public LeftRight LeftRight = LeftRight.Center; + public TopBottom TopBottom = TopBottom.Center; + } + + public class Hole : Feature + { + } + + public class MotorFeature : Feature + { + } + + public class Fire : Instance + { + public Color3 Color = Color3.FromRGB(236, 139, 70); + public bool Enabled = true; + + public float Heat + { + get { return heat_xml; } + set { heat_xml = value; } + } + + public Color3 SecondaryColor = Color3.FromRGB(139, 80, 55); + + public float Size + { + get { return size_xml; } + set { size_xml = value; } + } + + public float heat_xml = 9; + + [Obsolete] + public float size + { + get { return Size; } + set { Size = value; } + } + + public float size_xml = 5; + } + + public class FlyweightService : Instance + { + public FlyweightService() + { + IsService = true; + } + } + + public class CSGDictionaryService : FlyweightService + { + public CSGDictionaryService() + { + IsService = true; + } + } + + public class NonReplicatedCSGDictionaryService : FlyweightService + { + public NonReplicatedCSGDictionaryService() + { + IsService = true; + } + } + + public class Folder : Instance + { + } + + public class ForceField : Instance + { + public bool Visible = true; + } + + public class FriendService : Instance + { + public FriendService() + { + IsService = true; + } + } + + public class FunctionalTest : Instance + { + public string Description = "?"; + } + + public class GamePassService : Instance + { + public GamePassService() + { + IsService = true; + } + } + + public class GamepadService : Instance + { + public GamepadService() + { + IsService = true; + } + } + + public class Geometry : Instance + { + public Geometry() + { + IsService = true; + } + } + + public class GroupService : Instance + { + public GroupService() + { + IsService = true; + } + } + + public abstract class GuiBase : Instance + { + } + + public abstract class GuiBase2d : GuiBase + { + public bool AutoLocalize = true; + + [Obsolete] + public bool Localize + { + get { return AutoLocalize; } + set { AutoLocalize = value; } + } + + public LocalizationTable RootLocalizationTable = null; + } + + public abstract class GuiObject : GuiBase2d + { + public bool Active = false; + public Vector2 AnchorPoint = new Vector2(); + + [Obsolete] + public BrickColor BackgroundColor + { + get { return BrickColor.FromColor3(BackgroundColor3); } + set { BackgroundColor3 = value.Color; } + } + + public Color3 BackgroundColor3 = Color3.FromRGB(163, 162, 165); + public float BackgroundTransparency = 0; + + [Obsolete] + public BrickColor BorderColor + { + get { return BrickColor.FromColor3(BorderColor3); } + set { BorderColor3 = value.Color; } + } + + public Color3 BorderColor3 = Color3.FromRGB(27, 42, 53); + public int BorderSizePixel = 1; + public bool ClipsDescendants = false; + + [Obsolete] + public bool Draggable = false; + + public int LayoutOrder = 0; + public GuiObject NextSelectionDown = null; + public GuiObject NextSelectionLeft = null; + public GuiObject NextSelectionRight = null; + public GuiObject NextSelectionUp = null; + public UDim2 Position = new UDim2(); + public float Rotation = 0; + public bool Selectable = false; + public GuiObject SelectionImageObject = null; + public UDim2 Size = new UDim2(); + public SizeConstraint SizeConstraint = SizeConstraint.RelativeXY; + + public float Transparency + { + get { return BackgroundTransparency; } + set { BackgroundTransparency = value; } + } + + public bool Visible = true; + public int ZIndex = 1; + } + + public class Frame : GuiObject + { + public FrameStyle Style = FrameStyle.Custom; + } + + public abstract class GuiButton : GuiObject + { + public bool AutoButtonColor = true; + public bool Modal = false; + public bool Selected = false; + public ButtonStyle Style = ButtonStyle.Custom; + } + + public class ImageButton : GuiButton + { + public Content HoverImage = ""; + public Content Image = ""; + public Color3 ImageColor3 = new Color3(1, 1, 1); + public Vector2 ImageRectOffset = new Vector2(); + public Vector2 ImageRectSize = new Vector2(); + public float ImageTransparency = 0; + public Content PressedImage = ""; + public ScaleType ScaleType = ScaleType.Stretch; + public Rect SliceCenter = new Rect(new Vector2(), new Vector2()); + public float SliceScale = 1; + public UDim2 TileSize = new UDim2(1, 0, 1, 0); + } + + public class TextButton : GuiButton + { + public Font Font = Font.Legacy; + + [Obsolete] + public FontSize FontSize + { + get { return FontUtility.GetFontSize(TextSize); } + set { TextSize = FontUtility.GetFontSize(value); } + } + + public float LineHeight = 1; + public string Text = "Button"; + + [Obsolete] + public BrickColor TextColor + { + get { return BrickColor.FromColor3(TextColor3); } + set { TextColor3 = value.Color; } + } + + public Color3 TextColor3 = Color3.FromRGB(27, 42, 53); + public bool TextScaled = false; + public float TextSize = 8; + public Color3 TextStrokeColor3 = new Color3(); + public float TextStrokeTransparency = 1; + public float TextTransparency = 0; + public TextTruncate TextTruncate = TextTruncate.None; + + [Obsolete] + public bool TextWrap + { + get { return TextWrapped; } + set { TextWrapped = value; } + } + + public bool TextWrapped = false; + public TextXAlignment TextXAlignment = TextXAlignment.Center; + public TextYAlignment TextYAlignment = TextYAlignment.Center; + } + + public abstract class GuiLabel : GuiObject + { + } + + public class ImageLabel : GuiLabel + { + public Content Image = ""; + public Color3 ImageColor3 = new Color3(1, 1, 1); + public Vector2 ImageRectOffset = new Vector2(); + public Vector2 ImageRectSize = new Vector2(); + public float ImageTransparency = 0; + public ScaleType ScaleType = ScaleType.Stretch; + public Rect SliceCenter = new Rect(new Vector2(), new Vector2()); + public float SliceScale = 1; + public UDim2 TileSize = new UDim2(1, 0, 1, 0); + } + + public class TextLabel : GuiLabel + { + public Font Font = Font.Legacy; + + [Obsolete] + public FontSize FontSize + { + get { return FontUtility.GetFontSize(TextSize); } + set { TextSize = FontUtility.GetFontSize(value); } + } + + public float LineHeight = 1; + public string Text = "Label"; + + [Obsolete] + public BrickColor TextColor + { + get { return BrickColor.FromColor3(TextColor3); } + set { TextColor3 = value.Color; } + } + + public Color3 TextColor3 = Color3.FromRGB(27, 42, 53); + public bool TextScaled = false; + public float TextSize = 8; + public Color3 TextStrokeColor3 = new Color3(); + public float TextStrokeTransparency = 1; + public float TextTransparency = 0; + public TextTruncate TextTruncate = TextTruncate.None; + + [Obsolete] + public bool TextWrap + { + get { return TextWrapped; } + set { TextWrapped = value; } + } + + public bool TextWrapped = false; + public TextXAlignment TextXAlignment = TextXAlignment.Center; + public TextYAlignment TextYAlignment = TextYAlignment.Center; + } + + public class ScrollingFrame : GuiObject + { + public Content BottomImage = "rbxasset://textures/ui/Scroll/scroll-bottom.png"; + public Vector2 CanvasPosition = new Vector2(); + public UDim2 CanvasSize = new UDim2(0, 0, 2, 0); + public ElasticBehavior ElasticBehavior = ElasticBehavior.WhenScrollable; + public ScrollBarInset HorizontalScrollBarInset = ScrollBarInset.None; + public Content MidImage = "rbxasset://textures/ui/Scroll/scroll-middle.png"; + public Color3 ScrollBarImageColor3 = new Color3(1, 1, 1); + public float ScrollBarImageTransparency = 0; + public int ScrollBarThickness = 12; + public ScrollingDirection ScrollingDirection = ScrollingDirection.XY; + public bool ScrollingEnabled = true; + public Content TopImage = "rbxasset://textures/ui/Scroll/scroll-top.png"; + public ScrollBarInset VerticalScrollBarInset = ScrollBarInset.None; + public VerticalScrollBarPosition VerticalScrollBarPosition = VerticalScrollBarPosition.Right; + } + + public class TextBox : GuiObject + { + public bool ClearTextOnFocus = true; + public Font Font = Font.Legacy; + + [Obsolete] + public FontSize FontSize + { + get { return FontUtility.GetFontSize(TextSize); } + set { TextSize = FontUtility.GetFontSize(value); } + } + + public bool IsPassword = false; // [Load-only] + public float LineHeight = 1; + public bool ManualFocusRelease = false; // [Load-only] + public bool MultiLine = false; + public bool OverlayNativeInput = false; // [Load-only] + public Color3 PlaceholderColor3 = Color3.FromRGB(178, 178, 178); + public string PlaceholderText = ""; + public bool ShowNativeInput = true; + public string Text = "TextBox"; + + [Obsolete] + public BrickColor TextColor + { + get { return BrickColor.FromColor3(TextColor3); } + set { TextColor3 = value.Color; } + } + + public Color3 TextColor3 = Color3.FromRGB(27, 42, 53); + public bool TextEditable = true; + public bool TextScaled = false; + public float TextSize = 8; + public Color3 TextStrokeColor3 = new Color3(); + public float TextStrokeTransparency = 1; + public float TextTransparency = 0; + public TextTruncate TextTruncate = TextTruncate.None; + + [Obsolete] + public bool TextWrap + { + get { return TextWrapped; } + set { TextWrapped = value; } + } + + public bool TextWrapped = false; + public TextXAlignment TextXAlignment = TextXAlignment.Center; + public TextYAlignment TextYAlignment = TextYAlignment.Center; + } + + public class ViewportFrame : GuiObject + { + public Color3 Ambient = Color3.FromRGB(200, 200, 200); + public CFrame CameraCFrame = new CFrame(); + public float CameraFieldOfView = 70; + public Color3 ImageColor3 = new Color3(1, 1, 1); + public float ImageTransparency = 0; + public Color3 LightColor = Color3.FromRGB(140, 140, 140); + public Vector3 LightDirection = new Vector3(-1, -1, -1); + } + + public abstract class LayerCollector : GuiBase2d + { + public bool Enabled = true; + public bool ResetOnSpawn = true; + public ZIndexBehavior ZIndexBehavior = ZIndexBehavior.Global; + } + + public class BillboardGui : LayerCollector + { + public bool Active = false; + public Instance Adornee = null; + public bool AlwaysOnTop = false; + public bool ClipsDescendants = false; + public float DistanceLowerLimit = 0; + public float DistanceStep = 0; + public float DistanceUpperLimit = -1; + public Vector3 ExtentsOffset = new Vector3(); + public Vector3 ExtentsOffsetWorldSpace = new Vector3(); + public float LightInfluence = 0; + public float MaxDistance = float.MaxValue; + public Instance PlayerToHideFrom = null; + public UDim2 Size = new UDim2(); + public Vector2 SizeOffset = new Vector2(); + public Vector3 StudsOffset = new Vector3(); + public Vector3 StudsOffsetWorldSpace = new Vector3(); + } + + public class ScreenGui : LayerCollector + { + public int DisplayOrder = 0; + public bool IgnoreGuiInset = false; + } + + public class GuiMain : ScreenGui + { + } + + public class SurfaceGui : LayerCollector + { + public bool Active = true; + public Instance Adornee = null; + public bool AlwaysOnTop = false; + public Vector2 CanvasSize = new Vector2(800, 600); + public bool ClipsDescendants = false; + public NormalId Face = NormalId.Front; + public float LightInfluence = 0; + public float PixelsPerStud = 50; + public SurfaceGuiSizingMode SizingMode = SurfaceGuiSizingMode.FixedSize; + public float ToolPunchThroughDistance = 0; + public float ZOffset = 0; + } + + public abstract class GuiBase3d : GuiBase + { + [Obsolete] + public BrickColor Color + { + get { return BrickColor.FromColor3(Color3); } + set { Color3 = value.Color; } + } + + public Color3 Color3 = Color3.FromRGB(13, 105, 172); + public float Transparency = 0; + public bool Visible = true; + } + + public class FloorWire : GuiBase3d + { + public float CycleOffset = 0; + public BasePart From = null; + public float StudsBetweenTextures = 4; + public Content Texture = ""; + public Vector2 TextureSize = new Vector2(1, 1); + public BasePart To = null; + public float Velocity = 2; + public float WireRadius = 0.0625f; + } + + public abstract class PVAdornment : GuiBase3d + { + public PVInstance Adornee = null; + } + + public abstract class HandleAdornment : PVAdornment + { + public bool AlwaysOnTop = false; + public CFrame CFrame = new CFrame(); + public Vector3 SizeRelativeOffset = new Vector3(); + public int ZIndex = -1; + } + + public class BoxHandleAdornment : HandleAdornment + { + public Vector3 Size = new Vector3(1, 1, 1); + } + + public class ConeHandleAdornment : HandleAdornment + { + public float Height = 2; + public float Radius = 0.5f; + } + + public class CylinderHandleAdornment : HandleAdornment + { + public float Height = 1; + public float Radius = 1; + } + + public class ImageHandleAdornment : HandleAdornment + { + public Content Image = "rbxasset://textures/SurfacesDefault.png"; + public Vector2 Size = new Vector2(1, 1); + } + + public class LineHandleAdornment : HandleAdornment + { + public float Length = 5; + public float Thickness = 1; + } + + public class SphereHandleAdornment : HandleAdornment + { + public float Radius = 1; + } + + public class ParabolaAdornment : PVAdornment + { + } + + public class SelectionBox : PVAdornment + { + public float LineThickness = 0.15f; + + [Obsolete] + public BrickColor SurfaceColor + { + get { return BrickColor.FromColor3(SurfaceColor3); } + set { SurfaceColor3 = value.Color; } + } + + public Color3 SurfaceColor3 = Color3.FromRGB(13, 105, 172); + public float SurfaceTransparency = 1; + } + + public class SelectionSphere : PVAdornment + { + [Obsolete] + public BrickColor SurfaceColor + { + get { return BrickColor.FromColor3(SurfaceColor3); } + set { SurfaceColor3 = value.Color; } + } + + public Color3 SurfaceColor3 = Color3.FromRGB(13, 105, 172); + public float SurfaceTransparency = 1; + } + + public abstract class PartAdornment : GuiBase3d + { + public BasePart Adornee = null; + } + + public abstract class HandlesBase : PartAdornment + { + } + + public class ArcHandles : HandlesBase + { + public Axes Axes = (Axes)7; + } + + public class Handles : HandlesBase + { + public Faces Faces = (Faces)63; + public HandlesStyle Style = HandlesStyle.Resize; + } + + public class SurfaceSelection : PartAdornment + { + public NormalId TargetSurface = NormalId.Right; + } + + public abstract class SelectionLasso : GuiBase3d + { + public Humanoid Humanoid = null; + } + + public class SelectionPartLasso : SelectionLasso + { + public BasePart Part = null; + } + + public class SelectionPointLasso : SelectionLasso + { + public Vector3 Point = new Vector3(); + } + + public class GuiService : Instance + { + public GuiService() + { + IsService = true; + } + + public bool AutoSelectGuiEnabled = true; + public bool CoreGuiNavigationEnabled = true; + public bool GuiNavigationEnabled = true; + public GuiObject SelectedCoreObject = null; + public GuiObject SelectedObject = null; + } + + public class HapticService : Instance + { + public HapticService() + { + IsService = true; + } + } + + public class HttpRbxApiService : Instance + { + public HttpRbxApiService() + { + IsService = true; + } + } + + public class HttpService : Instance + { + public HttpService() + { + IsService = true; + } + + public bool HttpEnabled = false; + } + + public class Humanoid : Instance + { + public bool AutoJumpEnabled = true; + public bool AutoRotate = true; + public bool AutomaticScalingEnabled = true; + public bool BreakJointsOnDeath = true; + public HumanoidCollisionType CollisionType = HumanoidCollisionType.OuterBox; + public HumanoidDisplayDistanceType DisplayDistanceType = HumanoidDisplayDistanceType.Viewer; + + public float Health + { + get { return Health_XML; } + set { Health_XML = value; } + } + + public float HealthDisplayDistance = 100; + public HumanoidHealthDisplayType HealthDisplayType = HumanoidHealthDisplayType.DisplayWhenDamaged; + public float Health_XML = 100; + public float HipHeight = 0; + public Vector3 InternalBodyScale = new Vector3(1, 1, 1); + public float InternalHeadScale = 1; + public float JumpHeight = 7.2f; + public float JumpPower = 50; + public float MaxHealth = 100; + public float MaxSlopeAngle = 89; + public float NameDisplayDistance = 100; + public NameOcclusion NameOcclusion = NameOcclusion.OccludeAll; + public HumanoidRigType RigType = HumanoidRigType.R6; + public bool UseJumpPower = true; + public float WalkSpeed = 16; + + [Obsolete] + public float maxHealth + { + get { return MaxHealth; } + set { MaxHealth = value; } + } + } + + public class HumanoidDescription : Instance + { + public string BackAccessory = ""; + public float BodyTypeScale = 0.3f; + public long ClimbAnimation = 0; + public float DepthScale = 1; + public string EmotesDataInternal = ""; + public string EquippedEmotesDataInternal = ""; + public long Face = 0; + public string FaceAccessory = ""; + public long FallAnimation = 0; + public string FrontAccessory = ""; + public long GraphicTShirt = 0; + public string HairAccessory = ""; + public string HatAccessory = ""; + public long Head = 0; + public Color3 HeadColor = new Color3(); + public float HeadScale = 1; + public float HeightScale = 1; + public long IdleAnimation = 0; + public long JumpAnimation = 0; + public long LeftArm = 0; + public Color3 LeftArmColor = new Color3(); + public long LeftLeg = 0; + public Color3 LeftLegColor = new Color3(); + public string NeckAccessory = ""; + public long Pants = 0; + public float ProportionScale = 1; + public long RightArm = 0; + public Color3 RightArmColor = new Color3(); + public long RightLeg = 0; + public Color3 RightLegColor = new Color3(); + public long RunAnimation = 0; + public long Shirt = 0; + public string ShouldersAccessory = ""; + public long SwimAnimation = 0; + public long Torso = 0; + public Color3 TorsoColor = new Color3(); + public string WaistAccessory = ""; + public long WalkAnimation = 0; + public float WidthScale = 1; + } + + public class InsertService : Instance + { + public InsertService() + { + IsService = true; + } + + public bool AllowClientInsertModels = false; + + [Obsolete] + public bool AllowInsertFreeModels = false; + } + + public abstract class JointInstance : Instance + { + public CFrame C0 = new CFrame(); + public CFrame C1 = new CFrame(); + public bool IsAutoJoint = true; + public BasePart Part0 = null; + public BasePart Part1 = null; + } + + public abstract class DynamicRotate : JointInstance + { + public float BaseAngle = 0; + } + + public class RotateP : DynamicRotate + { + } + + public class RotateV : DynamicRotate + { + } + + public class Glue : JointInstance + { + public Vector3 F0 = new Vector3(); + public Vector3 F1 = new Vector3(); + public Vector3 F2 = new Vector3(); + public Vector3 F3 = new Vector3(); + } + + public abstract class ManualSurfaceJointInstance : JointInstance + { + public int Surface0 = -1; + public int Surface1 = -1; + } + + public class ManualGlue : ManualSurfaceJointInstance + { + } + + public class ManualWeld : ManualSurfaceJointInstance + { + } + + public class Motor : JointInstance + { + public float CurrentAngle = 0; // [Load-only] + public float DesiredAngle = 0; + public float MaxVelocity = 0; + } + + public class Motor6D : Motor + { + } + + public class Rotate : JointInstance + { + } + + public class Snap : JointInstance + { + } + + public class VelocityMotor : JointInstance + { + public float CurrentAngle = 0; + public float DesiredAngle = 0; + public Hole Hole = null; + public float MaxVelocity = 0; + } + + public class Weld : JointInstance + { + } + + public class JointsService : Instance + { + public JointsService() + { + IsService = true; + } + } + + public class KeyboardService : Instance + { + public KeyboardService() + { + IsService = true; + } + } + + public class Keyframe : Instance + { + public float Time = 0; + } + + public class KeyframeMarker : Instance + { + public string Value = ""; + } + + public class KeyframeSequence : Instance + { + public float AuthoredHipHeight = 2; + public bool Loop = true; + public AnimationPriority Priority = AnimationPriority.Action; + } + + public class KeyframeSequenceProvider : Instance + { + public KeyframeSequenceProvider() + { + IsService = true; + } + } + + public abstract class Light : Instance + { + public float Brightness = 1; + public Color3 Color = new Color3(1, 1, 1); + public bool Enabled = true; + public bool Shadows = false; + } + + public class PointLight : Light + { + public float Range = 8; + } + + public class SpotLight : Light + { + public float Angle = 90; + public NormalId Face = NormalId.Front; + public float Range = 16; + } + + public class SurfaceLight : Light + { + public float Angle = 90; + public NormalId Face = NormalId.Front; + public float Range = 16; + } + + public class Lighting : Instance + { + public Lighting() + { + IsService = true; + } + + public Color3 Ambient = new Color3(); + public float Brightness = 2; + public Color3 ColorShift_Bottom = new Color3(); + public Color3 ColorShift_Top = new Color3(); + public float ExposureCompensation = 0; + public Color3 FogColor = Color3.FromRGB(192, 192, 192); + public float FogEnd = 100000; + public float FogStart = 0; + public float GeographicLatitude = 41.7333f; + public bool GlobalShadows = true; + public bool LegacyOutlines = false; + public Color3 OutdoorAmbient = Color3.FromRGB(128, 128, 128); + + public bool Outlines + { + get { return LegacyOutlines; } + set { LegacyOutlines = value; } + } + + [Obsolete] + public Color3 ShadowColor = Color3.FromRGB(178, 178, 183); + + public float ShadowSoftness = 0.5f; + public Technology Technology = Technology.Compatibility; + public string TimeOfDay = "14:00:00"; + } + + public abstract class LocalStorageService : Instance + { + public LocalStorageService() + { + IsService = true; + } + } + + public class LocalizationService : Instance + { + public LocalizationService() + { + IsService = true; + } + + } + + public class LocalizationTable : Instance + { + public string Contents = "[]"; + + [Obsolete] + public string DevelopmentLanguage + { + get { return SourceLocaleId; } + set { SourceLocaleId = value; } + } + + public string SourceLocaleId = "en-us"; + } + + public class LogService : Instance + { + public LogService() + { + IsService = true; + } + } + + public abstract class LuaSourceContainer : Instance + { + } + + public abstract class BaseScript : LuaSourceContainer + { + public bool Disabled = false; + public Content LinkedSource = ""; + } + + public class Script : BaseScript + { + public ProtectedString Source = ""; + } + + public class LocalScript : Script + { + } + + public class ModuleScript : LuaSourceContainer + { + public Content LinkedSource = ""; + public ProtectedString Source = ""; + } + + public class LuaWebService : Instance + { + public LuaWebService() + { + IsService = true; + } + } + + public class MarketplaceService : Instance + { + public MarketplaceService() + { + IsService = true; + } + } + + public class Message : Instance + { + public string Text = ""; + } + + public class Hint : Message + { + } + + public class MessagingService : Instance + { + public MessagingService() + { + IsService = true; + } + } + + public class MouseService : Instance + { + public MouseService() + { + IsService = true; + } + } + + public abstract class NetworkPeer : Instance + { + } + + public class NoCollisionConstraint : Instance + { + public bool Enabled = true; + public BasePart Part0 = null; + public BasePart Part1 = null; + } + + public class NotificationService : Instance + { + public NotificationService() + { + IsService = true; + } + + } + + public abstract class PVInstance : Instance + { + } + + public abstract class BasePart : PVInstance + { + public bool Anchored = false; + public float BackParamA = -0.5f; + public float BackParamB = 0.5f; + public SurfaceType BackSurface = SurfaceType.Smooth; + public InputType BackSurfaceInput = InputType.NoInput; + public float BottomParamA = -0.5f; + public float BottomParamB = 0.5f; + public SurfaceType BottomSurface = SurfaceType.Smooth; + public InputType BottomSurfaceInput = InputType.NoInput; + + public BrickColor BrickColor + { + get { return BrickColor.FromColor3(Color); } + set { Color = value.Color; } + } + + public CFrame CFrame = new CFrame(); + public bool CanCollide = true; + public bool CastShadow = true; + public int CollisionGroupId = 0; + + public Color3 Color + { + get { return Color3uint8; } + set { Color3uint8 = value; } + } + + public Color3uint8 Color3uint8 = Color3.FromRGB(163, 162, 165); + public PhysicalProperties CustomPhysicalProperties = null; + + [Obsolete] + public float Elasticity = 0.5f; + + [Obsolete] + public float Friction = 0.3f; + + public float FrontParamA = -0.5f; + public float FrontParamB = 0.5f; + public SurfaceType FrontSurface = SurfaceType.Smooth; + public InputType FrontSurfaceInput = InputType.NoInput; + public float LeftParamA = -0.5f; + public float LeftParamB = 0.5f; + public SurfaceType LeftSurface = SurfaceType.Smooth; + public InputType LeftSurfaceInput = InputType.NoInput; + public bool Locked = false; + public bool Massless = false; + public Material Material = Material.Plastic; + + public Vector3 Position + { + get { return CFrame.Position; } + set { CFrame.Position = value; } + } + + public float Reflectance = 0; + public float RightParamA = -0.5f; + public float RightParamB = 0.5f; + public SurfaceType RightSurface = SurfaceType.Smooth; + public InputType RightSurfaceInput = InputType.NoInput; + public int RootPriority = 0; + public Vector3 RotVelocity = new Vector3(); + + public Vector3 Size + { + get { return size; } + set { size = value; } + } + + public float TopParamA = -0.5f; + public float TopParamB = 0.5f; + public SurfaceType TopSurface = SurfaceType.Smooth; + public InputType TopSurfaceInput = InputType.NoInput; + public float Transparency = 0; + public Vector3 Velocity = new Vector3(); + + [Obsolete] + public BrickColor brickColor + { + get { return BrickColor; } + set { BrickColor = value; } + } + + public Vector3 size = new Vector3(4, 1.2f, 2); + } + + public class CornerWedgePart : BasePart + { + } + + public abstract class FormFactorPart : BasePart + { + [Obsolete] + public FormFactor FormFactor + { + get { return formFactorRaw; } + set { formFactorRaw = value; } + } + + [Obsolete] + public FormFactor formFactor + { + get { return FormFactor; } + set { FormFactor = value; } + } + + public FormFactor formFactorRaw = FormFactor.Brick; + } + + public class Part : FormFactorPart + { + public PartType Shape + { + get { return shape; } + set { shape = value; } + } + + public PartType shape = PartType.Block; + } + + public class FlagStand : Part + { + public BrickColor TeamColor = BrickColor.FromNumber(194); + } + + public class Seat : Part + { + public bool Disabled = false; + } + + public class SkateboardPlatform : Part + { + public int Steer = 0; + public bool StickyWheels = true; + public int Throttle = 0; + } + + public class SpawnLocation : Part + { + public bool AllowTeamChangeOnTouch = false; + public int Duration = 10; + public bool Enabled = true; + public bool Neutral = true; + public BrickColor TeamColor = BrickColor.FromNumber(194); + } + + public class WedgePart : FormFactorPart + { + } + + public class Terrain : BasePart + { + public string ClusterGrid = ""; + public string ClusterGridV2 = ""; + public byte[] ClusterGridV3 = new byte[0]; + public byte[] MaterialColors = Convert.FromBase64String("AAAAAAAAan8/P39rf2Y/ilY+j35fi21PZmxvZbDqw8faiVpHOi4kHh4lZlw76JxKc3trhHtagcLgc4RKxr21zq2UlJSM"); + public byte[] PhysicsGrid = Convert.FromBase64String("AgMAAAAAAAAAAAAAAAA="); + public byte[] SmoothGrid = Convert.FromBase64String("AQU="); + public Color3 WaterColor = Color3.FromRGB(12, 84, 92); + public float WaterReflectance = 1; + public float WaterTransparency = 0.3f; + public float WaterWaveSize = 0.15f; + public float WaterWaveSpeed = 10; + } + + public abstract class TriangleMeshPart : BasePart + { + public Vector3 InitialSize = new Vector3(1, 1, 1); + public byte[] LODData = new byte[0]; + public SharedString PhysicalConfigData = SharedString.FromBase64("1B2M2Y8AsgTpgAmY7PhCfg=="); + public byte[] PhysicsData = new byte[0]; + } + + public class MeshPart : TriangleMeshPart + { + [Obsolete] + public Content MeshID + { + get { return MeshId; } + set { MeshId = value; } + } + + public Content MeshId = ""; + public RenderFidelity RenderFidelity = RenderFidelity.Precise; + public Content TextureID = ""; + } + + public class PartOperation : TriangleMeshPart + { + public Content AssetId = ""; + public byte[] ChildData = new byte[0]; + public byte[] MeshData = new byte[0]; + public RenderFidelity RenderFidelity = RenderFidelity.Precise; + public bool UsePartColor = false; + } + + public class NegateOperation : PartOperation + { + } + + public class UnionOperation : PartOperation + { + } + + public class TrussPart : BasePart + { + public Style Style + { + get { return style; } + set { style = value; } + } + + public Style style = Style.AlternatingSupports; + } + + public class VehicleSeat : BasePart + { + public bool Disabled = false; + public bool HeadsUpDisplay = true; + public float MaxSpeed = 25; + public int Steer = 0; + public float SteerFloat = 0; + public int Throttle = 0; + public float ThrottleFloat = 0; + public float Torque = 10; + public float TurnSpeed = 1; + } + + public class Model : PVInstance + { + public CFrame ModelInPrimary = new CFrame(); + public BasePart PrimaryPart = null; + } + + public class Workspace : Model + { + public Workspace() + { + IsService = true; + } + + public bool AllowThirdPartySales = false; + public AutoJointsMode AutoJointsMode = AutoJointsMode.Default; + public string CollisionGroups = "Default^0^1"; + public Camera CurrentCamera = null; + public double DistributedGameTime = 0; + public bool ExplicitAutoJoints = true; + public float FallenPartsDestroyHeight = -500; + public bool FilteringEnabled = true; + public float Gravity = 196.2f; + public bool StreamingEnabled = false; + public int StreamingMinRadius = 64; + public StreamingPauseMode StreamingPauseMode = StreamingPauseMode.Default; + public int StreamingTargetRadius = 1024; + public bool TerrainWeldsFixed = true; + } + + public class PackageService : Instance + { + public PackageService() + { + IsService = true; + } + } + + public class PartOperationAsset : Instance + { + public byte[] ChildData = new byte[0]; + public byte[] MeshData = new byte[0]; + } + + public class ParticleEmitter : Instance + { + public Vector3 Acceleration = new Vector3(); + public ColorSequence Color = new ColorSequence(new Color3(1, 1, 1)); + public float Drag = 0; + public NormalId EmissionDirection = NormalId.Top; + public bool Enabled = true; + public NumberRange Lifetime = new NumberRange(5, 10); + public float LightEmission = 0; + public float LightInfluence = 0; + public bool LockedToPart = false; + public float Rate = 20; + public NumberRange RotSpeed = new NumberRange(0); + public NumberRange Rotation = new NumberRange(0); + public NumberSequence Size = new NumberSequence(1); + public NumberRange Speed = new NumberRange(5); + public Vector2 SpreadAngle = new Vector2(); + public Content Texture = "rbxasset://textures/particles/sparkles_main.dds"; + public NumberSequence Transparency = new NumberSequence(0); + public float VelocityInheritance = 0; + + [Obsolete] + public float VelocitySpread + { + get { return SpreadAngle.X; } + set { SpreadAngle = new Vector2(value, value); } + } + + public float ZOffset = 0; + } + + public class PathfindingService : Instance + { + public PathfindingService() + { + IsService = true; + } + + [Obsolete] + public float EmptyCutoff = 0; + } + + public class PhysicsService : Instance + { + public PhysicsService() + { + IsService = true; + } + } + + public class Players : Instance + { + public Players() + { + IsService = true; + } + + public bool CharacterAutoLoads = true; + public int MaxPlayersInternal = 16; + public int PreferredPlayersInternal = 0; + public float RespawnTime = 5; + } + + public class PluginAction : Instance + { + public bool Enabled = true; // [Load-only] + } + + public class PluginGuiService : Instance + { + public PluginGuiService() + { + IsService = true; + } + } + + public class PointsService : Instance + { + public PointsService() + { + IsService = true; + } + } + + public class Pose : Instance + { + public CFrame CFrame = new CFrame(); + public PoseEasingDirection EasingDirection = PoseEasingDirection.In; + public PoseEasingStyle EasingStyle = PoseEasingStyle.Linear; + + [Obsolete] + public float MaskWeight = 0; + + public float Weight = 1; + } + + public abstract class PostEffect : Instance + { + public bool Enabled = true; + } + + public class BloomEffect : PostEffect + { + public float Intensity = 0.4f; + public float Size = 24; + public float Threshold = 0.95f; + } + + public class BlurEffect : PostEffect + { + public float Size = 24; + } + + public class ColorCorrectionEffect : PostEffect + { + public float Brightness = 0; + public float Contrast = 0; + public float Saturation = 0; + public Color3 TintColor = new Color3(1, 1, 1); + } + + public class SunRaysEffect : PostEffect + { + public float Intensity = 0.25f; + public float Spread = 1; + } + + public class RbxAnalyticsService : Instance + { + public RbxAnalyticsService() + { + IsService = true; + } + } + + public class ReflectionMetadata : Instance + { + } + + public class ReflectionMetadataCallbacks : Instance + { + } + + public class ReflectionMetadataClasses : Instance + { + } + + public class ReflectionMetadataEnums : Instance + { + } + + public class ReflectionMetadataEvents : Instance + { + } + + public class ReflectionMetadataFunctions : Instance + { + } + + public abstract class ReflectionMetadataItem : Instance + { + public bool Browsable = true; + public string ClassCategory = ""; + public bool ClientOnly = false; + public string Constraint = ""; + public bool Deprecated = false; + public bool EditingDisabled = false; + public bool IsBackend = false; + public string ScriptContext = ""; + public bool ServerOnly = false; + public double UIMaximum = 0; + public double UIMinimum = 0; + public double UINumTicks = 0; + public string summary = ""; + } + + public class ReflectionMetadataClass : ReflectionMetadataItem + { + public int ExplorerImageIndex = 0; + public int ExplorerOrder = 2147483647; + public bool Insertable = true; + public string PreferredParent = ""; + } + + public class ReflectionMetadataEnum : ReflectionMetadataItem + { + } + + public class ReflectionMetadataEnumItem : ReflectionMetadataItem + { + } + + public class ReflectionMetadataMember : ReflectionMetadataItem + { + } + + public class ReflectionMetadataProperties : Instance + { + } + + public class ReflectionMetadataYieldFunctions : Instance + { + } + + public class RemoteEvent : Instance + { + } + + public class RemoteFunction : Instance + { + } + + public class RenderingTest : Instance + { + public CFrame CFrame = new CFrame(); + public int ComparisonDiffThreshold = 10; + public RenderingTestComparisonMethod ComparisonMethod = RenderingTestComparisonMethod.psnr; + public float ComparisonPsnrThreshold = 50; + public string Description = ""; + public float FieldOfView = 70; + public int QualityLevel = 21; + public bool ShouldSkip = false; + public string Ticket = ""; + } + + public class ReplicatedFirst : Instance + { + public ReplicatedFirst() + { + IsService = true; + } + } + + public class ReplicatedStorage : Instance + { + public ReplicatedStorage() + { + IsService = true; + } + } + + public class RobloxPluginGuiService : Instance + { + public RobloxPluginGuiService() + { + IsService = true; + } + } + + public class RobloxReplicatedStorage : Instance + { + public RobloxReplicatedStorage() + { + IsService = true; + } + } + + public class RunService : Instance + { + public RunService() + { + IsService = true; + } + } + + public class RuntimeScriptService : Instance + { + public RuntimeScriptService() + { + IsService = true; + } + } + + public class ScriptContext : Instance + { + public ScriptContext() + { + IsService = true; + } + } + + public class ScriptService : Instance + { + public ScriptService() + { + IsService = true; + } + } + + public class Selection : Instance + { + public Selection() + { + IsService = true; + } + } + + public class ServerScriptService : Instance + { + public ServerScriptService() + { + IsService = true; + } + + public bool LoadStringEnabled = false; + } + + public class ServerStorage : Instance + { + public ServerStorage() + { + IsService = true; + } + } + + public class Sky : Instance + { + public bool CelestialBodiesShown = true; + public float MoonAngularSize = 11; + public Content MoonTextureId = "rbxasset://sky/moon.jpg"; + public Content SkyboxBk = "rbxasset://textures/sky/sky512_bk.tex"; + public Content SkyboxDn = "rbxasset://textures/sky/sky512_dn.tex"; + public Content SkyboxFt = "rbxasset://textures/sky/sky512_ft.tex"; + public Content SkyboxLf = "rbxasset://textures/sky/sky512_lf.tex"; + public Content SkyboxRt = "rbxasset://textures/sky/sky512_rt.tex"; + public Content SkyboxUp = "rbxasset://textures/sky/sky512_up.tex"; + public int StarCount = 3000; + public float SunAngularSize = 21; + public Content SunTextureId = "rbxasset://sky/sun.jpg"; + } + + public class Smoke : Instance + { + public Color3 Color = new Color3(1, 1, 1); + public bool Enabled = true; + + public float Opacity + { + get { return opacity_xml; } + set { opacity_xml = value; } + } + + public float RiseVelocity + { + get { return riseVelocity_xml; } + set { riseVelocity_xml = value; } + } + + public float Size + { + get { return size_xml; } + set { size_xml = value; } + } + + public float opacity_xml = 0.5f; + public float riseVelocity_xml = 1; + public float size_xml = 1; + } + + public class SocialService : Instance + { + public SocialService() + { + IsService = true; + } + } + + public class Sound : Instance + { + public float EmitterSize + { + get { return xmlRead_MinDistance_3; } + set { xmlRead_MinDistance_3 = value; } + } + + public bool Looped = false; + + public float MaxDistance + { + get { return xmlRead_MaxDistance_3; } + set { xmlRead_MaxDistance_3 = value; } + } + + [Obsolete] + public float MinDistance + { + get { return EmitterSize; } + set { EmitterSize = value; } + } + + [Obsolete] + public float Pitch + { + get { return PlaybackSpeed; } + set { PlaybackSpeed = value; } + } + + public bool PlayOnRemove = false; + public float PlaybackSpeed = 1; + public bool Playing = false; + public RollOffMode RollOffMode = RollOffMode.Inverse; + public SoundGroup SoundGroup = null; + public Content SoundId = ""; + public double TimePosition = 0; + public float Volume = 0.5f; + public float xmlRead_MaxDistance_3 = 10000; + public float xmlRead_MinDistance_3 = 10; + } + + public abstract class SoundEffect : Instance + { + public bool Enabled = true; + public int Priority = 0; + } + + public class ChorusSoundEffect : SoundEffect + { + public float Depth = 0.15f; + public float Mix = 0.5f; + public float Rate = 0.5f; + } + + public class CompressorSoundEffect : SoundEffect + { + public float Attack = 0.1f; + public float GainMakeup = 0; + public float Ratio = 40; + public float Release = 0.1f; + public Instance SideChain = null; + public float Threshold = -40; + } + + public class DistortionSoundEffect : SoundEffect + { + public float Level = 0.75f; + } + + public class EchoSoundEffect : SoundEffect + { + public float Delay = 1; + public float DryLevel = 0; + public float Feedback = 0.5f; + public float WetLevel = 0; + } + + public class EqualizerSoundEffect : SoundEffect + { + public float HighGain = 0; + public float LowGain = -20; + public float MidGain = -10; + } + + public class FlangeSoundEffect : SoundEffect + { + public float Depth = 0.45f; + public float Mix = 0.85f; + public float Rate = 5; + } + + public class PitchShiftSoundEffect : SoundEffect + { + public float Octave = 1.25f; + } + + public class ReverbSoundEffect : SoundEffect + { + public float DecayTime = 1.5f; + public float Density = 1; + public float Diffusion = 1; + public float DryLevel = -6; + public float WetLevel = 0; + } + + public class TremoloSoundEffect : SoundEffect + { + public float Depth = 1; + public float Duty = 0.5f; + public float Frequency = 5; + } + + public class SoundGroup : Instance + { + public float Volume = 0.5f; + } + + public class SoundService : Instance + { + public SoundService() + { + IsService = true; + } + + public ReverbType AmbientReverb = ReverbType.NoReverb; + public float DistanceFactor = 3.33f; + public float DopplerScale = 1; + public bool RespectFilteringEnabled = true; + public float RolloffScale = 1; + } + + public class Sparkles : Instance + { + public Color3 Color + { + get { return SparkleColor; } + set { SparkleColor = value; } + } + + public bool Enabled = true; + public Color3 SparkleColor = Color3.FromRGB(144, 25, 255); + } + + public class StarterGear : Instance + { + } + + public class StarterPack : Instance + { + public StarterPack() + { + IsService = true; + } + } + + public class StarterPlayer : Instance + { + public StarterPlayer() + { + IsService = true; + } + + public bool AllowCustomAnimations = true; + public bool AutoJumpEnabled = true; + public float CameraMaxZoomDistance = 400; + public float CameraMinZoomDistance = 0.5f; + public CameraMode CameraMode = CameraMode.Classic; + public float CharacterJumpHeight = 7.2f; + public float CharacterJumpPower = 50; + public float CharacterMaxSlopeAngle = 89; + public bool CharacterUseJumpPower = true; + public float CharacterWalkSpeed = 16; + public DevCameraOcclusionMode DevCameraOcclusionMode = DevCameraOcclusionMode.Zoom; + public DevComputerCameraMovementMode DevComputerCameraMovementMode = DevComputerCameraMovementMode.UserChoice; + public DevComputerMovementMode DevComputerMovementMode = DevComputerMovementMode.UserChoice; + public DevTouchCameraMovementMode DevTouchCameraMovementMode = DevTouchCameraMovementMode.UserChoice; + public DevTouchMovementMode DevTouchMovementMode = DevTouchMovementMode.UserChoice; + public bool EnableMouseLockOption = true; + public long GameSettingsAssetIDFace = 0; + public long GameSettingsAssetIDHead = 0; + public long GameSettingsAssetIDLeftArm = 0; + public long GameSettingsAssetIDLeftLeg = 0; + public long GameSettingsAssetIDPants = 0; + public long GameSettingsAssetIDRightArm = 0; + public long GameSettingsAssetIDRightLeg = 0; + public long GameSettingsAssetIDShirt = 0; + public long GameSettingsAssetIDTeeShirt = 0; + public long GameSettingsAssetIDTorso = 0; + public GameAvatarType GameSettingsAvatar = GameAvatarType.R15; + public R15CollisionType GameSettingsR15Collision = R15CollisionType.OuterBox; + public NumberRange GameSettingsScaleRangeBodyType = new NumberRange(0, 1); + public NumberRange GameSettingsScaleRangeHead = new NumberRange(0.95f, 1); + public NumberRange GameSettingsScaleRangeHeight = new NumberRange(0.9f, 1.05f); + public NumberRange GameSettingsScaleRangeProportion = new NumberRange(0, 1); + public NumberRange GameSettingsScaleRangeWidth = new NumberRange(0.7f, 1); + public float HealthDisplayDistance = 100; + public bool LoadCharacterAppearance = true; + public float NameDisplayDistance = 100; + public bool UserEmotesEnabled = true; + } + + public class StarterPlayerScripts : Instance + { + } + + public class StarterCharacterScripts : StarterPlayerScripts + { + } + + public class Stats : Instance + { + public Stats() + { + IsService = true; + } + + } + + public class StudioData : Instance + { + public StudioData() + { + IsService = true; + } + + public long SrcPlaceId = 0; + public long SrcUniverseId = 0; + } + + public class StudioService : Instance + { + public StudioService() + { + IsService = true; + } + + } + + public class Team : Instance + { + public bool AutoAssignable = true; + + [Obsolete] + public bool AutoColorCharacters = true; + + [Obsolete] + public int Score = 0; + + public BrickColor TeamColor = BrickColor.FromNumber(1); + } + + public class Teams : Instance + { + public Teams() + { + IsService = true; + } + } + + public class TeleportService : Instance + { + public TeleportService() + { + IsService = true; + } + + [Obsolete] + public bool CustomizedTeleportUI = false; + } + + public class TerrainRegion : Instance + { + public Vector3int16 ExtentsMax = new Vector3int16(); + public Vector3int16 ExtentsMin = new Vector3int16(); + public byte[] GridV3 = new byte[0]; + public byte[] SmoothGrid = Convert.FromBase64String("AQU="); + } + + public class TestService : Instance + { + public TestService() + { + IsService = true; + } + + public bool AutoRuns = true; + public string Description = ""; + public bool ExecuteWithStudioRun = false; + public bool Is30FpsThrottleEnabled = true; + public bool IsPhysicsEnvironmentalThrottled = true; + public bool IsSleepAllowed = true; + public int NumberOfPlayers = 0; + public double SimulateSecondsLag = 0; + public double Timeout = 10; + } + + public class TextService : Instance + { + public TextService() + { + IsService = true; + } + } + + public class TimerService : Instance + { + public TimerService() + { + IsService = true; + } + } + + public class TouchInputService : Instance + { + public TouchInputService() + { + IsService = true; + } + } + + public class Trail : Instance + { + public Attachment Attachment0 = null; + public Attachment Attachment1 = null; + public ColorSequence Color = new ColorSequence(new Color3(1, 1, 1)); + public bool Enabled = true; + public bool FaceCamera = false; + public float Lifetime = 2; + public float LightEmission = 0; + public float LightInfluence = 0; + public float MaxLength = 0; + public float MinLength = 0.1f; + public Content Texture = ""; + public float TextureLength = 1; + public TextureMode TextureMode = TextureMode.Stretch; + public NumberSequence Transparency = new NumberSequence(0.5f); + public NumberSequence WidthScale = new NumberSequence(1); + } + + public abstract class TweenBase : Instance + { + } + + public class Tween : TweenBase + { + } + + public class TweenService : Instance + { + public TweenService() + { + IsService = true; + } + } + + public abstract class UIBase : Instance + { + } + + public abstract class UIComponent : UIBase + { + } + + public abstract class UIConstraint : UIComponent + { + } + + public class UIAspectRatioConstraint : UIConstraint + { + public float AspectRatio = 1; + public AspectType AspectType = AspectType.FitWithinMaxSize; + public DominantAxis DominantAxis = DominantAxis.Width; + } + + public class UISizeConstraint : UIConstraint + { + public Vector2 MaxSize = new Vector2(float.MaxValue, float.MaxValue); + public Vector2 MinSize = new Vector2(); + } + + public class UITextSizeConstraint : UIConstraint + { + public int MaxTextSize = 100; + public int MinTextSize = 1; + } + + public abstract class UILayout : UIComponent + { + } + + public abstract class UIGridStyleLayout : UILayout + { + public FillDirection FillDirection = FillDirection.Horizontal; + public HorizontalAlignment HorizontalAlignment = HorizontalAlignment.Left; + public SortOrder SortOrder = SortOrder.Name; + public VerticalAlignment VerticalAlignment = VerticalAlignment.Top; + } + + public class UIGridLayout : UIGridStyleLayout + { + public UDim2 CellPadding = new UDim2(0, 5, 0, 5); + public UDim2 CellSize = new UDim2(0, 100, 0, 100); + public int FillDirectionMaxCells = 0; + public StartCorner StartCorner = StartCorner.TopLeft; + } + + public class UIInlineLayout : UIGridStyleLayout + { + public InlineAlignment InlineAlignment = InlineAlignment.Center; + public UDim2 InlinePadding = new UDim2(); + } + + public class UIListLayout : UIGridStyleLayout + { + public UDim Padding = new UDim(); + } + + public class UIPageLayout : UIGridStyleLayout + { + public bool Animated = true; + public bool Circular = false; + public EasingDirection EasingDirection = EasingDirection.Out; + public EasingStyle EasingStyle = EasingStyle.Back; + public bool GamepadInputEnabled = true; + public UDim Padding = new UDim(); + public bool ScrollWheelInputEnabled = true; + public bool TouchInputEnabled = true; + public float TweenTime = 1; + } + + public class UITableLayout : UIGridStyleLayout + { + public bool FillEmptySpaceColumns = false; + public bool FillEmptySpaceRows = false; + public TableMajorAxis MajorAxis = TableMajorAxis.RowMajor; + public UDim2 Padding = new UDim2(); + } + + public class UIPadding : UIComponent + { + public UDim PaddingBottom = new UDim(); + public UDim PaddingLeft = new UDim(); + public UDim PaddingRight = new UDim(); + public UDim PaddingTop = new UDim(); + } + + public class UIScale : UIComponent + { + public float Scale = 1; + } + + public class UserInputService : Instance + { + public UserInputService() + { + IsService = true; + } + + public bool LegacyInputEventsEnabled = true; + public MouseBehavior MouseBehavior = MouseBehavior.Default; + public float MouseDeltaSensitivity = 1; // [Load-only] + public bool MouseIconEnabled = true; + } + + public class VRService : Instance + { + public VRService() + { + IsService = true; + } + + } + + public abstract class ValueBase : Instance + { + } + + public class BinaryStringValue : ValueBase + { + public byte[] Value = new byte[0]; + } + + public class BoolValue : ValueBase + { + public bool Value = false; + } + + public class BrickColorValue : ValueBase + { + public BrickColor Value = BrickColor.FromNumber(194); + } + + public class CFrameValue : ValueBase + { + public CFrame Value = new CFrame(); + } + + public class Color3Value : ValueBase + { + public Color3 Value = new Color3(); + } + + public class DoubleConstrainedValue : ValueBase + { + public double ConstrainedValue + { + get { return Value; } + set { Value = value; } + } + + public double MaxValue = 1; + public double MinValue = 0; + public double Value = 0; // [Load-only] + } + + public class IntConstrainedValue : ValueBase + { + public long ConstrainedValue + { + get { return Value; } + set { Value = value; } + } + + public long MaxValue = 10; + public long MinValue = 0; + public long Value = 0; // [Load-only] + } + + public class IntValue : ValueBase + { + public long Value = 0; + } + + public class NumberValue : ValueBase + { + public double Value = 0; + } + + public class ObjectValue : ValueBase + { + public Instance Value = null; + } + + public class RayValue : ValueBase + { + public Ray Value = new Ray(); + } + + public class StringValue : ValueBase + { + public string Value = ""; + } + + public class Vector3Value : ValueBase + { + public Vector3 Value = new Vector3(); + } + + public class VersionControlService : Instance + { + public VersionControlService() + { + IsService = true; + } + } + + public class VirtualInputManager : Instance + { + public VirtualInputManager() + { + IsService = true; + } + + public string AdditionalLuaState = ""; // [Load-only] + } + + public class VirtualUser : Instance + { + public VirtualUser() + { + IsService = true; + } + } + + public class Visit : Instance + { + public Visit() + { + IsService = true; + } + } + + public class WeldConstraint : Instance + { + public CFrame CFrame0 = new CFrame(); + public CFrame CFrame1 = new CFrame(); + public bool Enabled = true; + + public BasePart Part0 + { + get { return Part0Internal; } + set { Part0Internal = value; } + } + + public BasePart Part0Internal = null; + + public BasePart Part1 + { + get { return Part1Internal; } + set { Part1Internal = value; } + } + + public BasePart Part1Internal = null; + } +} diff --git a/Tree/Enums.cs b/Generated/Enums.cs similarity index 95% rename from Tree/Enums.cs rename to Generated/Enums.cs index 78a40b8..ff35304 100644 --- a/Tree/Enums.cs +++ b/Generated/Enums.cs @@ -1,5 +1,5 @@ -// This is an auto-generated list of all available enums on Roblox! -// Updated as of 0.370.0.274702 +// Auto-generated list of Roblox enums. +// Updated as of 0.392.0.316618 namespace RobloxFiles.Enums { @@ -26,6 +26,12 @@ namespace RobloxFiles.Enums Servo } + public enum AlignType + { + Parallel, + Perpendicular + } + public enum AnimationPriority { Idle, @@ -55,9 +61,15 @@ namespace RobloxFiles.Enums ScaleWithParentSize } + public enum AssetFetchStatus + { + Success, + Failure + } + public enum AssetType { - Image, + Image = 1, TeeShirt, Audio, Mesh, @@ -99,7 +111,8 @@ namespace RobloxFiles.Enums WalkAnimation, PoseAnimation, EarAccessory, - EyeAccessory + EyeAccessory, + EmoteAnimation = 61 } public enum AutoJointsMode @@ -113,7 +126,8 @@ namespace RobloxFiles.Enums { Friend, Chat, - Emote + Emote, + InspectMenu } public enum AvatarJointPositionType @@ -169,6 +183,14 @@ namespace RobloxFiles.Enums Unknown = 17 } + public enum BreakReason + { + Other, + Error, + SpecialBreakpoint, + UserBreakpoint + } + public enum Button { Dismount = 8, @@ -250,7 +272,7 @@ namespace RobloxFiles.Enums public enum CenterDialogType { - UnsolicitedDialog, + UnsolicitedDialog = 1, PlayerInitiatedDialog, ModalDialog, QuitDialog @@ -258,7 +280,7 @@ namespace RobloxFiles.Enums public enum ChatCallbackType { - OnCreatingChatWindow, + OnCreatingChatWindow = 1, OnClientSendingMessage, OnClientFormattingMessage, OnServerReceivingMessage = 17 @@ -342,6 +364,8 @@ namespace RobloxFiles.Enums DisconnectIdle, DisconnectRaknetErrors, DisconnectWrongVersion, + DisconnectBySecurityPolicy, + DisconnectBlockedIP, PlacelaunchErrors = 512, PlacelaunchDisabled = 515, PlacelaunchError, @@ -376,10 +400,9 @@ namespace RobloxFiles.Enums public enum ContextActionPriority { Low = 1000, + Default = 2000, Medium = 2000, - High = 3000, - - Default = Medium + High = 3000 } public enum ContextActionResult @@ -400,7 +423,8 @@ namespace RobloxFiles.Enums Health, Backpack, Chat, - All + All, + EmotesMenu } public enum CreatorType @@ -423,12 +447,6 @@ namespace RobloxFiles.Enums Follow } - public enum DEPRECATED_DebuggerDataModelPreference - { - Server, - Client - } - public enum DataStoreRequestType { GetAsync, @@ -506,6 +524,14 @@ namespace RobloxFiles.Enums Navigation } + public enum DeviceType + { + Unknown, + Desktop, + Tablet, + Phone + } + public enum DialogBehaviorType { SinglePlayer, @@ -735,7 +761,7 @@ namespace RobloxFiles.Enums public enum GraphicsMode { - Automatic, + Automatic = 1, Direct3D11, Direct3D9, OpenGL, @@ -811,6 +837,12 @@ namespace RobloxFiles.Enums Localization = 24 } + public enum HumanoidCollisionType + { + OuterBox, + InnerBox + } + public enum HumanoidDisplayDistanceType { Viewer, @@ -875,6 +907,13 @@ namespace RobloxFiles.Enums Float } + public enum InlineAlignment + { + Bottom, + Center, + Top + } + public enum InputType { NoInput, @@ -891,7 +930,7 @@ namespace RobloxFiles.Enums public enum JointType { - Weld, + Weld = 1, Snap = 3, Rotate = 7, RotateP, @@ -1169,6 +1208,13 @@ namespace RobloxFiles.Enums Default } + public enum LanguagePreference + { + SystemDefault, + English, + SimplifiedChinese + } + public enum LeftRight { Left, @@ -1244,6 +1290,7 @@ namespace RobloxFiles.Enums Ice = 1536, Glacier = 1552, Glass = 1568, + ForceField = 1584, Air = 1792, Water = 2048 } @@ -1253,7 +1300,8 @@ namespace RobloxFiles.Enums None, BuildersClub, TurboBuildersClub, - OutrageousBuildersClub + OutrageousBuildersClub, + Premium } public enum MeshType @@ -1619,7 +1667,7 @@ namespace RobloxFiles.Enums public enum ScrollingDirection { - X, + X = 1, Y, XY = 4 } @@ -1688,6 +1736,13 @@ namespace RobloxFiles.Enums Confusion } + public enum StreamingPauseMode + { + Default, + Disabled, + ClientPhysicsPause + } + public enum StudioStyleGuideColor { MainBackground, @@ -1773,7 +1828,15 @@ namespace RobloxFiles.Enums CheckedFieldIndicator, HeaderSection, Midlight, - StatusBar + StatusBar, + DialogButton, + DialogButtonText, + DialogButtonBorder, + DialogMainButton, + DialogMainButtonText, + Merge3HighlightOriginal, + Merge3HighlightMine, + Merge3HighlightTheirs } public enum StudioStyleGuideModifier @@ -1800,6 +1863,12 @@ namespace RobloxFiles.Enums Motor } + public enum SurfaceGuiSizingMode + { + FixedSize, + PixelsPerStud + } + public enum SurfaceType { Smooth, @@ -1832,7 +1901,9 @@ namespace RobloxFiles.Enums public enum Technology { Legacy, - Voxel + Voxel, + Compatibility, + ShadowMap } public enum TeleportResult @@ -1865,7 +1936,7 @@ namespace RobloxFiles.Enums public enum TextFilterContext { - PublicChat, + PublicChat = 1, PrivateChat } @@ -2038,6 +2109,7 @@ namespace RobloxFiles.Enums Gamepad7, Gamepad8, TextInput, + InputMethod, None } @@ -2115,4 +2187,4 @@ namespace RobloxFiles.Enums Global, Sibling } -} \ No newline at end of file +} diff --git a/Plugins/GenerateApiDump.rbxm b/Plugins/GenerateApiDump.rbxm new file mode 100644 index 0000000..8f53365 Binary files /dev/null and b/Plugins/GenerateApiDump.rbxm differ diff --git a/Plugins/GenerateApiDump/ApiPlugin.server.lua b/Plugins/GenerateApiDump/ApiPlugin.server.lua new file mode 100644 index 0000000..cbc3efd --- /dev/null +++ b/Plugins/GenerateApiDump/ApiPlugin.server.lua @@ -0,0 +1,607 @@ +local HttpService = game:GetService("HttpService") +local ServerStorage = game:GetService("ServerStorage") +local StarterPlayer = game:GetService("StarterPlayer") +local StudioService = game:GetService("StudioService") + +local classes = {} +local outStream = "" +local stackLevel = 0 + +local singletons = +{ + Terrain = workspace:WaitForChild("Terrain"); + StarterPlayerScripts = StarterPlayer:WaitForChild("StarterPlayerScripts"); + StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts"); +} + +local isCoreScript = pcall(function () + local restricted = game:GetService("RobloxPluginGuiService") + return tostring(restricted) +end) + +local function write(formatString, ...) + local tabs = string.rep(' ', stackLevel * 4) + local fmt = formatString or "" + + local value = tabs .. fmt:format(...) + outStream = outStream .. value +end + +local function writeLine(formatString, ...) + if not formatString then + outStream = outStream .. '\n' + return + end + + write(formatString .. '\n', ...) +end + +local function openStack() + writeLine('{') + stackLevel = stackLevel + 1 +end + +local function closeStack() + stackLevel = stackLevel - 1 + writeLine('}') +end + +local function clearStream() + stackLevel = 0 + outStream = "" +end + +local function exportStream(label) + local results = outStream:gsub("\n\n\n", "\n\n") + + if plugin then + local export = Instance.new("Script") + export.Archivable = false + export.Source = results + export.Name = label + + plugin:OpenScript(export) + end + + if isCoreScript then + StudioService:CopyToClipboard(results) + elseif not plugin then + warn(label) + print(results) + end +end + +local function getTags(object) + local tags = {} + + if object.Tags ~= nil then + for _,tag in pairs(object.Tags) do + tags[tag] = true + end + end + + if object.Name == "Terrain" then + tags.NotCreatable = nil + end + + return tags +end + +local function upcastInheritance(class, root) + local superClass = classes[class.Superclass] + + if not superClass then + return + end + + if not root then + root = class + end + + if not superClass.Inherited then + superClass.Inherited = root + end + + upcastInheritance(superClass, root) +end + +local function canCreateClass(class) + local tags = getTags(class) + local canCreate = true + + if tags.NotCreatable then + canCreate = false + end + + if tags.Service then + canCreate = true + end + + if tags.Settings then + canCreate = false + end + + if singletons[class.Name] then + canCreate = true + end + + return canCreate +end + +local function collectProperties(class) + local propMap = {} + + for _,member in ipairs(class.Members) do + if member.MemberType == "Property" then + local propName = member.Name + propMap[propName] = member + end + end + + return propMap +end + +local function createProperty(propName, propType) + local category = "DataType"; + local name = propType + + if propType:find(':') then + local data = string.split(propType, ':') + category = data[1] + name = data[2] + end + + return + { + Name = propName; + + Serialization = + { + CanSave = true; + CanLoad = true; + }; + + ValueType = + { + Category = category; + Name = name; + }; + + Security = "None"; + } +end + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Formatting +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +local formatting = require(script.Parent.Formatting) + +local formatLinks = +{ + ["int"] = "Int"; + ["nil"] = "Null"; + ["long"] = "Int"; + + ["float"] = "Float"; + ["byte[]"] = "Bytes"; + ["double"] = "Double"; + + ["string"] = "String"; + ["Content"] = "String"; + ["Instance"] = "Null"; + + ["Color3uint8"] = "Color3"; + ["ProtectedString"] = "String"; +} + +local function getFormatFunction(valueType) + if not formatting[valueType] then + valueType = formatLinks[valueType] + end + + return formatting[valueType] +end + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Property Patches +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +local patches = require(script.Parent.PropertyPatches) +local patchIndex = {} + +function patchIndex:__index(key) + if not rawget(self, key) then + rawset(self, key, {}) + end + + return self[key] +end + +local function getPatches(className) + local classPatches = patches[className] + return setmetatable(classPatches, patchIndex) +end + +setmetatable(patches, patchIndex) + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Main +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +local baseUrl = "https://raw.githubusercontent.com/CloneTrooper1019/Roblox-Client-Tracker/roblox/" +local toolbar, classButton, enumButton + +if plugin then + toolbar = plugin:CreateToolbar("C# API Dump") + + classButton = toolbar:CreateButton( + "Dump Classes", + "Generates a C# dump of Roblox's Class API.", + "rbxasset://textures/Icon_Stream_Off@2x.png" + ) + + enumButton = toolbar:CreateButton( + "Dump Enums", + "Generates a C# dump of Roblox's Enum API.", + "rbxasset://textures/Icon_Stream_Off@2x.png" + ) +end + +local function getAsync(url) + local enabled + + if isCoreScript then + enabled = HttpService:GetHttpEnabled() + HttpService:SetHttpEnabled(true) + end + + local result = HttpService:GetAsync(url) + + if isCoreScript then + HttpService:SetHttpEnabled(enabled) + end + + return result +end + +local function generateClasses() + local version = getAsync(baseUrl .. "version.txt") + + local apiDump = getAsync(baseUrl .. "API-Dump.json") + apiDump = HttpService:JSONDecode(apiDump) + + local classNames = {} + classes = {} + + for _,class in ipairs(apiDump.Classes) do + local className = class.Name + local superClass = classes[class.Superclass] + + if singletons[className] then + class.Singleton = true + class.Object = singletons[className] + end + + if superClass and canCreateClass(class) then + local classTags = getTags(class) + + if classTags.Service then + pcall(function () + if not className:find("Network") then + class.Object = game:GetService(className) + end + end) + elseif not classTags.NotCreatable then + pcall(function () + class.Object = Instance.new(className) + + if ServerStorage:FindFirstChild("DumpFolder") then + class.Object.Name = className + class.Object.Parent = ServerStorage.DumpFolder + end + end) + end + + upcastInheritance(class) + end + + classes[className] = class + table.insert(classNames, className) + end + + outStream = "" + + writeLine("// Auto-generated list of creatable Roblox classes.") + writeLine("// Updated as of %s", version) + writeLine() + + writeLine("using System;") + writeLine() + + writeLine("using RobloxFiles.DataTypes;") + writeLine("using RobloxFiles.Enums;") + writeLine("using RobloxFiles.Utility;") + writeLine() + + writeLine("namespace RobloxFiles") + openStack() + + for i,className in ipairs(classNames) do + local class = classes[className] + local classTags = getTags(class) + + local registerClass = canCreateClass(class) + + if class.Inherited then + registerClass = true + end + + if class.Name == "Instance" or class.Name == "Studio" then + registerClass = false + end + + local object = class.Object + + if not object then + if class.Inherited then + object = class.Inherited.Object + elseif singletons[className] then + object = singletons[className] + else + registerClass = false + end + end + + if registerClass then + local objectType + + if classTags.NotCreatable and class.Inherited and not class.Singleton then + objectType = "abstract class" + else + objectType = "class" + end + + writeLine("public %s %s : %s", objectType, className, class.Superclass) + openStack() + + local classPatches = getPatches(className) + local redirectProps = classPatches.Redirect + + local propMap = collectProperties(class) + local propNames = {} + + for _,propName in pairs(classPatches.Remove) do + propMap[propName] = nil + end + + for propName in pairs(propMap) do + table.insert(propNames, propName) + end + + for propName, propType in pairs(classPatches.Add) do + if not propMap[propName] then + propMap[propName] = createProperty(propName, propType) + table.insert(propNames, propName) + else + propMap[propName].Serialization.CanLoad = true + end + end + + local firstLine = true + table.sort(propNames) + + + if classTags.Service then + writeLine("public %s()", className) + openStack() + + writeLine("IsService = true;") + closeStack() + + if #propNames > 0 then + writeLine() + end + end + + for i, propName in ipairs(propNames) do + local prop = propMap[propName] + + local serial = prop.Serialization + local valueType = prop.ValueType.Name + + if serial.CanLoad then + local propTags = getTags(prop) + + local redirect = redirectProps[propName] + local name = propName + local default = "" + + if propName == className then + name = name .. '_' + end + + if valueType == "int64" then + valueType = "long" + elseif valueType == "BinaryString" then + valueType = "byte[]" + end + + local first = name:sub(1, 1) + + if first == first:lower() then + local pascal = first:upper() .. name:sub(2) + if propMap[pascal] ~= nil and propTags.Deprecated then + redirect = pascal + end + end + + if redirect then + local get, set + + if typeof(redirect) == "string" then + get = redirect + set = redirect .. " = value" + else + get = redirect.Get + set = redirect.Set + end + + if not firstLine then + writeLine() + end + + if propTags.Deprecated then + writeLine("[Obsolete]") + end + + writeLine("public %s %s", valueType, name) + + openStack() + writeLine("get { return %s; }", get) + writeLine("set { %s; }", set) + closeStack() + + if (i ~= #propNames) then + writeLine() + end + else + local value = classPatches.Defaults[propName] + local gotValue = (value ~= nil) + + if not gotValue then + gotValue, value = pcall(function () + return object[propName] + end) + end + + local comment = " // Default missing!" + local category = prop.ValueType.Category + + if gotValue then + local category = prop.ValueType.Category + local formatFunc = getFormatFunction(valueType) + + if not formatFunc then + local literal = typeof(value) + formatFunc = getFormatFunction(literal) + end + + if not formatFunc then + formatFunc = tostring + end + + local result + + if typeof(formatFunc) == "string" then + result = formatFunc + else + result = formatFunc(value) + end + + if not serial.CanSave and not propTags.Deprecated then + comment = " // [Load-only]" + else + comment = "" + end + + default = " = " .. result + end + + if propTags.Deprecated then + if not firstLine then + writeLine() + end + + writeLine("[Obsolete]") + end + + if category == "Class" then + default = " = null" + comment = "" + end + + writeLine("public %s %s%s;%s", valueType, name, default, comment) + + if propTags.Deprecated and i ~= #propNames then + writeLine() + end + end + + firstLine = false + end + end + + closeStack() + + if (i ~= #classNames) then + writeLine() + end + end + end + + closeStack() + exportStream("Classes") +end + +local function generateEnums() + local version = getfenv().version():gsub("%. ", ".") + clearStream() + + writeLine("// Auto-generated list of Roblox enums.") + writeLine("// Updated as of %s", version) + writeLine() + + writeLine("namespace RobloxFiles.Enums") + openStack() + + local enums = Enum:GetEnums() + + for i, enum in ipairs(enums) do + writeLine("public enum %s", tostring(enum)) + openStack() + + local enumItems = enum:GetEnumItems() + local lastValue = -1 + + table.sort(enumItems, function (a, b) + return a.Value < b.Value + end) + + for i, enumItem in ipairs(enumItems) do + local text = "" + local comma = ',' + + local name = enumItem.Name + local value = enumItem.Value + + if (value - lastValue) ~= 1 then + text = " = " .. value; + end + + if i == #enumItems then + comma = "" + end + + lastValue = value + writeLine("%s%s%s", name, text, comma) + end + + closeStack() + + if i ~= #enums then + writeLine() + end + end + + closeStack() + exportStream("Enums") +end + +if plugin then + classButton.Click:Connect(generateClasses) + enumButton.Click:Connect(generateEnums) +else + generateClasses() + generateEnums() +end \ No newline at end of file diff --git a/Plugins/GenerateApiDump/Formatting.lua b/Plugins/GenerateApiDump/Formatting.lua new file mode 100644 index 0000000..47b442a --- /dev/null +++ b/Plugins/GenerateApiDump/Formatting.lua @@ -0,0 +1,273 @@ +local Format = {} + +function Format.Null(value) + return "null" +end + +function Format.Bytes(value) + if #value > 0 then + local fmt = "Convert.FromBase64String(%q)" + return fmt:format(value) + else + return "new byte[0]" + end +end + +function Format.String(value) + return string.format("%q", value) +end + +function Format.Int(value) + return string.format("%i", value) +end + +function Format.Number(value) + local int = math.floor(value) + + if math.abs(value - int) < 0.001 then + return Format.Int(int) + end + + local result = string.format("%.5f", value) + result = result:gsub("%.?0+$", "") + + return result +end + +function Format.Double(value) + local result = Format.Number(value) + + if result == "inf" then + return "double.MaxValue" + elseif result == "-inf" then + return "double.MinValue" + else + return result + end +end + +function Format.Float(value) + local result = Format.Number(value) + + if result == "inf" then + return "float.MaxValue" + elseif result == "-inf" then + return "float.MinValue" + else + if result:find("%.") then + result = result .. 'f' + end + + return result + end +end + +function Format.Flags(flag, enum) + local value = 0 + + for _,item in pairs(enum:GetEnumItems()) do + if flag[item.Name] then + value = value + (2 ^ item.Value) + end + end + + return value +end + +function Format.Axes(axes) + return "(Axes)" .. Format.Flags(axes, Enum.Axis) +end + +function Format.Faces(faces) + return "(Faces)" .. Format.Flags(faces, Enum.NormalId) +end + +function Format.EnumItem(item) + local enum = tostring(item.EnumType) + return enum .. '.' .. item.Name +end + +function Format.BrickColor(brickColor) + local fmt = "BrickColor.FromNumber(%i)" + return fmt:format(brickColor.Number) +end + +function Format.Color3(color) + if color == Color3.new() then + return "new Color3()" + end + + local r = Format.Float(color.r) + local g = Format.Float(color.g) + local b = Format.Float(color.b) + + local fmt = "%s(%s, %s, %s)"; + local constructor = "new Color3"; + + if string.find(r .. g .. b, 'f') then + r = Format.Int(color.r * 255) + g = Format.Int(color.g * 255) + b = Format.Int(color.b * 255) + + constructor = "Color3.FromRGB" + end + + return fmt:format(constructor, r, g, b) +end + +function Format.UDim(udim) + if udim == UDim.new() then + return "new UDim()" + end + + local scale = Format.Float(udim.Scale) + local offset = Format.Int(udim.Offset) + + local fmt = "new UDim(%s, %s)" + return fmt:format(scale, offset) +end + +function Format.UDim2(udim2) + if udim2 == UDim2.new() then + return "new UDim2()" + end + + local xScale = Format.Float(udim2.X.Scale) + local yScale = Format.Float(udim2.Y.Scale) + + local xOffset = Format.Int(udim2.X.Offset) + local yOffset = Format.Int(udim2.Y.Offset) + + local fmt = "new UDim2(%s, %s, %s, %s)" + return fmt:format(xScale, xOffset, yScale, yOffset) +end + +function Format.Vector2(v2) + if v2.Magnitude < 0.001 then + return "new Vector2()" + end + + local x = Format.Float(v2.X) + local y = Format.Float(v2.Y) + + local fmt = "new Vector2(%s, %s)" + return fmt:format(x, y) +end + +function Format.Vector3(v3) + if v3.Magnitude < 0.001 then + return "new Vector3()" + end + + local x = Format.Float(v3.X) + local y = Format.Float(v3.Y) + local z = Format.Float(v3.Z) + + local fmt = "new Vector3(%s, %s, %s)" + return fmt:format(x, y, z) +end + +function Format.CFrame(cf) + local blankCF = CFrame.new() + + if cf == blankCF then + return "new CFrame()" + end + + local rot = cf - cf.p + + if rot == blankCF then + local fmt = "new CFrame(%s, %s, %s)" + + local x = Format.Float(cf.X) + local y = Format.Float(cf.Y) + local z = Format.Float(cf.Z) + + return fmt:format(x, y, z) + else + local comp = { cf:GetComponents() } + + for i = 1,12 do + comp[i] = Format.Float(comp[i]) + end + + local fmt = "new CFrame(%s)" + local matrix = table.concat(comp, ", ") + + return fmt:format(matrix) + end +end + +function Format.NumberRange(nr) + local min = nr.Min + local max = nr.Max + + local fmt = "new NumberRange(%s)" + local value = Format.Float(min) + + if min ~= max then + value = value .. ", " .. Format.Float(max) + end + + return fmt:format(value) +end + +function Format.Ray(ray) + if ray == Ray.new() then + return "new Ray()" + end + + local fmt = "new Ray(%s, %s)" + + local origin = Format.Vector3(ray.Origin) + local direction = Format.Vector3(ray.Direction) + + return fmt:format(origin, direction) +end + +function Format.Rect(rect) + local fmt = "new Rect(%s, %s)" + + local min = Format.Vector2(rect.Min) + local max = Format.Vector2(rect.Max) + + return fmt:format(min, max) +end + +function Format.ColorSequence(cs) + local csKey = cs.Keypoints[1] + + local fmt = "new ColorSequence(%s)" + local value = Format.Color3(csKey.Value) + + return fmt:format(value) +end + +function Format.NumberSequence(ns) + local nsKey = ns.Keypoints[1] + + local fmt = "new NumberSequence(%s)" + local value = Format.Float(nsKey.Value) + + return fmt:format(value) +end + +function Format.Vector3int16(v3) + if v3 == Vector3int16.new() then + return "new Vector3int16()" + end + + local x = Format.Int(v3.X) + local y = Format.Int(v3.Y) + local z = Format.Int(v3.Z) + + local fmt = "new Vector3int16(%s, %s, %s)" + return fmt:format(x, y, z) +end + +function Format.SharedString(str) + local fmt = "SharedString.FromBase64(%q)" + return fmt:format(str) +end + +return Format \ No newline at end of file diff --git a/Plugins/GenerateApiDump/PropertyPatches.lua b/Plugins/GenerateApiDump/PropertyPatches.lua new file mode 100644 index 0000000..1f6ee6f --- /dev/null +++ b/Plugins/GenerateApiDump/PropertyPatches.lua @@ -0,0 +1,674 @@ +local function UseColor3(propName) + return + { + Get = "BrickColor.FromColor3(" .. propName .. ')'; + Set = propName .. " = value.Color"; + } +end + +local GuiTextMixIn = +{ + Redirect = + { + FontSize = + { + Get = "FontUtility.GetFontSize(TextSize)"; + Set = "TextSize = FontUtility.GetFontSize(value)"; + }; + + TextColor = UseColor3("TextColor3"); + TextWrap = "TextWrapped"; + }; +} + +return +{ + Accoutrement = + { + Remove = + { + "AttachmentUp"; + "AttachmentPos"; + "AttachmentRight"; + "AttachmentForward"; + }; + }; + + AnalyticsService = + { + Defaults = { ApiKey = "" } + }; + + Attachment = + { + Remove = + { + "Axis"; + "Orientation"; + "Position"; + "SecondaryAxis"; + "WorldAxis"; + "WorldCFrame"; + "WorldOrientation"; + "WorldPosition"; + "WorldSecondaryAxis"; + }; + }; + + BasePart = + { + Add = + { + Color3uint8 = "Color3uint8"; + size = "Vector3"; + }; + + Redirect = + { + Position = "CFrame.Position"; + BrickColor = UseColor3("Color"); + Color = "Color3uint8"; + Size = "size"; + }; + + Defaults = + { + Color3uint8 = Color3.fromRGB(163, 162, 165); + size = Vector3.new(4, 1.2, 2); + }; + + Remove = + { + "Orientation"; + "Rotation"; + } + }; + + BinaryStringValue = + { + Add = + { + Value = "BinaryString"; + }; + + Defaults = + { + Value = ""; + }; + }; + + BodyColors = + { + Redirect = + { + HeadColor = UseColor3("HeadColor3"); + LeftArmColor = UseColor3("LeftArmColor3"); + RightArmColor = UseColor3("RightArmColor3"); + LeftLegColor = UseColor3("LeftLegColor3"); + RightLegColor = UseColor3("RightLegColor3"); + TorsoColor = UseColor3("TorsoColor3"); + } + }; + + BodyAngularVelocity = + { + Redirect = { angularvelocity = "AngularVelocity" }; + }; + + BodyGyro = + { + Redirect = { cframe = "CFrame" }; + }; + + Camera = + { + Redirect = { CoordinateFrame = "CFrame" } + }; + + DataModelMesh = + { + Add = + { + LODX = "Enum:LevelOfDetailSetting"; + LODY = "Enum:LevelOfDetailSetting"; + }; + + Defaults = + { + LODX = Enum.LevelOfDetailSetting.High; + LODY = Enum.LevelOfDetailSetting.High; + }; + }; + + DataStoreService = + { + Defaults = + { + AutomaticRetry = true; + LegacyNamingScheme = false; + } + }; + + DebuggerWatch = + { + Defaults = { Expression = "" }; + }; + + DoubleConstrainedValue = + { + Redirect = { ConstrainedValue = "Value" } + }; + + Fire = + { + Add = + { + heat_xml = "float"; + size_xml = "float"; + }; + + Defaults = + { + heat_xml = 9; + size_xml = 5; + }; + + Redirect = + { + Heat = "heat_xml"; + Size = "size_xml"; + }; + }; + + FormFactorPart = + { + Add = + { + formFactorRaw = "Enum:FormFactor"; + }; + + Defaults = + { + formFactorRaw = Enum.FormFactor.Brick; + }; + + Redirect = + { + FormFactor = "formFactorRaw"; + }; + }; + + GuiBase2d = + { + Redirect = { Localize = "AutoLocalize" } + }; + + GuiBase3d = + { + Redirect = { Color = UseColor3("Color3") } + }; + + GuiObject = + { + Redirect = + { + BackgroundColor = UseColor3("BackgroundColor3"); + BorderColor = UseColor3("BorderColor3"); + Transparency = "BackgroundTransparency"; + } + }; + + HttpService = + { + Defaults = { HttpEnabled = false } + }; + + Humanoid = + { + Add = + { + Health_XML = "float"; + InternalHeadScale = "float"; + InternalBodyScale = "Vector3"; + }; + + Defaults = + { + Health_XML = 100; + InternalHeadScale = 1; + InternalBodyScale = Vector3.new(1, 1, 1); + }; + + Redirect = + { + Health = "Health_XML"; + }; + + Remove = + { + "Jump"; + "Torso"; + "LeftLeg"; + "RightLeg"; + }; + }; + + HumanoidDescription = + { + Add = + { + EmotesDataInternal = "string"; + EquippedEmotesDataInternal = "string"; + }; + + Defaults = + { + EmotesDataInternal = ""; + EquippedEmotesDataInternal = ""; + }; + }; + + InsertService = + { + Add = { AllowClientInsertModels = "bool" }; + Defaults = { AllowClientInsertModels = false }; + }; + + IntConstrainedValue = + { + Redirect = { ConstrainedValue = "Value" } + }; + + JointInstance = + { + Add = { IsAutoJoint = "bool" }; + Defaults = { IsAutoJoint = true }; + }; + + Lighting = + { + Add = + { + Technology = "Enum:Technology"; + }; + + Defaults = + { + LegacyOutlines = false; + Technology = Enum.Technology.Compatibility; + }; + + Redirect = + { + Outlines = "LegacyOutlines"; + }; + + Remove = + { + "ClockTime"; + }; + }; + + LocalizationService = + { + Remove = + { + "ForcePlayModeGameLocaleId"; + "ForcePlayModeRobloxLocaleId"; + "RobloxForcePlayModeGameLocaleId"; + "RobloxForcePlayModeRobloxLocaleId"; + } + }; + + LocalizationTable = + { + Add = { Contents = "string" }; + Defaults = { Contents = "[]" }; + + Redirect = + { + DevelopmentLanguage = "SourceLocaleId"; + } + }; + + ManualSurfaceJointInstance = + { + Add = + { + Surface0 = "int"; + Surface1 = "int"; + }; + + Defaults = + { + Surface0 = -1; + Surface1 = -1; + } + }; + + MeshPart = + { + Redirect = { MeshID = "MeshId" } + }; + + Model = + { + Add = { ModelInPrimary = "CFrame" }; + Defaults = { ModelInPrimary = CFrame.new() }; + }; + + NotificationService = + { + Remove = {"SelectedTheme"} + }; + + Part = + { + Add = { shape = "Enum:PartType" }; + Redirect = { Shape = "shape" }; + }; + + ParticleEmitter = + { + Redirect = + { + VelocitySpread = + { + Get = "SpreadAngle.X"; + Set = "SpreadAngle = new Vector2(value, value)"; + } + } + }; + + PartOperation = + { + Add = + { + AssetId = "Content"; + ChildData = "BinaryString"; + MeshData = "BinaryString"; + }; + + Defaults = + { + AssetId = ""; + ChildData = ""; + MeshData = ""; + }; + }; + + PartOperationAsset = + { + Add = + { + ChildData = "BinaryString"; + MeshData = "BinaryString"; + }; + + Defaults = + { + ChildData = ""; + MeshData = ""; + }; + }; + + Players = + { + Defaults = + { + MaxPlayersInternal = 16; + PreferredPlayersInternal = 0; + } + }; + + RenderingTest = + { + Remove = + { + "Position"; + "Orientation"; + }; + }; + + ScriptContext = + { + Remove = { "ScriptsDisabled" } + }; + + SelectionBox = + { + Redirect = { SurfaceColor = UseColor3("SurfaceColor3") } + }; + + SelectionSphere = + { + Redirect = { SurfaceColor = UseColor3("SurfaceColor3") } + }; + + ServerScriptService = + { + Defaults = { LoadStringEnabled = false } + }; + + Smoke = + { + Add = + { + size_xml = "float"; + opacity_xml = "float"; + riseVelocity_xml = "float"; + }; + + Defaults = + { + size_xml = 1; + opacity_xml = 0.5; + riseVelocity_xml = 1; + }; + + Redirect = + { + Size = "size_xml"; + Opacity = "opacity_xml"; + RiseVelocity = "riseVelocity_xml"; + }; + }; + + Sound = + { + Add = + { + MaxDistance = "float"; -- ?! + + xmlRead_MaxDistance_3 = "float"; + xmlRead_MinDistance_3 = "float"; + }; + + Defaults = + { + xmlRead_MinDistance_3 = 10; + xmlRead_MaxDistance_3 = 10000; + }; + + Redirect = + { + EmitterSize = "xmlRead_MinDistance_3"; + MaxDistance = "xmlRead_MaxDistance_3"; + + MinDistance = "EmitterSize"; + Pitch = "PlaybackSpeed"; + }; + }; + + Sparkles = + { + Redirect = { Color = "SparkleColor" }; + }; + + StudioData = + { + Defaults = + { + SrcPlaceId = 0; + SrcUniverseId = 0; + }; + }; + + TextBox = GuiTextMixIn; + TextLabel = GuiTextMixIn; + TextButton = GuiTextMixIn; + + Terrain = + { + Add = + { + ClusterGrid = "string"; + ClusterGridV2 = "string"; + ClusterGridV3 = "BinaryString"; + + SmoothGrid = "BinaryString"; + PhysicsGrid = "BinaryString"; + }; + + Defaults = + { + ClusterGrid = ""; + ClusterGridV2 = ""; + ClusterGridV3 = ""; + + SmoothGrid = "AQU="; + PhysicsGrid = "AgMAAAAAAAAAAAAAAAA="; + MaterialColors = "AAAAAAAAan8/P39rf2Y/ilY+j35fi21PZmxvZbDqw8faiVpHOi4kHh4lZlw76JxKc3trhHtagcLgc4RKxr21zq2UlJSM"; + }; + }; + + TerrainRegion = + { + Add = + { + ExtentsMax = "Vector3int16"; + ExtentsMin = "Vector3int16"; + + GridV3 = "BinaryString"; + SmoothGrid = "BinaryString"; + }; + + Defaults = + { + ExtentsMax = Vector3int16.new(); + ExtentsMin = Vector3int16.new(); + + GridV3 = ""; + SmoothGrid = "AQU="; + }; + }; + + Tool = + { + Remove = + { + "GripForward"; + "GripPos"; + "GripRight"; + "GripUp"; + }; + }; + + TriangleMeshPart = + { + Add = + { + InitialSize = "Vector3"; + LODData = "BinaryString"; + PhysicsData = "BinaryString"; + PhysicalConfigData = "SharedString"; + }; + + Defaults = + { + LODData = ""; + PhysicsData = ""; + InitialSize = Vector3.new(1, 1, 1); + PhysicalConfigData = "1B2M2Y8AsgTpgAmY7PhCfg=="; + }; + }; + + TrussPart = + { + Add = { style = "Enum:Style" }; + Redirect = { Style = "style" }; + }; + + ViewportFrame = + { + Add = + { + CameraCFrame = "CFrame"; + CameraFieldOfView = "float"; + }; + + Defaults = + { + CameraCFrame = CFrame.new(); + CameraFieldOfView = 70; + }; + + Remove = {"CurrentCamera"}; + }; + + WeldConstraint = + { + Add = + { + Part0Internal = "Class:BasePart"; + Part1Internal = "Class:BasePart"; + + CFrame0 = "CFrame"; + CFrame1 = "CFrame"; + }; + + Defaults = + { + CFrame0 = CFrame.new(); + CFrame1 = CFrame.new(); + + Part0 = Instance.new("Part"); + Part1 = Instance.new("Part"); + }; + + Redirect = + { + Part0 = "Part0Internal"; + Part1 = "Part1Internal"; + }; + }; + + Workspace = + { + Add = + { + AutoJointsMode = "Enum:AutoJointsMode"; + CollisionGroups = "string"; + ExplicitAutoJoints = "bool"; + + StreamingMinRadius = "int"; + StreamingTargetRadius = "int"; + StreamingPauseMode = "Enum:StreamingPauseMode"; + + TerrainWeldsFixed = "bool"; + }; + + Defaults = + { + AutoJointsMode = Enum.AutoJointsMode.Default; + CollisionGroups = "Default^0^1"; + ExplicitAutoJoints = true; + + StreamingMinRadius = 64; + StreamingTargetRadius = 1024; + StreamingPauseMode = Enum.StreamingPauseMode.Default; + + TerrainWeldsFixed = true; + } + } +} \ No newline at end of file diff --git a/RobloxFile.cs b/RobloxFile.cs index 2e8d6b6..051b84e 100644 --- a/RobloxFile.cs +++ b/RobloxFile.cs @@ -3,9 +3,6 @@ using System.IO; using System.Text; using System.Threading.Tasks; -using RobloxFiles.BinaryFormat; -using RobloxFiles.XmlFormat; - namespace RobloxFiles { /// diff --git a/RobloxFileFormat.csproj b/RobloxFileFormat.csproj index 05f162e..45536c0 100644 --- a/RobloxFileFormat.csproj +++ b/RobloxFileFormat.csproj @@ -71,9 +71,14 @@ + + + + - + + @@ -89,7 +94,6 @@ - @@ -100,41 +104,43 @@ + - + - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tree/Instance.cs b/Tree/Instance.cs index e282d6e..1eb3ddf 100644 --- a/Tree/Instance.cs +++ b/Tree/Instance.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Text; namespace RobloxFiles { @@ -11,16 +13,32 @@ namespace RobloxFiles public class Instance { + public Instance() + { + Name = ClassName; + } + /// The ClassName of this Instance. - public string ClassName; + public string ClassName => GetType().Name; + + /// Internal list of Properties that are under this Instance. + private Dictionary props = new Dictionary(); /// A list of properties that are defined under this Instance. - private Dictionary props = new Dictionary(); public IReadOnlyDictionary Properties => props; - protected List Children = new List(); - private Instance parent; + /// The raw list of children for this Instance. + internal List Children = new List(); + /// Raw value of the Instance's parent. + private Instance RawParent; + + /// The name of this Instance. + public string Name; + + /// Indicates whether this Instance should be serialized. + public bool Archivable = true; + /// The name of this Instance, if a Name property is defined. public override string ToString() => Name; @@ -30,14 +48,56 @@ namespace RobloxFiles /// Indicates whether the parent of this object is locked. public bool ParentLocked { get; internal set; } - /// Indicates whether this Instance is marked as a Service in the binary file format. + /// Indicates whether this Instance is a Service. public bool IsService { get; internal set; } - /// If this instance is a service, this indicates whether the service should be loaded via GetService when Roblox loads the place file. - public bool IsRootedService { get; internal set; } + /// Raw list of CollectionService tags assigned to this Instance. + private List RawTags = new List(); - /// Indicates whether this object should be serialized. - public bool Archivable = true; + /// A list of CollectionService tags assigned to this Instance. + public List Tags => RawTags; + + /// + /// Internal format of the Instance's CollectionService tags. + /// Property objects will look to this member for serializing the Tags property. + /// + internal byte[] SerializedTags + { + get + { + string fullString = string.Join("\0", Tags.ToArray()); + + byte[] buffer = fullString.ToCharArray() + .Select(ch => (byte)ch) + .ToArray(); + + return buffer; + } + set + { + int length = value.Length; + + List buffer = new List(); + Tags.Clear(); + + for (int i = 0; i < length; i++) + { + byte id = value[i]; + + if (id != 0) + buffer.Add(id); + + if (id == 0 || i == (length - 1)) + { + byte[] data = buffer.ToArray(); + buffer.Clear(); + + string tag = Encoding.UTF8.GetString(data); + Tags.Add(tag); + } + } + } + } /// Returns true if this Instance is an ancestor to the provided Instance. /// The instance whose descendance will be tested against this Instance. @@ -61,26 +121,20 @@ namespace RobloxFiles return ancestor.IsAncestorOf(this); } - public string Name + /// + /// Returns true if the provided instance inherits from the provided instance type. + /// + public bool IsA() where T : Instance { - get - { - Property propName = GetProperty("Name"); - - if (propName == null) - SetProperty("Name", "Instance"); - - return propName.Value.ToString(); - } - set - { - SetProperty("Name", value); - } + Type myType = GetType(); + Type classType = typeof(T); + return classType.IsAssignableFrom(myType); } /// /// The parent of this Instance, or null if the instance is the root of a tree. /// Setting the value of this property will throw an exception if: + /// - The parent is currently locked. /// - The value is set to itself. /// - The value is a descendant of the Instance. /// @@ -88,7 +142,7 @@ namespace RobloxFiles { get { - return parent; + return RawParent; } set { @@ -101,11 +155,10 @@ namespace RobloxFiles if (Parent == this) throw new Exception("Attempt to set parent to self."); - if (parent != null) - parent.Children.Remove(this); + RawParent?.Children.Remove(this); + value?.Children.Add(this); - value.Children.Add(this); - parent = value; + RawParent = value; } } @@ -143,10 +196,14 @@ namespace RobloxFiles /// /// The Name of the Instance to find. /// Indicates if we should search descendants as well. - public Instance FindFirstChild(string name, bool recursive = false) + public T FindFirstChild(string name, bool recursive = false) where T : Instance { - Instance result = null; - var query = Children.Where((child) => name == child.Name); + T result = null; + + var query = Children + .Where(child => child is T) + .Where(child => name == child.Name) + .Cast(); if (query.Count() > 0) { @@ -156,7 +213,7 @@ namespace RobloxFiles { foreach (Instance child in Children) { - Instance found = child.FindFirstChild(name, true); + T found = child.FindFirstChild(name, true); if (found != null) { @@ -169,6 +226,37 @@ namespace RobloxFiles return result; } + /// + /// Returns the first child of this Instance whose Name is the provided string name. + /// If the instance is not found, this returns null. + /// + /// The Name of the Instance to find. + /// Indicates if we should search descendants as well. + public Instance FindFirstChild(string name, bool recursive = false) + { + return FindFirstChild(name, recursive); + } + + /// + /// Returns the first ancestor of this Instance whose Name is the provided string name. + /// If the instance is not found, this returns null. + /// + /// The Name of the Instance to find. + public T FindFirstAncestor(string name) where T : Instance + { + Instance ancestor = Parent; + + while (ancestor != null) + { + if (ancestor is T && ancestor.Name == name) + return (T)ancestor; + + ancestor = ancestor.Parent; + } + + return null; + } + /// /// Returns the first ancestor of this Instance whose Name is the provided string name. /// If the instance is not found, this returns null. @@ -176,17 +264,7 @@ namespace RobloxFiles /// The Name of the Instance to find. public Instance FindFirstAncestor(string name) { - Instance ancestor = Parent; - - while (ancestor != null) - { - if (ancestor.Name == name) - break; - - ancestor = ancestor.Parent; - } - - return ancestor; + return FindFirstAncestor(name); } /// @@ -194,18 +272,45 @@ namespace RobloxFiles /// If the instance is not found, this returns null. /// /// The Name of the Instance to find. - public Instance FindFirstAncestorOfClass(string className) + public T FindFirstAncestorOfClass() where T : Instance { + Type classType = typeof(T); + string className = classType.Name; + Instance ancestor = Parent; while (ancestor != null) { - if (ancestor.ClassName == className) - break; - + if (ancestor is T) + return (T)ancestor; + ancestor = ancestor.Parent; } + return null; + } + + /// + /// Returns the first ancestor of this Instance which derives from the provided type T. + /// If the instance is not found, this returns null. + /// + /// The Name of the Instance to find. + public T FindFirstAncestorWhichIsA() where T : Instance + { + T ancestor = null; + Instance check = Parent; + + while (check != null) + { + if (check.IsA()) + { + ancestor = (T)check; + break; + } + + check = check.Parent; + } + return ancestor; } @@ -214,13 +319,65 @@ namespace RobloxFiles /// If the instance is not found, this returns null. /// /// The ClassName of the Instance to find. - public Instance FindFirstChildOfClass(string className, bool recursive = false) + public T FindFirstChildOfClass(bool recursive = false) where T : Instance { - Instance result = null; - var query = Children.Where((child) => className == child.ClassName); + var query = Children + .Where(child => child is T) + .Cast(); + + T result = null; + + if (query.Count() > 0) + { + result = query.First(); + } + else if (recursive) + { + foreach (Instance child in Children) + { + T found = child.FindFirstChildOfClass(true); + + if (found != null) + { + result = found; + break; + } + } + } + + return result; + } + + /// + /// Returns the first child of this Instance which derives from the provided type T. + /// If the instance is not found, this returns null. + /// + /// Whether this should search descendants as well. + public T FindFirstChildWhichIsA(bool recursive = false) where T : Instance + { + var query = Children + .Where(child => child.IsA()) + .Cast(); + + T result = null; if (query.Count() > 0) + { result = query.First(); + } + else if (recursive) + { + foreach (Instance child in Children) + { + T found = child.FindFirstChildWhichIsA(true); + + if (found != null) + { + result = found; + break; + } + } + } return result; } @@ -243,123 +400,18 @@ namespace RobloxFiles } /// - /// Returns a Property object if a property with the specified name is defined in this Instance. + /// Returns a Property object whose name is the provided string name. /// public Property GetProperty(string name) { Property result = null; - if (Properties.ContainsKey(name)) - result = Properties[name]; + if (props.ContainsKey(name)) + result = props[name]; return result; } - - /// - /// Finds or creates a property with the specified name, and sets its value to the provided object. - /// Returns the property object that had its value set, if the value is not null. - /// - public Property SetProperty(string name, object value, PropertyType? preferType = null) - { - Property prop = GetProperty(name) ?? new Property() - { - Type = preferType ?? PropertyType.Unknown, - Name = name - }; - - if (preferType == null) - { - object oldValue = prop.Value; - - Type oldType = oldValue?.GetType(); - Type newType = value?.GetType(); - - if (oldType != newType) - { - if (value == null) - { - RemoveProperty(name); - return prop; - } - - string typeName = newType.Name; - - if (value is Instance) - typeName = "Ref"; - else if (value is int) - typeName = "Int"; - else if (value is long) - typeName = "Int64"; - - Enum.TryParse(typeName, out prop.Type); - } - } - - prop.Value = value; - - if (prop.Instance == null) - AddProperty(ref prop); - - return prop; - } - - /// - /// Looks for a property with the specified property name, and returns its value as an object. - /// The resulting value may be null if the property is not serialized. - /// You can use the templated ReadProperty overload to fetch it as a specific type with a default value provided. - /// - /// The name of the property to be fetched from this Instance. - /// An object reference to the value of the specified property, if it exists. - public object ReadProperty(string propertyName) - { - Property property = GetProperty(propertyName); - return property?.Value; - } - - /// - /// Looks for a property with the specified property name, and returns it as the specified type. - /// If it cannot be converted, the provided nullFallback value will be returned instead. - /// - /// The value type to convert to when finding the specified property name. - /// The name of the property to be fetched from this Instance. - /// A fallback value to be returned if casting to T fails, or the property is not found. - /// - public T ReadProperty(string propertyName, T nullFallback) - { - try - { - object result = ReadProperty(propertyName); - return (T)result; - } - catch - { - return nullFallback; - } - } - - /// - /// Looks for a property with the specified property name. If found, it will try to set the value of the referenced outValue to its value. - /// Returns true if the property was found and its value was casted to the referenced outValue. - /// If it returns false, the outValue will not have its value set. - /// - /// The value type to convert to when finding the specified property name. - /// The name of the property to be fetched from this Instance. - /// The value to write to if the property can be casted to T correctly. - public bool TryReadProperty(string propertyName, ref T outValue) - { - try - { - object result = ReadProperty(propertyName); - outValue = (T)result; - - return true; - } - catch - { - return false; - } - } - + /// /// Adds a property by reference to this Instance's property list. /// @@ -368,16 +420,8 @@ namespace RobloxFiles { prop.Instance = this; - if (prop.Name == "Name") - { - Property nameProp = GetProperty("Name"); - - if (nameProp != null) - { - nameProp.Value = prop.Value; - return; - } - } + if (props.ContainsKey(prop.Name)) + props.Remove(prop.Name); props.Add(prop.Name, prop); } @@ -387,14 +431,69 @@ namespace RobloxFiles /// /// The name of the property to be removed. /// True if a property with the provided name was removed. - public bool RemoveProperty(string name) + internal bool RemoveProperty(string name) { - Property prop = GetProperty(name); - - if (prop != null) + if (props.ContainsKey(name)) + { + Property prop = Properties[name]; prop.Instance = null; + } return props.Remove(name); } + + /// + /// Ensures that all serializable properties of this Instance have + /// a registered Property object with the correct PropertyType. + /// + internal IReadOnlyDictionary RefreshProperties() + { + Type instType = GetType(); + FieldInfo[] fields = instType.GetFields(Property.BindingFlags); + + foreach (FieldInfo field in fields) + { + string fieldName = field.Name; + Type fieldType = field.FieldType; + + if (field.GetCustomAttribute() != null) + continue; + + if (Property.Types.ContainsKey(fieldType)) + { + if (fieldName.EndsWith("_")) + fieldName = instType.Name; + + if (!props.ContainsKey(fieldName)) + { + Property newProp = new Property() + { + Type = Property.Types[fieldType], + Value = field.GetValue(this), + Name = fieldName, + Instance = this + }; + + AddProperty(ref newProp); + } + else + { + Property prop = props[fieldName]; + prop.Value = field.GetValue(this); + prop.Type = Property.Types[fieldType]; + } + } + } + + Property tags = GetProperty("Tags"); + + if (tags == null) + { + tags = new Property("Tags", PropertyType.String); + AddProperty(ref tags); + } + + return Properties; + } } } \ No newline at end of file diff --git a/Tree/Property.cs b/Tree/Property.cs index 707d62b..01398c9 100644 --- a/Tree/Property.cs +++ b/Tree/Property.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Generic; +using System.Reflection; using RobloxFiles.BinaryFormat; using RobloxFiles.BinaryFormat.Chunks; +using RobloxFiles.DataTypes; +using RobloxFiles.Utility; + namespace RobloxFiles { public enum PropertyType @@ -22,8 +27,7 @@ namespace RobloxFiles Color3, Vector2, Vector3, - Vector2int16, - CFrame, + CFrame = 16, Quaternion, Enum, Ref, @@ -48,11 +52,57 @@ namespace RobloxFiles public string XmlToken = ""; public byte[] RawBuffer; - internal BinaryRobloxFileWriter CurrentWriter; internal object RawValue; + internal BinaryRobloxFileWriter CurrentWriter; + + internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase; + + public static IReadOnlyDictionary Types = new Dictionary() + { + { typeof(Axes), PropertyType.Axes }, + { typeof(Faces), PropertyType.Faces }, + + { typeof(int), PropertyType.Int }, + { typeof(bool), PropertyType.Bool }, + { typeof(long), PropertyType.Int64 }, + { typeof(float), PropertyType.Float }, + { typeof(double), PropertyType.Double }, + { typeof(string), PropertyType.String }, + + { typeof(Ray), PropertyType.Ray }, + { typeof(Rect), PropertyType.Rect }, + { typeof(UDim), PropertyType.UDim }, + { typeof(UDim2), PropertyType.UDim2 }, + { typeof(CFrame), PropertyType.CFrame }, + { typeof(Color3), PropertyType.Color3 }, + { typeof(Vector2), PropertyType.Vector2 }, + { typeof(Vector3), PropertyType.Vector3 }, + + { typeof(BrickColor), PropertyType.BrickColor }, + { typeof(Quaternion), PropertyType.Quaternion }, + { typeof(NumberRange), PropertyType.NumberRange }, + { typeof(SharedString), PropertyType.SharedString }, + { typeof(Vector3int16), PropertyType.Vector3int16 }, + { typeof(ColorSequence), PropertyType.ColorSequence }, + { typeof(NumberSequence), PropertyType.NumberSequence }, + + { typeof(PhysicalProperties), PropertyType.PhysicalProperties }, + }; private void ImproviseRawBuffer() { + if (RawValue is byte[]) + { + RawBuffer = RawValue as byte[]; + return; + } + else if (RawValue is SharedString) + { + var sharedString = CastValue(); + RawBuffer = Convert.FromBase64String(sharedString.MD5_Key); + return; + } + switch (Type) { case PropertyType.Int: @@ -70,21 +120,85 @@ namespace RobloxFiles case PropertyType.Double: RawBuffer = BitConverter.GetBytes((double)Value); break; - case PropertyType.SharedString: - RawBuffer = Convert.FromBase64String((string)Value); - break; // } } + private string ImplicitName + { + get + { + if (Instance != null) + { + Type instType = Instance.GetType(); + string typeName = instType.Name; + + if (typeName == Name) + { + var implicitName = Name + '_'; + return implicitName; + } + } + + return Name; + } + } + public object Value { get { + if (Instance != null) + { + if (Name == "Tags") + { + byte[] data = Instance.SerializedTags; + RawValue = data; + } + else + { + FieldInfo field = Instance.GetType() + .GetField(ImplicitName, BindingFlags); + + if (field != null) + { + object value = field.GetValue(Instance); + RawValue = value; + } + else + { + Console.WriteLine($"RobloxFiles.Property - No defined field for {Instance.ClassName}.{Name}"); + } + } + } + return RawValue; } set { + if (Instance != null) + { + if (Name == "Tags" && value is byte[]) + { + byte[] data = value as byte[]; + Instance.SerializedTags = data; + } + else + { + FieldInfo field = Instance.GetType() + .GetField(ImplicitName, BindingFlags); + + try + { + field?.SetValue(Instance, value); + } + catch + { + Console.WriteLine($"RobloxFiles.Property - Failed to cast value {value} into property {Instance.ClassName}.{Name}"); + } + } + } + RawValue = value; RawBuffer = null; @@ -96,11 +210,9 @@ namespace RobloxFiles { get { + // Improvise what the buffer should be if this is a primitive. if (RawBuffer == null && Value != null) - { - // Improvise what the buffer should be if this is a primitive. ImproviseRawBuffer(); - } return (RawBuffer != null); } diff --git a/Utility/FontUtility.cs b/Utility/FontUtility.cs new file mode 100644 index 0000000..6fcd18e --- /dev/null +++ b/Utility/FontUtility.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using RobloxFiles.Enums; + +namespace RobloxFiles.Utility +{ + public static class FontUtility + { + public static IReadOnlyDictionary FontSizes = new Dictionary() + { + { 8, FontSize.Size8 }, + { 9, FontSize.Size9 }, + { 10, FontSize.Size10 }, + { 11, FontSize.Size11 }, + { 12, FontSize.Size12 }, + { 14, FontSize.Size14 }, + { 18, FontSize.Size18 }, + { 24, FontSize.Size24 }, + { 28, FontSize.Size28 }, + { 32, FontSize.Size32 }, + { 36, FontSize.Size36 }, + { 42, FontSize.Size42 }, + { 48, FontSize.Size48 }, + { 60, FontSize.Size60 }, + { 96, FontSize.Size96 }, + }; + + private static Dictionary IntToFontSize = new Dictionary(); + + public static FontSize GetFontSize(int fontSize) + { + if (fontSize > 60) + return FontSize.Size96; + + if (FontSizes.ContainsKey(fontSize)) + return FontSizes[fontSize]; + + FontSize closest = FontSizes + .Where(pair => pair.Key <= fontSize) + .Select(pair => pair.Value) + .Last(); + + return closest; + } + + public static FontSize GetFontSize(float size) + { + int fontSize = (int)size; + return GetFontSize(fontSize); + } + + public static int GetFontSize(FontSize fontSize) + { + int value = FontSizes + .Where(pair => pair.Value == fontSize) + .Select(pair => pair.Key) + .First(); + + return value; + } + } +} diff --git a/Utility/Formatting.cs b/Utility/Formatting.cs index d9c680b..4a7ddec 100644 --- a/Utility/Formatting.cs +++ b/Utility/Formatting.cs @@ -1,8 +1,5 @@ -using System.Globalization; -using System.Linq; - -// This global class defines extension methods to numeric types -// where I don't want system globalization to come into play. +using System; +using System.Globalization; internal static class Formatting { @@ -105,4 +102,14 @@ internal static class Formatting { return int.Parse(s, invariant); } + + public static bool FuzzyEquals(this float a, float b, float epsilon = 10e-5f) + { + return Math.Abs(a - b) < epsilon; + } + + public static bool FuzzyEquals(this double a, double b, double epsilon = 10e-5) + { + return Math.Abs(a - b) < epsilon; + } } \ No newline at end of file diff --git a/Utility/MaterialInfo.cs b/Utility/MaterialInfo.cs index f0b5882..d78a57e 100644 --- a/Utility/MaterialInfo.cs +++ b/Utility/MaterialInfo.cs @@ -25,6 +25,7 @@ namespace RobloxFiles.Utility {Material.DiamondPlate, 7.85f}, {Material.Fabric, 0.70f}, {Material.Foil, 2.70f}, + {Material.ForceField, 2.40f}, {Material.Glacier, 0.92f}, {Material.Glass, 2.40f}, {Material.Granite, 2.69f}, @@ -68,6 +69,7 @@ namespace RobloxFiles.Utility {Material.DiamondPlate, 0.25f}, {Material.Fabric, 0.05f}, {Material.Foil, 0.25f}, + {Material.ForceField, 0.20f}, {Material.Glacier, 0.15f}, {Material.Glass, 0.20f}, {Material.Granite, 0.20f}, @@ -111,6 +113,7 @@ namespace RobloxFiles.Utility {Material.DiamondPlate, 0.35f}, {Material.Fabric, 0.35f}, {Material.Foil, 0.40f}, + {Material.ForceField, 0.25f}, {Material.Glacier, 0.05f}, {Material.Glass, 0.25f}, {Material.Granite, 0.40f}, diff --git a/XmlFormat/IO/XmlFileReader.cs b/XmlFormat/IO/XmlFileReader.cs index bc5b8ed..377dc4c 100644 --- a/XmlFormat/IO/XmlFileReader.cs +++ b/XmlFormat/IO/XmlFileReader.cs @@ -1,6 +1,8 @@ using System; using System.Xml; +using RobloxFiles.DataTypes; + namespace RobloxFiles.XmlFormat { public static class XmlRobloxFileReader @@ -35,11 +37,35 @@ namespace RobloxFiles.XmlFormat string key = md5Node.InnerText; string value = sharedString.InnerText.Replace("\n", ""); - file.SharedStrings.Add(key, value); + byte[] buffer = Convert.FromBase64String(value); + SharedString record = SharedString.FromBase64(value); + + if (record.MD5_Key != key) + throw error("The provided md5 hash did not match with the md5 hash computed for the value!"); + + file.SharedStrings.Add(key); } } } + public static void ReadMetadata(XmlNode meta, XmlRobloxFile file) + { + var error = createErrorHandler("ReadMetadata"); + + if (meta.Name != "Meta") + throw error("Provided XmlNode's class should be 'Meta'!"); + + XmlNode propName = meta.Attributes.GetNamedItem("name"); + + if (propName == null) + throw error("Got a Meta node without a 'name' attribute!"); + + string key = propName.InnerText; + string value = meta.InnerText; + + file.Metadata[key] = value; + } + public static void ReadProperties(Instance instance, XmlNode propsNode) { var error = createErrorHandler("ReadProperties"); @@ -53,7 +79,12 @@ namespace RobloxFiles.XmlFormat XmlNode propName = propNode.Attributes.GetNamedItem("name"); if (propName == null) + { + if (propNode.Name == "Item") + continue; + throw error("Got a property node without a 'name' attribute!"); + } IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType); @@ -90,8 +121,12 @@ namespace RobloxFiles.XmlFormat if (classToken == null) throw error("Got an Item without a defined 'class' attribute!"); - Instance inst = new Instance() { ClassName = classToken.InnerText }; + string className = classToken.InnerText; + + Type instType = Type.GetType($"RobloxFiles.{className}") ?? typeof(Instance); + Instance inst = Activator.CreateInstance(instType) as Instance; + // The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance. XmlNode refToken = instNode.Attributes.GetNamedItem("referent"); diff --git a/XmlFormat/IO/XmlFileWriter.cs b/XmlFormat/IO/XmlFileWriter.cs index fafb4c6..e5e2ebe 100644 --- a/XmlFormat/IO/XmlFileWriter.cs +++ b/XmlFormat/IO/XmlFileWriter.cs @@ -1,9 +1,8 @@ using System; -using System.Linq; -using System.Security.Cryptography; using System.Text; using System.Xml; +using RobloxFiles.DataTypes; using RobloxFiles.XmlFormat.PropertyTokens; namespace RobloxFiles.XmlFormat @@ -40,7 +39,7 @@ namespace RobloxFiles.XmlFormat foreach (Instance child in inst.GetChildren()) RecordInstances(file, child); - if (inst.Referent.Length < 35) + if (inst.Referent == null || inst.Referent.Length < 35) inst.Referent = CreateReferent(); file.Instances.Add(inst.Referent, inst); @@ -102,19 +101,8 @@ namespace RobloxFiles.XmlFormat if (prop.Type == PropertyType.SharedString) { - string data = prop.Value.ToString(); - byte[] buffer = Convert.FromBase64String(data); - - using (MD5 md5 = MD5.Create()) - { - byte[] hash = md5.ComputeHash(buffer); - string key = Convert.ToBase64String(hash); - - if (!file.SharedStrings.ContainsKey(key)) - file.SharedStrings.Add(key, data); - - propNode.InnerText = key; - } + SharedString value = prop.CastValue(); + file.SharedStrings.Add(value.MD5_Key); } return propNode; @@ -122,6 +110,9 @@ namespace RobloxFiles.XmlFormat public static XmlNode WriteInstance(Instance instance, XmlDocument doc, XmlRobloxFile file) { + if (!instance.Archivable) + return null; + XmlElement instNode = doc.CreateElement("Item"); instNode.SetAttribute("class", instance.ClassName); instNode.SetAttribute("referent", instance.Referent); @@ -129,7 +120,7 @@ namespace RobloxFiles.XmlFormat XmlElement propsNode = doc.CreateElement("Properties"); instNode.AppendChild(propsNode); - var props = instance.Properties; + var props = instance.RefreshProperties(); foreach (string propName in props.Keys) { @@ -140,8 +131,11 @@ namespace RobloxFiles.XmlFormat foreach (Instance child in instance.GetChildren()) { - XmlNode childNode = WriteInstance(child, doc, file); - instNode.AppendChild(childNode); + if (child.Archivable) + { + XmlNode childNode = WriteInstance(child, doc, file); + instNode.AppendChild(childNode); + } } return instNode; @@ -152,18 +146,15 @@ namespace RobloxFiles.XmlFormat XmlElement sharedStrings = doc.CreateElement("SharedStrings"); var binaryWriter = XmlPropertyTokens.GetHandler(); - var bufferProp = new Property("SharedString", PropertyType.String); + var binaryBuffer = new Property("SharedString", PropertyType.String); - foreach (string md5 in file.SharedStrings.Keys) + foreach (string md5 in file.SharedStrings) { XmlElement sharedString = doc.CreateElement("SharedString"); sharedString.SetAttribute("md5", md5); - string data = file.SharedStrings[md5]; - byte[] buffer = Convert.FromBase64String(data); - - bufferProp.RawBuffer = buffer; - binaryWriter.WriteProperty(bufferProp, doc, sharedString); + binaryBuffer.RawBuffer = SharedString.FindRecord(md5); + binaryWriter.WriteProperty(binaryBuffer, doc, sharedString); sharedStrings.AppendChild(sharedString); } diff --git a/XmlFormat/PropertyTokens/Tokens/Enum.cs b/XmlFormat/PropertyTokens/Tokens/Enum.cs deleted file mode 100644 index aef6d4b..0000000 --- a/XmlFormat/PropertyTokens/Tokens/Enum.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Xml; - -namespace RobloxFiles.XmlFormat.PropertyTokens -{ - public class EnumToken : IXmlPropertyToken - { - public string Token => "token"; - - public bool ReadProperty(Property prop, XmlNode token) - { - return XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Enum, token); - } - - public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) - { - node.InnerText = prop.Value.ToString(); - } - } -} diff --git a/XmlFormat/PropertyTokens/Tokens/Axes.cs b/XmlFormat/Tokens/Axes.cs similarity index 51% rename from XmlFormat/PropertyTokens/Tokens/Axes.cs rename to XmlFormat/Tokens/Axes.cs index 844363e..c43509b 100644 --- a/XmlFormat/PropertyTokens/Tokens/Axes.cs +++ b/XmlFormat/Tokens/Axes.cs @@ -1,5 +1,4 @@ -using System; -using System.Xml; +using System.Xml; using RobloxFiles.DataTypes; namespace RobloxFiles.XmlFormat.PropertyTokens @@ -7,25 +6,20 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public class AxesToken : IXmlPropertyToken { public string Token => "Axes"; + public bool ReadProperty(Property prop, XmlNode token) { - bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Axes, token); + uint value; - if (success) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) { - uint value = (uint)prop.Value; - try - { - Axes axes = (Axes)value; - prop.Value = axes; - } - catch - { - success = false; - } + Axes axes = (Axes)value; + prop.Value = axes; + + return true; } - return success; + return false; } public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) @@ -33,7 +27,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens XmlElement axes = doc.CreateElement("axes"); node.AppendChild(axes); - int value = (int)prop.Value; + int value = prop.CastValue(); axes.InnerText = value.ToInvariantString(); } } diff --git a/XmlFormat/PropertyTokens/Tokens/BinaryString.cs b/XmlFormat/Tokens/BinaryString.cs similarity index 94% rename from XmlFormat/PropertyTokens/Tokens/BinaryString.cs rename to XmlFormat/Tokens/BinaryString.cs index ad5cd15..7aa88a2 100644 --- a/XmlFormat/PropertyTokens/Tokens/BinaryString.cs +++ b/XmlFormat/Tokens/BinaryString.cs @@ -11,9 +11,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens { // BinaryStrings are encoded in base64 string base64 = token.InnerText.Replace("\n", ""); + prop.Value = Convert.FromBase64String(base64); prop.Type = PropertyType.String; - prop.Value = base64; - + byte[] buffer = Convert.FromBase64String(base64); prop.RawBuffer = buffer; diff --git a/XmlFormat/PropertyTokens/Tokens/Boolean.cs b/XmlFormat/Tokens/Boolean.cs similarity index 100% rename from XmlFormat/PropertyTokens/Tokens/Boolean.cs rename to XmlFormat/Tokens/Boolean.cs diff --git a/XmlFormat/PropertyTokens/Tokens/BrickColor.cs b/XmlFormat/Tokens/BrickColor.cs similarity index 60% rename from XmlFormat/PropertyTokens/Tokens/BrickColor.cs rename to XmlFormat/Tokens/BrickColor.cs index 3e811cf..c509c6a 100644 --- a/XmlFormat/PropertyTokens/Tokens/BrickColor.cs +++ b/XmlFormat/Tokens/BrickColor.cs @@ -13,31 +13,23 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.BrickColor, token); + int value; - if (success) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) { - int value = (int)prop.Value; + BrickColor brickColor = BrickColor.FromNumber(value); + prop.XmlToken = "BrickColor"; + prop.Value = brickColor; - try - { - BrickColor brickColor = BrickColor.FromNumber(value); - prop.XmlToken = "BrickColor"; - prop.Value = brickColor; - } - catch - { - // Invalid BrickColor Id? - success = false; - } + return true; } - return success; + return false; } public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - BrickColor value = prop.Value as BrickColor; + BrickColor value = prop.CastValue(); XmlElement brickColor = doc.CreateElement("int"); brickColor.InnerText = value.Number.ToInvariantString(); diff --git a/XmlFormat/PropertyTokens/Tokens/CFrame.cs b/XmlFormat/Tokens/CFrame.cs similarity index 97% rename from XmlFormat/PropertyTokens/Tokens/CFrame.cs rename to XmlFormat/Tokens/CFrame.cs index 0d46dd8..416c7f4 100644 --- a/XmlFormat/PropertyTokens/Tokens/CFrame.cs +++ b/XmlFormat/Tokens/CFrame.cs @@ -46,7 +46,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - CFrame cf = prop.Value as CFrame; + CFrame cf = prop.CastValue(); float[] components = cf.GetComponents(); for (int i = 0; i < 12; i++) diff --git a/XmlFormat/PropertyTokens/Tokens/Color3.cs b/XmlFormat/Tokens/Color3.cs similarity index 69% rename from XmlFormat/PropertyTokens/Tokens/Color3.cs rename to XmlFormat/Tokens/Color3.cs index 9ada037..62462b3 100644 --- a/XmlFormat/PropertyTokens/Tokens/Color3.cs +++ b/XmlFormat/Tokens/Color3.cs @@ -51,26 +51,18 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - if (prop.Name == "Color3uint8") + Color3 color = prop.CastValue(); + float[] rgb = new float[3] { color.R, color.G, color.B }; + + for (int i = 0; i < 3; i++) { - var handler = XmlPropertyTokens.GetHandler(); - handler.WriteProperty(prop, doc, node); - } - else - { - Color3 color = prop.Value as Color3; - float[] rgb = new float[3] { color.R, color.G, color.B }; + string field = Fields[i]; + float value = rgb[i]; - for (int i = 0; i < 3; i++) - { - string field = Fields[i]; - float value = rgb[i]; + XmlElement channel = doc.CreateElement(field); + channel.InnerText = value.ToInvariantString(); - XmlElement channel = doc.CreateElement(field); - channel.InnerText = value.ToInvariantString(); - - node.AppendChild(channel); - } + node.AppendChild(channel); } } } diff --git a/XmlFormat/PropertyTokens/Tokens/Color3uint8.cs b/XmlFormat/Tokens/Color3uint8.cs similarity index 58% rename from XmlFormat/PropertyTokens/Tokens/Color3uint8.cs rename to XmlFormat/Tokens/Color3uint8.cs index c1ad0fc..3b3ce05 100644 --- a/XmlFormat/PropertyTokens/Tokens/Color3uint8.cs +++ b/XmlFormat/Tokens/Color3uint8.cs @@ -1,5 +1,4 @@ -using System; -using System.Xml; +using System.Xml; using RobloxFiles.DataTypes; namespace RobloxFiles.XmlFormat.PropertyTokens @@ -10,29 +9,30 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Color3, token); + uint value; - if (success) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) { - uint value = (uint)prop.Value; - uint r = (value >> 16) & 0xFF; uint g = (value >> 8) & 0xFF; uint b = value & 0xFF; - prop.Value = Color3.FromRGB(r, g, b); + Color3uint8 result = Color3.FromRGB(r, g, b); + prop.Value = result; + + return true; } - return success; + return false; } public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Color3 color = prop.Value as Color3; + Color3uint8 color = prop.CastValue(); - uint r = (uint)(color.R * 256); - uint g = (uint)(color.G * 256); - uint b = (uint)(color.B * 256); + uint r = color.R, + g = color.G, + b = color.B; uint rgb = (255u << 24) | (r << 16) | (g << 8) | b; node.InnerText = rgb.ToString(); diff --git a/XmlFormat/PropertyTokens/Tokens/ColorSequence.cs b/XmlFormat/Tokens/ColorSequence.cs similarity index 92% rename from XmlFormat/PropertyTokens/Tokens/ColorSequence.cs rename to XmlFormat/Tokens/ColorSequence.cs index 50bac61..424a884 100644 --- a/XmlFormat/PropertyTokens/Tokens/ColorSequence.cs +++ b/XmlFormat/Tokens/ColorSequence.cs @@ -47,7 +47,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToString() + ' '; + ColorSequence value = prop.CastValue(); + node.InnerText = value.ToString() + ' '; } } } diff --git a/XmlFormat/PropertyTokens/Tokens/Content.cs b/XmlFormat/Tokens/Content.cs similarity index 88% rename from XmlFormat/PropertyTokens/Tokens/Content.cs rename to XmlFormat/Tokens/Content.cs index fad23e3..1933bac 100644 --- a/XmlFormat/PropertyTokens/Tokens/Content.cs +++ b/XmlFormat/Tokens/Content.cs @@ -1,6 +1,8 @@ using System; using System.Xml; +using RobloxFiles.DataTypes; + namespace RobloxFiles.XmlFormat.PropertyTokens { public class ContentToken : IXmlPropertyToken @@ -9,9 +11,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - string content = token.InnerText; + string data = token.InnerText; + prop.Value = new Content(data); prop.Type = PropertyType.String; - prop.Value = content; if (token.HasChildNodes) { @@ -23,12 +25,12 @@ namespace RobloxFiles.XmlFormat.PropertyTokens try { // Roblox technically doesn't support this anymore, but load it anyway :P - byte[] buffer = Convert.FromBase64String(content); + byte[] buffer = Convert.FromBase64String(data); prop.RawBuffer = buffer; } catch { - Console.WriteLine("ContentToken: Got illegal base64 string: {0}", content); + Console.WriteLine("ContentToken: Got illegal base64 string: {0}", data); } } } @@ -38,7 +40,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - string content = prop.Value.ToString(); + string content = prop.CastValue(); string type = "null"; if (prop.HasRawBuffer) diff --git a/XmlFormat/PropertyTokens/Tokens/Double.cs b/XmlFormat/Tokens/Double.cs similarity index 80% rename from XmlFormat/PropertyTokens/Tokens/Double.cs rename to XmlFormat/Tokens/Double.cs index dc6face..2eda661 100644 --- a/XmlFormat/PropertyTokens/Tokens/Double.cs +++ b/XmlFormat/Tokens/Double.cs @@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToInvariantString(); + double value = prop.CastValue(); + node.InnerText = value.ToInvariantString(); } } } diff --git a/XmlFormat/Tokens/Enum.cs b/XmlFormat/Tokens/Enum.cs new file mode 100644 index 0000000..b9f8536 --- /dev/null +++ b/XmlFormat/Tokens/Enum.cs @@ -0,0 +1,48 @@ +using System; +using System.Reflection; +using System.Xml; + +namespace RobloxFiles.XmlFormat.PropertyTokens +{ + public class EnumToken : IXmlPropertyToken + { + public string Token => "token"; + + public bool ReadProperty(Property prop, XmlNode token) + { + uint value; + + if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) + { + Instance inst = prop.Instance; + Type instType = inst?.GetType(); + + FieldInfo info = instType.GetField(prop.Name, Property.BindingFlags); + + if (info != null) + { + Type enumType = info.FieldType; + string item = value.ToInvariantString(); + + prop.Type = PropertyType.Enum; + prop.Value = Enum.Parse(enumType, item); + + return true; + } + } + + return false; + } + + public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) + { + object rawValue = prop.Value; + Type valueType = rawValue.GetType(); + + int signed = (int)rawValue; + uint value = (uint)signed; + + node.InnerText = value.ToString(); + } + } +} diff --git a/XmlFormat/PropertyTokens/Tokens/Faces.cs b/XmlFormat/Tokens/Faces.cs similarity index 54% rename from XmlFormat/PropertyTokens/Tokens/Faces.cs rename to XmlFormat/Tokens/Faces.cs index 2ab9963..f29db4b 100644 --- a/XmlFormat/PropertyTokens/Tokens/Faces.cs +++ b/XmlFormat/Tokens/Faces.cs @@ -9,23 +9,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - bool success = XmlPropertyTokens.ReadPropertyGeneric(prop, PropertyType.Faces, token); + uint value; - if (success) + if (XmlPropertyTokens.ReadPropertyGeneric(token, out value)) { - uint value = (uint)prop.Value; - try - { - Faces faces = (Faces)value; - prop.Value = faces; - } - catch - { - success = false; - } + Faces faces = (Faces)value; + prop.Value = faces; + + return true; } - return success; + return false; } public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) @@ -33,7 +27,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens XmlElement faces = doc.CreateElement("faces"); node.AppendChild(faces); - int value = (int)prop.Value; + int value = prop.CastValue(); faces.InnerText = value.ToInvariantString(); } } diff --git a/XmlFormat/PropertyTokens/Tokens/Float.cs b/XmlFormat/Tokens/Float.cs similarity index 80% rename from XmlFormat/PropertyTokens/Tokens/Float.cs rename to XmlFormat/Tokens/Float.cs index 31caa3c..1899943 100644 --- a/XmlFormat/PropertyTokens/Tokens/Float.cs +++ b/XmlFormat/Tokens/Float.cs @@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToInvariantString(); + float value = prop.CastValue(); + node.InnerText = value.ToInvariantString(); } } } \ No newline at end of file diff --git a/XmlFormat/PropertyTokens/Tokens/Int.cs b/XmlFormat/Tokens/Int.cs similarity index 89% rename from XmlFormat/PropertyTokens/Tokens/Int.cs rename to XmlFormat/Tokens/Int.cs index 2223036..fcf0637 100644 --- a/XmlFormat/PropertyTokens/Tokens/Int.cs +++ b/XmlFormat/Tokens/Int.cs @@ -24,7 +24,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToInvariantString(); + int value = prop.CastValue(); + node.InnerText = value.ToInvariantString(); } } } diff --git a/XmlFormat/PropertyTokens/Tokens/Int64.cs b/XmlFormat/Tokens/Int64.cs similarity index 82% rename from XmlFormat/PropertyTokens/Tokens/Int64.cs rename to XmlFormat/Tokens/Int64.cs index bdbbbf3..48b6f8b 100644 --- a/XmlFormat/PropertyTokens/Tokens/Int64.cs +++ b/XmlFormat/Tokens/Int64.cs @@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToString(); + long value = prop.CastValue(); + node.InnerText = value.ToString(); } } } \ No newline at end of file diff --git a/XmlFormat/PropertyTokens/Tokens/NumberRange.cs b/XmlFormat/Tokens/NumberRange.cs similarity index 90% rename from XmlFormat/PropertyTokens/Tokens/NumberRange.cs rename to XmlFormat/Tokens/NumberRange.cs index cf5c919..8198a57 100644 --- a/XmlFormat/PropertyTokens/Tokens/NumberRange.cs +++ b/XmlFormat/Tokens/NumberRange.cs @@ -35,7 +35,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToString() + ' '; + NumberRange value = prop.CastValue(); + node.InnerText = value.ToString() + ' '; } } } diff --git a/XmlFormat/PropertyTokens/Tokens/NumberSequence.cs b/XmlFormat/Tokens/NumberSequence.cs similarity index 92% rename from XmlFormat/PropertyTokens/Tokens/NumberSequence.cs rename to XmlFormat/Tokens/NumberSequence.cs index db0183b..af7a1d5 100644 --- a/XmlFormat/PropertyTokens/Tokens/NumberSequence.cs +++ b/XmlFormat/Tokens/NumberSequence.cs @@ -44,7 +44,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - node.InnerText = prop.Value.ToString() + ' '; + NumberSequence value = prop.CastValue(); + node.InnerText = value.ToString() + ' '; } } } diff --git a/XmlFormat/PropertyTokens/Tokens/PhysicalProperties.cs b/XmlFormat/Tokens/PhysicalProperties.cs similarity index 97% rename from XmlFormat/PropertyTokens/Tokens/PhysicalProperties.cs rename to XmlFormat/Tokens/PhysicalProperties.cs index 2950156..3b6ef1e 100644 --- a/XmlFormat/PropertyTokens/Tokens/PhysicalProperties.cs +++ b/XmlFormat/Tokens/PhysicalProperties.cs @@ -61,7 +61,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens if (hasCustomPhysics) { - var customProps = prop.Value as PhysicalProperties; + var customProps = prop.CastValue(); var data = new Dictionary() { diff --git a/XmlFormat/Tokens/ProtectedString.cs b/XmlFormat/Tokens/ProtectedString.cs new file mode 100644 index 0000000..833df8b --- /dev/null +++ b/XmlFormat/Tokens/ProtectedString.cs @@ -0,0 +1,34 @@ +using System.Xml; +using RobloxFiles.DataTypes; + +namespace RobloxFiles.XmlFormat.PropertyTokens +{ + public class ProtectedStringToken : IXmlPropertyToken + { + public string Token => "ProtectedString"; + + public bool ReadProperty(Property prop, XmlNode token) + { + ProtectedString contents = token.InnerText; + prop.Type = PropertyType.String; + prop.Value = contents; + + return true; + } + + public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) + { + string value = prop.CastValue(); + + if (value.Contains("\r") || value.Contains("\n")) + { + XmlCDataSection cdata = doc.CreateCDataSection(value); + node.AppendChild(cdata); + } + else + { + node.InnerText = value; + } + } + } +} diff --git a/XmlFormat/PropertyTokens/Tokens/Ray.cs b/XmlFormat/Tokens/Ray.cs similarity index 97% rename from XmlFormat/PropertyTokens/Tokens/Ray.cs rename to XmlFormat/Tokens/Ray.cs index 7c3c98d..b861b0a 100644 --- a/XmlFormat/PropertyTokens/Tokens/Ray.cs +++ b/XmlFormat/Tokens/Ray.cs @@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Ray ray = prop.Value as Ray; + Ray ray = prop.CastValue(); XmlElement origin = doc.CreateElement("origin"); XmlElement direction = doc.CreateElement("direction"); diff --git a/XmlFormat/PropertyTokens/Tokens/Rect.cs b/XmlFormat/Tokens/Rect.cs similarity index 96% rename from XmlFormat/PropertyTokens/Tokens/Rect.cs rename to XmlFormat/Tokens/Rect.cs index 3b31f55..6b9b6f9 100644 --- a/XmlFormat/PropertyTokens/Tokens/Rect.cs +++ b/XmlFormat/Tokens/Rect.cs @@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Rect rect = prop.Value as Rect; + Rect rect = prop.CastValue(); XmlElement min = doc.CreateElement("min"); Vector2Token.WriteVector2(doc, min, rect.Min); diff --git a/XmlFormat/PropertyTokens/Tokens/Ref.cs b/XmlFormat/Tokens/Ref.cs similarity index 79% rename from XmlFormat/PropertyTokens/Tokens/Ref.cs rename to XmlFormat/Tokens/Ref.cs index 05c46dd..d15a68f 100644 --- a/XmlFormat/PropertyTokens/Tokens/Ref.cs +++ b/XmlFormat/Tokens/Ref.cs @@ -10,7 +10,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens { string refId = token.InnerText; prop.Type = PropertyType.Ref; - prop.Value = refId; + prop.XmlToken = refId; return true; } @@ -19,9 +19,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens { string result = "null"; - if (prop.Value != null && prop.Value.ToString() != "null") + if (prop.Value != null) { - Instance inst = prop.Value as Instance; + Instance inst = prop.CastValue(); result = inst.Referent; } diff --git a/XmlFormat/PropertyTokens/Tokens/SharedString.cs b/XmlFormat/Tokens/SharedString.cs similarity index 60% rename from XmlFormat/PropertyTokens/Tokens/SharedString.cs rename to XmlFormat/Tokens/SharedString.cs index 8ef7b85..942cc0a 100644 --- a/XmlFormat/PropertyTokens/Tokens/SharedString.cs +++ b/XmlFormat/Tokens/SharedString.cs @@ -1,6 +1,5 @@ -using System; -using System.Text; -using System.Xml; +using System.Xml; +using RobloxFiles.DataTypes; namespace RobloxFiles.XmlFormat.PropertyTokens { @@ -10,17 +9,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public bool ReadProperty(Property prop, XmlNode token) { - string contents = token.InnerText; + string md5 = token.InnerText; prop.Type = PropertyType.SharedString; - prop.Value = contents; + prop.Value = new SharedString(md5); return true; } public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - var BinaryStringToken = XmlPropertyTokens.GetHandler(); - BinaryStringToken.WriteProperty(prop, doc, node); + SharedString value = prop.CastValue(); + node.InnerText = value.MD5_Key; } } } diff --git a/XmlFormat/PropertyTokens/Tokens/String.cs b/XmlFormat/Tokens/String.cs similarity index 87% rename from XmlFormat/PropertyTokens/Tokens/String.cs rename to XmlFormat/Tokens/String.cs index 86bfdf3..12ea8f2 100644 --- a/XmlFormat/PropertyTokens/Tokens/String.cs +++ b/XmlFormat/Tokens/String.cs @@ -1,12 +1,10 @@ -using System; -using System.Text; -using System.Xml; +using System.Xml; namespace RobloxFiles.XmlFormat.PropertyTokens { public class StringToken : IXmlPropertyToken { - public string Token => "string; ProtectedString"; + public string Token => "string"; public bool ReadProperty(Property prop, XmlNode token) { diff --git a/XmlFormat/PropertyTokens/Tokens/UDim.cs b/XmlFormat/Tokens/UDim.cs similarity index 97% rename from XmlFormat/PropertyTokens/Tokens/UDim.cs rename to XmlFormat/Tokens/UDim.cs index 1b3d97b..2316ad4 100644 --- a/XmlFormat/PropertyTokens/Tokens/UDim.cs +++ b/XmlFormat/Tokens/UDim.cs @@ -53,7 +53,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - UDim value = prop.Value as UDim; + UDim value = prop.CastValue(); WriteUDim(doc, node, value); } } diff --git a/XmlFormat/PropertyTokens/Tokens/UDim2.cs b/XmlFormat/Tokens/UDim2.cs similarity index 94% rename from XmlFormat/PropertyTokens/Tokens/UDim2.cs rename to XmlFormat/Tokens/UDim2.cs index 3affceb..2fc10cc 100644 --- a/XmlFormat/PropertyTokens/Tokens/UDim2.cs +++ b/XmlFormat/Tokens/UDim2.cs @@ -26,7 +26,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - UDim2 value = prop.Value as UDim2; + UDim2 value = prop.CastValue(); UDim xUDim = value.X; UDimToken.WriteUDim(doc, node, xUDim, "X"); diff --git a/XmlFormat/PropertyTokens/Tokens/Vector2.cs b/XmlFormat/Tokens/Vector2.cs similarity index 96% rename from XmlFormat/PropertyTokens/Tokens/Vector2.cs rename to XmlFormat/Tokens/Vector2.cs index a5b13b7..26ed07b 100644 --- a/XmlFormat/PropertyTokens/Tokens/Vector2.cs +++ b/XmlFormat/Tokens/Vector2.cs @@ -58,7 +58,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Vector2 value = prop.Value as Vector2; + Vector2 value = prop.CastValue(); WriteVector2(doc, node, value); } } diff --git a/XmlFormat/PropertyTokens/Tokens/Vector3.cs b/XmlFormat/Tokens/Vector3.cs similarity index 97% rename from XmlFormat/PropertyTokens/Tokens/Vector3.cs rename to XmlFormat/Tokens/Vector3.cs index f45ee9f..fcd4b52 100644 --- a/XmlFormat/PropertyTokens/Tokens/Vector3.cs +++ b/XmlFormat/Tokens/Vector3.cs @@ -62,7 +62,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Vector3 value = prop.Value as Vector3; + Vector3 value = prop.CastValue(); WriteVector3(doc, node, value); } } diff --git a/XmlFormat/PropertyTokens/Tokens/Vector3int16.cs b/XmlFormat/Tokens/Vector3int16.cs similarity index 95% rename from XmlFormat/PropertyTokens/Tokens/Vector3int16.cs rename to XmlFormat/Tokens/Vector3int16.cs index 2893b06..1bf7f8a 100644 --- a/XmlFormat/PropertyTokens/Tokens/Vector3int16.cs +++ b/XmlFormat/Tokens/Vector3int16.cs @@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { - Vector3int16 value = prop.Value as Vector3int16; + Vector3int16 value = prop.CastValue(); XmlElement x = doc.CreateElement("X"); x.InnerText = value.X.ToString(); diff --git a/XmlFormat/PropertyTokens/XmlPropertyTokens.cs b/XmlFormat/XmlPropertyTokens.cs similarity index 75% rename from XmlFormat/PropertyTokens/XmlPropertyTokens.cs rename to XmlFormat/XmlPropertyTokens.cs index 1bc1f6e..d15d4e3 100644 --- a/XmlFormat/PropertyTokens/XmlPropertyTokens.cs +++ b/XmlFormat/XmlPropertyTokens.cs @@ -35,38 +35,54 @@ namespace RobloxFiles.XmlFormat Handlers = tokenHandlers; } - public static bool ReadPropertyGeneric(Property prop, PropertyType propType, XmlNode token) where T : struct + public static bool ReadPropertyGeneric(XmlNode token, out T outValue) where T : struct { try { string value = token.InnerText; Type type = typeof(T); - if (type == typeof(int)) - prop.Value = Formatting.ParseInt(value); - else if (type == typeof(float)) - prop.Value = Formatting.ParseFloat(value); - else if (type == typeof(double)) - prop.Value = Formatting.ParseDouble(value); + object result = null; - if (prop.Value == null) + if (type == typeof(int)) + result = Formatting.ParseInt(value); + else if (type == typeof(float)) + result = Formatting.ParseFloat(value); + else if (type == typeof(double)) + result = Formatting.ParseDouble(value); + + if (result == null) { Type resultType = typeof(T); - TypeConverter converter = TypeDescriptor.GetConverter(resultType); - - object result = converter.ConvertFromString(token.InnerText); - prop.Value = result; + var converter = TypeDescriptor.GetConverter(resultType); + result = converter.ConvertFromString(token.InnerText); } - prop.Type = propType; + outValue = (T)result; return true; } catch { + outValue = default(T); return false; } } + public static bool ReadPropertyGeneric(Property prop, PropertyType propType, XmlNode token) where T : struct + { + T result; + + if (ReadPropertyGeneric(token, out result)) + { + prop.Type = propType; + prop.Value = result; + + return true; + } + + return false; + } + public static IXmlPropertyToken GetHandler(string tokenName) { IXmlPropertyToken result = null; diff --git a/XmlFormat/XmlRobloxFile.cs b/XmlFormat/XmlRobloxFile.cs index 33763a7..917250f 100644 --- a/XmlFormat/XmlRobloxFile.cs +++ b/XmlFormat/XmlRobloxFile.cs @@ -6,20 +6,26 @@ using System.Linq; using System.Text; using System.Xml; -namespace RobloxFiles.XmlFormat +using RobloxFiles.DataTypes; +using RobloxFiles.XmlFormat; + +namespace RobloxFiles { public class XmlRobloxFile : RobloxFile { - // Runtime Specific - public readonly XmlDocument Root = new XmlDocument(); + public readonly XmlDocument XmlDocument = new XmlDocument(); internal Dictionary Instances = new Dictionary(); - internal Dictionary SharedStrings = new Dictionary(); + internal HashSet SharedStrings = new HashSet(); - internal XmlRobloxFile() + private Dictionary RawMetadata = new Dictionary(); + public Dictionary Metadata => RawMetadata; + + public XmlRobloxFile() { Name = "XmlRobloxFile"; ParentLocked = true; + Referent = "null"; } protected override void ReadFile(byte[] buffer) @@ -27,14 +33,14 @@ namespace RobloxFiles.XmlFormat try { string xml = Encoding.UTF8.GetString(buffer); - Root.LoadXml(xml); + XmlDocument.LoadXml(xml); } catch { throw new Exception("XmlRobloxFile: Could not read provided buffer as XML!"); } - XmlNode roblox = Root.FirstChild; + XmlNode roblox = XmlDocument.FirstChild; if (roblox != null && roblox.Name == "roblox") { @@ -59,6 +65,10 @@ namespace RobloxFiles.XmlFormat { XmlRobloxFileReader.ReadSharedStrings(child, this); } + else if (child.Name == "Meta") + { + XmlRobloxFileReader.ReadMetadata(child, this); + } } // Query the properties. @@ -71,7 +81,8 @@ namespace RobloxFiles.XmlFormat foreach (Property refProp in refProps) { - string refId = refProp.Value as string; + string refId = refProp.XmlToken; + refProp.XmlToken = "Ref"; if (Instances.ContainsKey(refId)) { @@ -86,26 +97,13 @@ namespace RobloxFiles.XmlFormat } } - // Resolve shared strings. + // Record shared strings. var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString); foreach (Property sharedProp in sharedProps) { - string md5 = sharedProp.Value as string; - - if (SharedStrings.ContainsKey(md5)) - { - string value = SharedStrings[md5]; - sharedProp.Value = value; - - byte[] data = Convert.FromBase64String(value); - sharedProp.RawBuffer = data; - } - else - { - string name = sharedProp.GetFullName(); - Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name); - } + SharedString shared = sharedProp.CastValue(); + SharedStrings.Add(shared.MD5_Key); } } else @@ -125,17 +123,32 @@ namespace RobloxFiles.XmlFormat Instances.Clear(); SharedStrings.Clear(); + // First, append the metadata + foreach (string key in Metadata.Keys) + { + string value = Metadata[key]; + + XmlElement meta = doc.CreateElement("Meta"); + meta.SetAttribute("name", key); + meta.InnerText = value; + + roblox.AppendChild(meta); + } + Instance[] children = GetChildren(); - // First, record all of the instances. + // Record all of the instances. foreach (Instance inst in children) XmlRobloxFileWriter.RecordInstances(this, inst); // Now append them into the document. foreach (Instance inst in children) { - XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this); - roblox.AppendChild(instNode); + if (inst.Archivable) + { + XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this); + roblox.AppendChild(instNode); + } } // Append the shared strings.