Large scale refactor to add class support!
Instance classes are now strongly typed with real property fields that are derived from the JSON API Dump! This required a lot of reworking across the board: - Classes and Enums are auto-generated in the 'Generated' folder now. This is done using a custom built-in plugin, which can be found in the Plugins folder of this project. - Property objects are now tied to .NET's reflection system. Reading and writing from them will try to redirect into a field of the Instance they are bound to. - Property types that were loosely defined now have proper data types (such as Color3uint8, Content, ProtectedString, SharedString, etc) - Fixed an error with the CFrame directional vectors. - The binary PRNT chunk now writes instances in child->parent order. - Enums are now generated correctly, with up-to-date values. - INST chunks are now referred to as 'Classes' instead of 'Types'. - Unary operator added to Vector2 and Vector3. - CollectionService tags can now be manipulated per-instance using the Instance.Tags member. - The Instance.Archivable property now works correctly. - XML files now save/load metadata correctly. - Cleaned up the property tokens directory. I probably missed a few things, but that's a general overview of everything that changed.
This commit is contained in:
parent
8e01f33d6b
commit
de8df15d3f
@ -35,9 +35,8 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
string chunkType = ChunkType.Replace('\0', ' ');
|
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)
|
public BinaryRobloxFileChunk(BinaryRobloxFileReader reader)
|
||||||
|
@ -4,9 +4,11 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
using RobloxFiles.BinaryFormat;
|
||||||
using RobloxFiles.BinaryFormat.Chunks;
|
using RobloxFiles.BinaryFormat.Chunks;
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
namespace RobloxFiles.BinaryFormat
|
namespace RobloxFiles
|
||||||
{
|
{
|
||||||
public class BinaryRobloxFile : RobloxFile
|
public class BinaryRobloxFile : RobloxFile
|
||||||
{
|
{
|
||||||
@ -14,7 +16,7 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public const string MagicHeader = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
|
public const string MagicHeader = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
|
||||||
|
|
||||||
public ushort Version;
|
public ushort Version;
|
||||||
public uint NumTypes;
|
public uint NumClasses;
|
||||||
public uint NumInstances;
|
public uint NumInstances;
|
||||||
public long Reserved;
|
public long Reserved;
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public override string ToString() => GetType().Name;
|
public override string ToString() => GetType().Name;
|
||||||
|
|
||||||
public Instance[] Instances;
|
public Instance[] Instances;
|
||||||
public INST[] Types;
|
public INST[] Classes;
|
||||||
|
|
||||||
internal META META = null;
|
internal META META = null;
|
||||||
internal SSTR SSTR = null;
|
internal SSTR SSTR = null;
|
||||||
@ -32,9 +34,9 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public Dictionary<string, string> Metadata => META?.Data;
|
public Dictionary<string, string> Metadata => META?.Data;
|
||||||
|
|
||||||
public bool HasSharedStrings => (SSTR != null);
|
public bool HasSharedStrings => (SSTR != null);
|
||||||
public IReadOnlyDictionary<uint, string> SharedStrings => SSTR?.Strings;
|
public IReadOnlyDictionary<uint, SharedString> SharedStrings => SSTR?.Strings;
|
||||||
|
|
||||||
internal BinaryRobloxFile()
|
public BinaryRobloxFile()
|
||||||
{
|
{
|
||||||
Name = "BinaryRobloxFile";
|
Name = "BinaryRobloxFile";
|
||||||
ParentLocked = true;
|
ParentLocked = true;
|
||||||
@ -55,14 +57,14 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
|
|
||||||
// Read header data.
|
// Read header data.
|
||||||
Version = reader.ReadUInt16();
|
Version = reader.ReadUInt16();
|
||||||
NumTypes = reader.ReadUInt32();
|
NumClasses = reader.ReadUInt32();
|
||||||
NumInstances = reader.ReadUInt32();
|
NumInstances = reader.ReadUInt32();
|
||||||
Reserved = reader.ReadInt64();
|
Reserved = reader.ReadInt64();
|
||||||
|
|
||||||
// Begin reading the file chunks.
|
// Begin reading the file chunks.
|
||||||
bool reading = true;
|
bool reading = true;
|
||||||
|
|
||||||
Types = new INST[NumTypes];
|
Classes = new INST[NumClasses];
|
||||||
Instances = new Instance[NumInstances];
|
Instances = new Instance[NumInstances];
|
||||||
|
|
||||||
while (reading)
|
while (reading)
|
||||||
@ -92,6 +94,7 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
handler = new SSTR();
|
handler = new SSTR();
|
||||||
break;
|
break;
|
||||||
case "END\0":
|
case "END\0":
|
||||||
|
Chunks.Add(chunk);
|
||||||
reading = false;
|
reading = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -129,41 +132,34 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
Chunks.Clear();
|
Chunks.Clear();
|
||||||
|
|
||||||
NumInstances = 0;
|
NumInstances = 0;
|
||||||
NumTypes = 0;
|
NumClasses = 0;
|
||||||
SSTR = null;
|
SSTR = null;
|
||||||
|
|
||||||
// Record all instances and types.
|
// Recursively capture all instances and classes.
|
||||||
writer.RecordInstances(Children);
|
writer.RecordInstances(Children);
|
||||||
|
|
||||||
// Apply the type values.
|
// Apply the recorded instances and classes.
|
||||||
INST.ApplyTypeMap(writer);
|
writer.ApplyClassMap();
|
||||||
|
|
||||||
// Write the INST chunks.
|
// Write the INST chunks.
|
||||||
foreach (INST type in Types)
|
foreach (INST inst in Classes)
|
||||||
{
|
writer.SaveChunk(inst);
|
||||||
var instChunk = type.SaveAsChunk(writer);
|
|
||||||
Chunks.Add(instChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the PROP chunks.
|
// Write the PROP chunks.
|
||||||
foreach (INST type in Types)
|
foreach (INST inst in Classes)
|
||||||
{
|
{
|
||||||
Dictionary<string, PROP> props = PROP.CollectProperties(writer, type);
|
Dictionary<string, PROP> props = PROP.CollectProperties(writer, inst);
|
||||||
|
|
||||||
foreach (string propName in props.Keys)
|
foreach (string propName in props.Keys)
|
||||||
{
|
{
|
||||||
PROP prop = props[propName];
|
PROP prop = props[propName];
|
||||||
|
writer.SaveChunk(prop);
|
||||||
var chunk = prop.SaveAsChunk(writer);
|
|
||||||
Chunks.Add(chunk);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the PRNT chunk.
|
// Write the PRNT chunk.
|
||||||
PRNT parents = new PRNT();
|
PRNT parents = new PRNT();
|
||||||
|
writer.SaveChunk(parents);
|
||||||
var parentChunk = parents.SaveAsChunk(writer);
|
|
||||||
Chunks.Add(parentChunk);
|
|
||||||
|
|
||||||
// Write the SSTR chunk.
|
// Write the SSTR chunk.
|
||||||
if (HasSharedStrings)
|
if (HasSharedStrings)
|
||||||
@ -180,15 +176,12 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write the END_ chunk.
|
// Write the END_ chunk.
|
||||||
writer.StartWritingChunk("END\0");
|
var endChunk = writer.WriteEndChunk();
|
||||||
writer.WriteString("</roblox>", true);
|
|
||||||
|
|
||||||
var endChunk = writer.FinishWritingChunk(false);
|
|
||||||
Chunks.Add(endChunk);
|
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))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
@ -196,20 +189,15 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
stream.SetLength(0);
|
stream.SetLength(0);
|
||||||
|
|
||||||
byte[] magicHeader = MagicHeader
|
writer.Write(MagicHeader
|
||||||
.Select(ch => (byte)ch)
|
.Select(ch => (byte)ch)
|
||||||
.ToArray();
|
.ToArray());
|
||||||
|
|
||||||
writer.Write(magicHeader);
|
|
||||||
|
|
||||||
writer.Write(Version);
|
writer.Write(Version);
|
||||||
writer.Write(NumTypes);
|
writer.Write(NumClasses);
|
||||||
writer.Write(NumInstances);
|
writer.Write(NumInstances);
|
||||||
|
writer.Write(Reserved);
|
||||||
|
|
||||||
// Write the 8 reserved-bytes.
|
|
||||||
writer.Write(0L);
|
|
||||||
|
|
||||||
// Write all of the chunks.
|
|
||||||
foreach (BinaryRobloxFileChunk chunk in Chunks)
|
foreach (BinaryRobloxFileChunk chunk in Chunks)
|
||||||
{
|
{
|
||||||
if (chunk.HasWriteBuffer)
|
if (chunk.HasWriteBuffer)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace RobloxFiles.BinaryFormat.Chunks
|
namespace RobloxFiles.BinaryFormat.Chunks
|
||||||
{
|
{
|
||||||
public class INST : IBinaryFileChunk
|
public class INST : IBinaryFileChunk
|
||||||
{
|
{
|
||||||
public int TypeIndex { get; internal set; }
|
public int ClassIndex { get; internal set; }
|
||||||
public string TypeName { get; internal set; }
|
public string ClassName { get; internal set; }
|
||||||
|
|
||||||
public bool IsService { get; internal set; }
|
public bool IsService { get; internal set; }
|
||||||
public List<bool> RootedServices { get; internal set; }
|
public List<bool> RootedServices { get; internal set; }
|
||||||
@ -14,17 +14,14 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
public int NumInstances { get; internal set; }
|
public int NumInstances { get; internal set; }
|
||||||
public List<int> InstanceIds { get; internal set; }
|
public List<int> InstanceIds { get; internal set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString() => ClassName;
|
||||||
{
|
|
||||||
return TypeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadFromReader(BinaryRobloxFileReader reader)
|
public void LoadFromReader(BinaryRobloxFileReader reader)
|
||||||
{
|
{
|
||||||
BinaryRobloxFile file = reader.File;
|
BinaryRobloxFile file = reader.File;
|
||||||
|
|
||||||
TypeIndex = reader.ReadInt32();
|
ClassIndex = reader.ReadInt32();
|
||||||
TypeName = reader.ReadString();
|
ClassName = reader.ReadString();
|
||||||
IsService = reader.ReadBoolean();
|
IsService = reader.ReadBoolean();
|
||||||
|
|
||||||
NumInstances = reader.ReadInt32();
|
NumInstances = reader.ReadInt32();
|
||||||
@ -44,32 +41,30 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
for (int i = 0; i < NumInstances; i++)
|
for (int i = 0; i < NumInstances; i++)
|
||||||
{
|
{
|
||||||
int instId = InstanceIds[i];
|
int instId = InstanceIds[i];
|
||||||
|
Type instType = Type.GetType($"RobloxFiles.{ClassName}") ?? typeof(Instance);
|
||||||
|
|
||||||
var inst = new Instance()
|
var inst = Activator.CreateInstance(instType) as Instance;
|
||||||
{
|
inst.Referent = instId.ToString();
|
||||||
ClassName = TypeName,
|
inst.IsService = IsService;
|
||||||
IsService = IsService,
|
|
||||||
Referent = instId.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
if (IsService)
|
if (IsService)
|
||||||
{
|
{
|
||||||
bool rooted = RootedServices[i];
|
bool isRooted = RootedServices[i];
|
||||||
inst.IsRootedService = rooted;
|
inst.Parent = (isRooted ? file : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Instances[instId] = inst;
|
file.Instances[instId] = inst;
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Types[TypeIndex] = this;
|
file.Classes[ClassIndex] = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryRobloxFileChunk SaveAsChunk(BinaryRobloxFileWriter writer)
|
public BinaryRobloxFileChunk SaveAsChunk(BinaryRobloxFileWriter writer)
|
||||||
{
|
{
|
||||||
writer.StartWritingChunk(this);
|
writer.StartWritingChunk(this);
|
||||||
|
|
||||||
writer.Write(TypeIndex);
|
writer.Write(ClassIndex);
|
||||||
writer.WriteString(TypeName);
|
writer.WriteString(ClassName);
|
||||||
|
|
||||||
writer.Write(IsService);
|
writer.Write(IsService);
|
||||||
writer.Write(NumInstances);
|
writer.Write(NumInstances);
|
||||||
@ -82,30 +77,13 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
foreach (int instId in InstanceIds)
|
foreach (int instId in InstanceIds)
|
||||||
{
|
{
|
||||||
Instance service = file.Instances[instId];
|
Instance service = file.Instances[instId];
|
||||||
writer.Write(service.IsRootedService);
|
bool isRooted = (service.Parent == file);
|
||||||
|
|
||||||
|
writer.Write(isRooted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return writer.FinishWritingChunk();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,21 +27,21 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
Instance child = file.Instances[childId];
|
Instance child = file.Instances[childId];
|
||||||
child.Parent = (parentId >= 0 ? file.Instances[parentId] : file);
|
child.Parent = (parentId >= 0 ? file.Instances[parentId] : file);
|
||||||
|
child.ParentLocked = child.IsService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryRobloxFileChunk SaveAsChunk(BinaryRobloxFileWriter writer)
|
public BinaryRobloxFileChunk SaveAsChunk(BinaryRobloxFileWriter writer)
|
||||||
{
|
{
|
||||||
BinaryRobloxFile file = writer.File;
|
|
||||||
writer.StartWritingChunk(this);
|
writer.StartWritingChunk(this);
|
||||||
|
|
||||||
Format = 0;
|
Format = 0;
|
||||||
NumRelations = file.Instances.Length;
|
NumRelations = 0;
|
||||||
|
|
||||||
ChildrenIds = new List<int>();
|
ChildrenIds = new List<int>();
|
||||||
ParentIds = new List<int>();
|
ParentIds = new List<int>();
|
||||||
|
|
||||||
foreach (Instance inst in file.Instances)
|
foreach (Instance inst in writer.PostInstances)
|
||||||
{
|
{
|
||||||
Instance parent = inst.Parent;
|
Instance parent = inst.Parent;
|
||||||
|
|
||||||
@ -53,6 +53,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
ChildrenIds.Add(childId);
|
ChildrenIds.Add(childId);
|
||||||
ParentIds.Add(parentId);
|
ParentIds.Add(parentId);
|
||||||
|
|
||||||
|
NumRelations++;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Write(Format);
|
writer.Write(Format);
|
||||||
|
@ -1,50 +1,62 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
using RobloxFiles.Enums;
|
using RobloxFiles.Enums;
|
||||||
using RobloxFiles.DataTypes;
|
using RobloxFiles.DataTypes;
|
||||||
using RobloxFiles.Utility;
|
using RobloxFiles.Utility;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace RobloxFiles.BinaryFormat.Chunks
|
namespace RobloxFiles.BinaryFormat.Chunks
|
||||||
{
|
{
|
||||||
public class PROP : IBinaryFileChunk
|
public class PROP : IBinaryFileChunk
|
||||||
{
|
{
|
||||||
public string Name { get; internal set; }
|
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 PropertyType Type { get; internal set; }
|
||||||
|
|
||||||
public byte TypeId
|
public byte TypeId
|
||||||
{
|
{
|
||||||
get { return (byte)Type; }
|
get { return (byte)Type; }
|
||||||
internal set { Type = (PropertyType)value; }
|
internal set { Type = (PropertyType)value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Type} {ClassName}.{Name}";
|
||||||
|
}
|
||||||
|
|
||||||
public void LoadFromReader(BinaryRobloxFileReader reader)
|
public void LoadFromReader(BinaryRobloxFileReader reader)
|
||||||
{
|
{
|
||||||
BinaryRobloxFile file = reader.File;
|
BinaryRobloxFile file = reader.File;
|
||||||
|
|
||||||
TypeIndex = reader.ReadInt32();
|
ClassIndex = reader.ReadInt32();
|
||||||
Name = reader.ReadString();
|
Name = reader.ReadString();
|
||||||
TypeId = reader.ReadByte();
|
|
||||||
|
|
||||||
INST type = file.Types[TypeIndex];
|
byte propType = reader.ReadByte();
|
||||||
Property[] props = new Property[type.NumInstances];
|
Type = (PropertyType)propType;
|
||||||
|
|
||||||
var ids = type.InstanceIds;
|
INST inst = file.Classes[ClassIndex];
|
||||||
int instCount = type.NumInstances;
|
ClassName = inst.ClassName;
|
||||||
|
|
||||||
|
Property[] props = new Property[inst.NumInstances];
|
||||||
|
|
||||||
|
var ids = inst.InstanceIds;
|
||||||
|
int instCount = inst.NumInstances;
|
||||||
|
|
||||||
for (int i = 0; i < instCount; i++)
|
for (int i = 0; i < instCount; i++)
|
||||||
{
|
{
|
||||||
int id = ids[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;
|
props[i] = prop;
|
||||||
|
|
||||||
inst.AddProperty(ref prop);
|
instance.AddProperty(ref prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup some short-hand functions for actions used during the read procedure.
|
// Setup some short-hand functions for actions used during the read procedure.
|
||||||
@ -66,14 +78,36 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.String:
|
case PropertyType.String:
|
||||||
readProperties(i =>
|
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.
|
// 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.
|
// This will allow the developer to read the sequence without any mangling from C# strings.
|
||||||
byte[] buffer = reader.GetLastStringBuffer();
|
byte[] buffer = reader.GetLastStringBuffer();
|
||||||
props[i].RawBuffer = buffer;
|
props[i].RawBuffer = buffer;
|
||||||
|
|
||||||
|
// 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;
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -213,15 +247,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.Quaternion:
|
case PropertyType.Quaternion:
|
||||||
// Temporarily load the rotation matrices into their properties.
|
// Temporarily load the rotation matrices into their properties.
|
||||||
// We'll update them to CFrames once we iterate over the position data.
|
// 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.
|
// 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);
|
NormalId xColumn = (NormalId)(orientId / 6);
|
||||||
Vector3 R0 = Vector3.FromNormalId(xColumn);
|
Vector3 R0 = Vector3.FromNormalId(xColumn);
|
||||||
@ -232,8 +267,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
// Compute R2 using the cross product of R0 and R1.
|
// Compute R2 using the cross product of R0 and R1.
|
||||||
Vector3 R2 = R0.Cross(R1);
|
Vector3 R2 = R0.Cross(R1);
|
||||||
|
|
||||||
// Generate the rotation matrix and return it.
|
// Generate the rotation matrix.
|
||||||
return new float[9]
|
matrices[i] = new float[9]
|
||||||
{
|
{
|
||||||
R0.X, R0.Y, R0.Z,
|
R0.X, R0.Y, R0.Z,
|
||||||
R1.X, R1.Y, R1.Z,
|
R1.X, R1.Y, R1.Z,
|
||||||
@ -247,8 +282,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
Quaternion quaternion = new Quaternion(qx, qy, qz, qw);
|
Quaternion quaternion = new Quaternion(qx, qy, qz, qw);
|
||||||
var rotation = quaternion.ToCFrame();
|
var rotation = quaternion.ToCFrame();
|
||||||
|
matrices[i] = rotation.GetComponents();
|
||||||
return rotation.GetComponents();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -260,9 +294,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
matrix[m] = value;
|
matrix[m] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matrix;
|
matrices[i] = matrix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
float[] CFrame_X = readFloats(),
|
float[] CFrame_X = readFloats(),
|
||||||
CFrame_Y = readFloats(),
|
CFrame_Y = readFloats(),
|
||||||
@ -270,25 +304,54 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
readProperties(i =>
|
readProperties(i =>
|
||||||
{
|
{
|
||||||
float[] matrix = props[i].Value as float[];
|
float[] matrix = matrices[i];
|
||||||
|
|
||||||
float x = CFrame_X[i],
|
float x = CFrame_X[i],
|
||||||
y = CFrame_Y[i],
|
y = CFrame_Y[i],
|
||||||
z = CFrame_Z[i];
|
z = CFrame_Z[i];
|
||||||
|
|
||||||
|
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 };
|
float[] position = new float[3] { x, y, z };
|
||||||
float[] components = position.Concat(matrix).ToArray();
|
components = position.Concat(matrix).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
return new CFrame(components);
|
return new CFrame(components);
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Enum:
|
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);
|
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;
|
break;
|
||||||
case PropertyType.Ref:
|
case PropertyType.Ref:
|
||||||
@ -345,9 +408,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
B = reader.ReadFloat();
|
B = reader.ReadFloat();
|
||||||
|
|
||||||
Color3 Value = new Color3(R, G, B);
|
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);
|
return new ColorSequence(keypoints);
|
||||||
@ -415,7 +478,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
g = Color3uint8_G[i],
|
g = Color3uint8_G[i],
|
||||||
b = Color3uint8_B[i];
|
b = Color3uint8_B[i];
|
||||||
|
|
||||||
return Color3.FromRGB(r, g, b);
|
Color3uint8 result = Color3.FromRGB(r, g, b);
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -434,6 +498,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
readProperties(i =>
|
readProperties(i =>
|
||||||
{
|
{
|
||||||
uint key = SharedKeys[i];
|
uint key = SharedKeys[i];
|
||||||
|
|
||||||
return file.SharedStrings[key];
|
return file.SharedStrings[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -455,11 +520,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
foreach (int instId in inst.InstanceIds)
|
foreach (int instId in inst.InstanceIds)
|
||||||
{
|
{
|
||||||
Instance instance = file.Instances[instId];
|
Instance instance = file.Instances[instId];
|
||||||
|
var props = instance.RefreshProperties();
|
||||||
|
|
||||||
var props = instance.Properties;
|
foreach (string propName in props.Keys)
|
||||||
var propNames = props.Keys;
|
|
||||||
|
|
||||||
foreach (string propName in propNames)
|
|
||||||
{
|
{
|
||||||
if (!propMap.ContainsKey(propName))
|
if (!propMap.ContainsKey(propName))
|
||||||
{
|
{
|
||||||
@ -469,7 +532,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
{
|
{
|
||||||
Name = prop.Name,
|
Name = prop.Name,
|
||||||
Type = prop.Type,
|
Type = prop.Type,
|
||||||
TypeIndex = inst.TypeIndex
|
|
||||||
|
ClassIndex = inst.ClassIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
propMap.Add(propName, propChunk);
|
propMap.Add(propName, propChunk);
|
||||||
@ -484,13 +548,13 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
{
|
{
|
||||||
BinaryRobloxFile file = writer.File;
|
BinaryRobloxFile file = writer.File;
|
||||||
|
|
||||||
INST inst = file.Types[TypeIndex];
|
INST inst = file.Classes[ClassIndex];
|
||||||
var props = new List<Property>();
|
var props = new List<Property>();
|
||||||
|
|
||||||
foreach (int instId in inst.InstanceIds)
|
foreach (int instId in inst.InstanceIds)
|
||||||
{
|
{
|
||||||
Instance instance = file.Instances[instId];
|
Instance instance = file.Instances[instId];
|
||||||
Property prop = instance.GetProperty(Name);
|
Property prop = instance.Properties[Name];
|
||||||
|
|
||||||
if (prop == null)
|
if (prop == null)
|
||||||
throw new Exception($"Property {Name} must be defined in {instance.GetFullName()}!");
|
throw new Exception($"Property {Name} must be defined in {instance.GetFullName()}!");
|
||||||
@ -502,7 +566,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
}
|
}
|
||||||
|
|
||||||
writer.StartWritingChunk(this);
|
writer.StartWritingChunk(this);
|
||||||
writer.Write(TypeIndex);
|
writer.Write(ClassIndex);
|
||||||
|
|
||||||
writer.WriteString(Name);
|
writer.WriteString(Name);
|
||||||
writer.Write(TypeId);
|
writer.Write(TypeId);
|
||||||
@ -526,7 +590,12 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Bool:
|
case PropertyType.Bool:
|
||||||
props.ForEach(prop => prop.WriteValue<bool>());
|
props.ForEach(prop =>
|
||||||
|
{
|
||||||
|
bool value = prop.CastValue<bool>();
|
||||||
|
writer.Write(value);
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Int:
|
case PropertyType.Int:
|
||||||
var ints = new List<int>();
|
var ints = new List<int>();
|
||||||
@ -737,7 +806,15 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
props.ForEach(prop =>
|
props.ForEach(prop =>
|
||||||
{
|
{
|
||||||
uint value = prop.CastValue<uint>();
|
if (prop.Value is uint)
|
||||||
|
{
|
||||||
|
uint raw = prop.CastValue<uint>();
|
||||||
|
Enums.Add(raw);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int signed = (int)prop.Value;
|
||||||
|
uint value = (uint)signed;
|
||||||
Enums.Add(value);
|
Enums.Add(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -805,7 +882,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
writer.Write(color.G);
|
writer.Write(color.G);
|
||||||
writer.Write(color.B);
|
writer.Write(color.B);
|
||||||
|
|
||||||
writer.Write(0);
|
writer.Write(keyPoint.Envelope);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -873,21 +950,20 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
props.ForEach(prop =>
|
props.ForEach(prop =>
|
||||||
{
|
{
|
||||||
Color3 value = prop.CastValue<Color3>();
|
Color3uint8 value = prop.CastValue<Color3uint8>();
|
||||||
|
Color3uint8_R.Add(value.R);
|
||||||
byte r = (byte)(value.R * 255);
|
Color3uint8_G.Add(value.G);
|
||||||
Color3uint8_R.Add(r);
|
Color3uint8_B.Add(value.B);
|
||||||
|
|
||||||
byte g = (byte)(value.G * 255);
|
|
||||||
Color3uint8_G.Add(g);
|
|
||||||
|
|
||||||
byte b = (byte)(value.B * 255);
|
|
||||||
Color3uint8_B.Add(b);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
writer.Write(Color3uint8_R.ToArray());
|
byte[] rBuffer = Color3uint8_R.ToArray();
|
||||||
writer.Write(Color3uint8_G.ToArray());
|
writer.Write(rBuffer);
|
||||||
writer.Write(Color3uint8_B.ToArray());
|
|
||||||
|
byte[] gBuffer = Color3uint8_G.ToArray();
|
||||||
|
writer.Write(gBuffer);
|
||||||
|
|
||||||
|
byte[] bBuffer = Color3uint8_B.ToArray();
|
||||||
|
writer.Write(bBuffer);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Int64:
|
case PropertyType.Int64:
|
||||||
@ -918,27 +994,18 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
props.ForEach(prop =>
|
props.ForEach(prop =>
|
||||||
{
|
{
|
||||||
uint sharedKey = 0;
|
SharedString shared = prop.CastValue<SharedString>();
|
||||||
|
string key = shared.MD5_Key;
|
||||||
string value = prop.CastValue<string>();
|
|
||||||
byte[] buffer = Encoding.UTF8.GetBytes(value);
|
|
||||||
|
|
||||||
using (MD5 md5 = MD5.Create())
|
|
||||||
{
|
|
||||||
byte[] hash = md5.ComputeHash(buffer);
|
|
||||||
string key = Convert.ToBase64String(hash);
|
|
||||||
|
|
||||||
if (!sstr.Lookup.ContainsKey(key))
|
if (!sstr.Lookup.ContainsKey(key))
|
||||||
{
|
{
|
||||||
uint id = (uint)(sstr.NumHashes++);
|
uint id = (uint)(sstr.NumHashes++);
|
||||||
sstr.Strings.Add(id, value);
|
sstr.Strings.Add(id, shared);
|
||||||
sstr.Lookup.Add(key, id);
|
sstr.Lookup.Add(key, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedKey = sstr.Lookup[key];
|
uint hashId = sstr.Lookup[key];
|
||||||
}
|
sharedKeys.Add(hashId);
|
||||||
|
|
||||||
sharedKeys.Add(sharedKey);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
writer.WriteInterleaved(sharedKeys);
|
writer.WriteInterleaved(sharedKeys);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
namespace RobloxFiles.BinaryFormat.Chunks
|
namespace RobloxFiles.BinaryFormat.Chunks
|
||||||
{
|
{
|
||||||
@ -9,7 +10,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
public int NumHashes;
|
public int NumHashes;
|
||||||
|
|
||||||
public Dictionary<string, uint> Lookup = new Dictionary<string, uint>();
|
public Dictionary<string, uint> Lookup = new Dictionary<string, uint>();
|
||||||
public Dictionary<uint, string> Strings = new Dictionary<uint, string>();
|
public Dictionary<uint, SharedString> Strings = new Dictionary<uint, SharedString>();
|
||||||
|
|
||||||
public void LoadFromReader(BinaryRobloxFileReader reader)
|
public void LoadFromReader(BinaryRobloxFileReader reader)
|
||||||
{
|
{
|
||||||
@ -25,10 +26,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
int length = reader.ReadInt32();
|
int length = reader.ReadInt32();
|
||||||
byte[] data = reader.ReadBytes(length);
|
byte[] data = reader.ReadBytes(length);
|
||||||
|
|
||||||
string key = Convert.ToBase64String(md5);
|
SharedString value = SharedString.FromBuffer(data);
|
||||||
string value = Convert.ToBase64String(data);
|
Lookup.Add(value.MD5_Key, id);
|
||||||
|
|
||||||
Lookup.Add(key, id);
|
|
||||||
Strings.Add(id, value);
|
Strings.Add(id, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +48,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
byte[] md5 = Convert.FromBase64String(key);
|
byte[] md5 = Convert.FromBase64String(key);
|
||||||
writer.Write(md5);
|
writer.Write(md5);
|
||||||
|
|
||||||
string value = Strings[pair.Value];
|
SharedString value = Strings[pair.Value];
|
||||||
byte[] buffer = Convert.FromBase64String(value);
|
byte[] buffer = SharedString.FindRecord(value.MD5_Key);
|
||||||
|
|
||||||
writer.Write(buffer.Length);
|
writer.Write(buffer.Length);
|
||||||
writer.Write(buffer);
|
writer.Write(buffer);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -16,10 +18,15 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public string ChunkType { get; private set; }
|
public string ChunkType { get; private set; }
|
||||||
public long ChunkStart { get; private set; }
|
public long ChunkStart { get; private set; }
|
||||||
|
|
||||||
public Dictionary<string, INST> TypeMap;
|
public Dictionary<string, INST> ClassMap;
|
||||||
public readonly BinaryRobloxFile File;
|
public readonly BinaryRobloxFile File;
|
||||||
|
|
||||||
|
// Instances in parent->child order
|
||||||
public List<Instance> Instances;
|
public List<Instance> Instances;
|
||||||
|
|
||||||
|
// Instances in child->parent order
|
||||||
|
public List<Instance> PostInstances;
|
||||||
|
|
||||||
public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer = null) : base(workBuffer ?? new MemoryStream())
|
public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer = null) : base(workBuffer ?? new MemoryStream())
|
||||||
{
|
{
|
||||||
File = file;
|
File = file;
|
||||||
@ -28,7 +35,9 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
ChunkType = "";
|
ChunkType = "";
|
||||||
|
|
||||||
Instances = new List<Instance>();
|
Instances = new List<Instance>();
|
||||||
TypeMap = new Dictionary<string, INST>();
|
PostInstances = new List<Instance>();
|
||||||
|
|
||||||
|
ClassMap = new Dictionary<string, INST>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] GetBytes<T>(T value, int bufferSize, IntPtr converter)
|
private static byte[] GetBytes<T>(T value, int bufferSize, IntPtr converter)
|
||||||
@ -144,37 +153,65 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
{
|
{
|
||||||
foreach (Instance instance in instances)
|
foreach (Instance instance in instances)
|
||||||
{
|
{
|
||||||
int instId = (int)(File.NumInstances++);
|
if (!instance.Archivable)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int instId = (int)(File.NumInstances++);
|
||||||
instance.Referent = instId.ToString();
|
instance.Referent = instId.ToString();
|
||||||
Instances.Add(instance);
|
Instances.Add(instance);
|
||||||
|
|
||||||
string className = instance.ClassName;
|
string className = instance.ClassName;
|
||||||
INST inst = null;
|
INST inst = null;
|
||||||
|
|
||||||
if (!TypeMap.ContainsKey(className))
|
if (!ClassMap.ContainsKey(className))
|
||||||
{
|
{
|
||||||
inst = new INST()
|
inst = new INST()
|
||||||
{
|
{
|
||||||
TypeName = className,
|
ClassName = className,
|
||||||
InstanceIds = new List<int>(),
|
InstanceIds = new List<int>(),
|
||||||
IsService = instance.IsService
|
IsService = instance.IsService
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeMap.Add(className, inst);
|
ClassMap.Add(className, inst);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inst = TypeMap[className];
|
inst = ClassMap[className];
|
||||||
}
|
}
|
||||||
|
|
||||||
inst.NumInstances++;
|
inst.NumInstances++;
|
||||||
inst.InstanceIds.Add(instId);
|
inst.InstanceIds.Add(instId);
|
||||||
|
|
||||||
RecordInstances(instance.GetChildren());
|
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.
|
// Marks that we are writing a chunk.
|
||||||
public bool StartWritingChunk(string chunkType)
|
public bool StartWritingChunk(string chunkType)
|
||||||
{
|
{
|
||||||
@ -236,5 +273,19 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
|
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SaveChunk(IBinaryFileChunk handler)
|
||||||
|
{
|
||||||
|
var chunk = handler.SaveAsChunk(this);
|
||||||
|
File.Chunks.Add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinaryRobloxFileChunk WriteEndChunk()
|
||||||
|
{
|
||||||
|
StartWritingChunk("END\0");
|
||||||
|
WriteString("</roblox>", true);
|
||||||
|
|
||||||
|
return FinishWritingChunk(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,23 @@ namespace RobloxFiles.DataTypes
|
|||||||
public float Y => m24;
|
public float Y => m24;
|
||||||
public float Z => m34;
|
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, m12, m13);
|
||||||
public Vector3 RightVector => new Vector3( m11, m21, m31);
|
public Vector3 UpVector => new Vector3( m21, m22, m23);
|
||||||
public Vector3 UpVector => new Vector3( m12, m22, m32);
|
public Vector3 LookVector => new Vector3(-m31, -m32, -m33);
|
||||||
|
|
||||||
public CFrame()
|
public CFrame()
|
||||||
{
|
{
|
||||||
@ -41,7 +53,6 @@ namespace RobloxFiles.DataTypes
|
|||||||
m34 = nz;
|
m34 = nz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public CFrame(Vector3 eye, Vector3 look)
|
public CFrame(Vector3 eye, Vector3 look)
|
||||||
{
|
{
|
||||||
Vector3 zAxis = (eye - look).Unit;
|
Vector3 zAxis = (eye - look).Unit;
|
||||||
@ -138,6 +149,7 @@ namespace RobloxFiles.DataTypes
|
|||||||
public static Vector3 operator *(CFrame a, Vector3 b)
|
public static Vector3 operator *(CFrame a, Vector3 b)
|
||||||
{
|
{
|
||||||
float[] ac = a.GetComponents();
|
float[] ac = a.GetComponents();
|
||||||
|
|
||||||
float x = ac[0], y = ac[1], z = ac[2],
|
float x = ac[0], y = ac[1], z = ac[2],
|
||||||
m11 = ac[3], m12 = ac[4], m13 = ac[5],
|
m11 = ac[3], m12 = ac[4], m13 = ac[5],
|
||||||
m21 = ac[6], m22 = ac[7], m23 = ac[8],
|
m21 = ac[6], m22 = ac[7], m23 = ac[8],
|
||||||
@ -345,14 +357,14 @@ namespace RobloxFiles.DataTypes
|
|||||||
|
|
||||||
for (int i = 3; i < 12; i++)
|
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
|
// Approximately ±1
|
||||||
sum1++;
|
sum1++;
|
||||||
}
|
}
|
||||||
else if (Math.Abs(t) < 10e-5f)
|
else if (t.FuzzyEquals(0))
|
||||||
{
|
{
|
||||||
// Approximately ±0
|
// Approximately ±0
|
||||||
sum0++;
|
sum0++;
|
||||||
@ -364,10 +376,10 @@ namespace RobloxFiles.DataTypes
|
|||||||
|
|
||||||
private static bool IsLegalOrientId(int orientId)
|
private static bool IsLegalOrientId(int orientId)
|
||||||
{
|
{
|
||||||
int xNormalAbs = (orientId / 6) % 3;
|
int xOrientId = (orientId / 6) % 3;
|
||||||
int yNormalAbs = orientId % 3;
|
int yOrientId = orientId % 3;
|
||||||
|
|
||||||
return (xNormalAbs != yNormalAbs);
|
return (xOrientId != yOrientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetOrientId()
|
public int GetOrientId()
|
||||||
|
41
DataTypes/Color3uint8.cs
Normal file
41
DataTypes/Color3uint8.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
namespace RobloxFiles.DataTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,8 @@ namespace RobloxFiles.DataTypes
|
|||||||
{
|
{
|
||||||
public readonly ColorSequenceKeypoint[] Keypoints;
|
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)
|
public ColorSequence(Color3 c0, Color3 c1)
|
||||||
@ -35,10 +31,13 @@ namespace RobloxFiles.DataTypes
|
|||||||
if (keypoints[key - 1].Time > keypoints[key].Time)
|
if (keypoints[key - 1].Time > keypoints[key].Time)
|
||||||
throw new Exception("ColorSequence: all keypoints must be ordered by 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");
|
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");
|
throw new Exception("ColorSequence must end at time=1.0");
|
||||||
|
|
||||||
Keypoints = keypoints;
|
Keypoints = keypoints;
|
||||||
|
@ -4,18 +4,18 @@
|
|||||||
{
|
{
|
||||||
public readonly float Time;
|
public readonly float Time;
|
||||||
public readonly Color3 Value;
|
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;
|
Time = time;
|
||||||
Value = value;
|
Value = value;
|
||||||
Reserved = reserved;
|
Envelope = envelope;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
DataTypes/Content.cs
Normal file
32
DataTypes/Content.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
namespace RobloxFiles.DataTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Content is a type used by most url-based XML properties.
|
||||||
|
/// Here, it only exists as a wrapper class for strings.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,12 @@ namespace RobloxFiles.DataTypes
|
|||||||
public readonly float Min;
|
public readonly float Min;
|
||||||
public readonly float Max;
|
public readonly float Max;
|
||||||
|
|
||||||
|
public NumberRange(float num)
|
||||||
|
{
|
||||||
|
Min = num;
|
||||||
|
Max = num;
|
||||||
|
}
|
||||||
|
|
||||||
public NumberRange(float min = 0, float max = 0)
|
public NumberRange(float min = 0, float max = 0)
|
||||||
{
|
{
|
||||||
if (max - min < 0)
|
if (max - min < 0)
|
||||||
|
@ -35,10 +35,13 @@ namespace RobloxFiles.DataTypes
|
|||||||
if (keypoints[key - 1].Time > keypoints[key].Time)
|
if (keypoints[key - 1].Time > keypoints[key].Time)
|
||||||
throw new Exception("NumberSequence: all keypoints must be ordered by 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");
|
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");
|
throw new Exception("NumberSequence must end at time=1.0");
|
||||||
|
|
||||||
Keypoints = keypoints;
|
Keypoints = keypoints;
|
||||||
|
31
DataTypes/ProtectedString.cs
Normal file
31
DataTypes/ProtectedString.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
namespace RobloxFiles.DataTypes
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ProtectedString is a type used by some of the XML properties.
|
||||||
|
/// Here, it only exists as a wrapper class for strings.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
208
DataTypes/Quaternion.cs
Normal file
208
DataTypes/Quaternion.cs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
|
namespace RobloxFiles.Utility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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!
|
||||||
|
/// </summary>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,10 +20,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ray(Vector3 origin, Vector3 direction)
|
public Ray(Vector3 origin = null, Vector3 direction = null)
|
||||||
{
|
{
|
||||||
Origin = origin;
|
Origin = origin ?? new Vector3();
|
||||||
Direction = direction;
|
Direction = direction ?? new Vector3();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
62
DataTypes/SharedString.cs
Normal file
62
DataTypes/SharedString.cs
Normal file
@ -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<string, byte[]> Records = new Dictionary<string, byte[]>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@ namespace RobloxFiles.DataTypes
|
|||||||
Y = y;
|
Y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2(float[] coords)
|
internal Vector2(float[] coords)
|
||||||
{
|
{
|
||||||
X = coords.Length > 0 ? coords[0] : 0;
|
X = coords.Length > 0 ? coords[0] : 0;
|
||||||
Y = coords.Length > 1 ? coords[1] : 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 /(Vector2 v, float n) => upcastFloatOp(v, n, div);
|
||||||
public static Vector2 operator /(float n, Vector2 v) => upcastFloatOp(n, v, 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 static Vector2 Zero => new Vector2(0, 0);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
@ -30,7 +30,7 @@ namespace RobloxFiles.DataTypes
|
|||||||
Z = z;
|
Z = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3(float[] coords)
|
internal Vector3(float[] coords)
|
||||||
{
|
{
|
||||||
X = coords.Length > 0 ? coords[0] : 0;
|
X = coords.Length > 0 ? coords[0] : 0;
|
||||||
Y = coords.Length > 1 ? coords[1] : 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 /(Vector3 v, float n) => upcastFloatOp(v, n, div);
|
||||||
public static Vector3 operator /(float n, Vector3 v) => upcastFloatOp(n, v, 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 Zero => new Vector3(0, 0, 0);
|
||||||
public static Vector3 Right => new Vector3(1, 0, 0);
|
public static Vector3 Right => new Vector3(1, 0, 0);
|
||||||
public static Vector3 Up => new Vector3(0, 1, 0);
|
public static Vector3 Up => new Vector3(0, 1, 0);
|
||||||
@ -141,7 +146,7 @@ namespace RobloxFiles.DataTypes
|
|||||||
|
|
||||||
float dotProd = normal.Dot(this);
|
float dotProd = normal.Dot(this);
|
||||||
|
|
||||||
if (Math.Abs(dotProd - 1f) < 10e-5f)
|
if (dotProd.FuzzyEquals(1))
|
||||||
{
|
{
|
||||||
result = i;
|
result = i;
|
||||||
break;
|
break;
|
||||||
|
3130
Generated/Classes.cs
Normal file
3130
Generated/Classes.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
// This is an auto-generated list of all available enums on Roblox!
|
// Auto-generated list of Roblox enums.
|
||||||
// Updated as of 0.370.0.274702
|
// Updated as of 0.392.0.316618
|
||||||
|
|
||||||
namespace RobloxFiles.Enums
|
namespace RobloxFiles.Enums
|
||||||
{
|
{
|
||||||
@ -26,6 +26,12 @@ namespace RobloxFiles.Enums
|
|||||||
Servo
|
Servo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum AlignType
|
||||||
|
{
|
||||||
|
Parallel,
|
||||||
|
Perpendicular
|
||||||
|
}
|
||||||
|
|
||||||
public enum AnimationPriority
|
public enum AnimationPriority
|
||||||
{
|
{
|
||||||
Idle,
|
Idle,
|
||||||
@ -55,9 +61,15 @@ namespace RobloxFiles.Enums
|
|||||||
ScaleWithParentSize
|
ScaleWithParentSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum AssetFetchStatus
|
||||||
|
{
|
||||||
|
Success,
|
||||||
|
Failure
|
||||||
|
}
|
||||||
|
|
||||||
public enum AssetType
|
public enum AssetType
|
||||||
{
|
{
|
||||||
Image,
|
Image = 1,
|
||||||
TeeShirt,
|
TeeShirt,
|
||||||
Audio,
|
Audio,
|
||||||
Mesh,
|
Mesh,
|
||||||
@ -99,7 +111,8 @@ namespace RobloxFiles.Enums
|
|||||||
WalkAnimation,
|
WalkAnimation,
|
||||||
PoseAnimation,
|
PoseAnimation,
|
||||||
EarAccessory,
|
EarAccessory,
|
||||||
EyeAccessory
|
EyeAccessory,
|
||||||
|
EmoteAnimation = 61
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AutoJointsMode
|
public enum AutoJointsMode
|
||||||
@ -113,7 +126,8 @@ namespace RobloxFiles.Enums
|
|||||||
{
|
{
|
||||||
Friend,
|
Friend,
|
||||||
Chat,
|
Chat,
|
||||||
Emote
|
Emote,
|
||||||
|
InspectMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AvatarJointPositionType
|
public enum AvatarJointPositionType
|
||||||
@ -169,6 +183,14 @@ namespace RobloxFiles.Enums
|
|||||||
Unknown = 17
|
Unknown = 17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum BreakReason
|
||||||
|
{
|
||||||
|
Other,
|
||||||
|
Error,
|
||||||
|
SpecialBreakpoint,
|
||||||
|
UserBreakpoint
|
||||||
|
}
|
||||||
|
|
||||||
public enum Button
|
public enum Button
|
||||||
{
|
{
|
||||||
Dismount = 8,
|
Dismount = 8,
|
||||||
@ -250,7 +272,7 @@ namespace RobloxFiles.Enums
|
|||||||
|
|
||||||
public enum CenterDialogType
|
public enum CenterDialogType
|
||||||
{
|
{
|
||||||
UnsolicitedDialog,
|
UnsolicitedDialog = 1,
|
||||||
PlayerInitiatedDialog,
|
PlayerInitiatedDialog,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
QuitDialog
|
QuitDialog
|
||||||
@ -258,7 +280,7 @@ namespace RobloxFiles.Enums
|
|||||||
|
|
||||||
public enum ChatCallbackType
|
public enum ChatCallbackType
|
||||||
{
|
{
|
||||||
OnCreatingChatWindow,
|
OnCreatingChatWindow = 1,
|
||||||
OnClientSendingMessage,
|
OnClientSendingMessage,
|
||||||
OnClientFormattingMessage,
|
OnClientFormattingMessage,
|
||||||
OnServerReceivingMessage = 17
|
OnServerReceivingMessage = 17
|
||||||
@ -342,6 +364,8 @@ namespace RobloxFiles.Enums
|
|||||||
DisconnectIdle,
|
DisconnectIdle,
|
||||||
DisconnectRaknetErrors,
|
DisconnectRaknetErrors,
|
||||||
DisconnectWrongVersion,
|
DisconnectWrongVersion,
|
||||||
|
DisconnectBySecurityPolicy,
|
||||||
|
DisconnectBlockedIP,
|
||||||
PlacelaunchErrors = 512,
|
PlacelaunchErrors = 512,
|
||||||
PlacelaunchDisabled = 515,
|
PlacelaunchDisabled = 515,
|
||||||
PlacelaunchError,
|
PlacelaunchError,
|
||||||
@ -376,10 +400,9 @@ namespace RobloxFiles.Enums
|
|||||||
public enum ContextActionPriority
|
public enum ContextActionPriority
|
||||||
{
|
{
|
||||||
Low = 1000,
|
Low = 1000,
|
||||||
|
Default = 2000,
|
||||||
Medium = 2000,
|
Medium = 2000,
|
||||||
High = 3000,
|
High = 3000
|
||||||
|
|
||||||
Default = Medium
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContextActionResult
|
public enum ContextActionResult
|
||||||
@ -400,7 +423,8 @@ namespace RobloxFiles.Enums
|
|||||||
Health,
|
Health,
|
||||||
Backpack,
|
Backpack,
|
||||||
Chat,
|
Chat,
|
||||||
All
|
All,
|
||||||
|
EmotesMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum CreatorType
|
public enum CreatorType
|
||||||
@ -423,12 +447,6 @@ namespace RobloxFiles.Enums
|
|||||||
Follow
|
Follow
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DEPRECATED_DebuggerDataModelPreference
|
|
||||||
{
|
|
||||||
Server,
|
|
||||||
Client
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DataStoreRequestType
|
public enum DataStoreRequestType
|
||||||
{
|
{
|
||||||
GetAsync,
|
GetAsync,
|
||||||
@ -506,6 +524,14 @@ namespace RobloxFiles.Enums
|
|||||||
Navigation
|
Navigation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DeviceType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Desktop,
|
||||||
|
Tablet,
|
||||||
|
Phone
|
||||||
|
}
|
||||||
|
|
||||||
public enum DialogBehaviorType
|
public enum DialogBehaviorType
|
||||||
{
|
{
|
||||||
SinglePlayer,
|
SinglePlayer,
|
||||||
@ -735,7 +761,7 @@ namespace RobloxFiles.Enums
|
|||||||
|
|
||||||
public enum GraphicsMode
|
public enum GraphicsMode
|
||||||
{
|
{
|
||||||
Automatic,
|
Automatic = 1,
|
||||||
Direct3D11,
|
Direct3D11,
|
||||||
Direct3D9,
|
Direct3D9,
|
||||||
OpenGL,
|
OpenGL,
|
||||||
@ -811,6 +837,12 @@ namespace RobloxFiles.Enums
|
|||||||
Localization = 24
|
Localization = 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum HumanoidCollisionType
|
||||||
|
{
|
||||||
|
OuterBox,
|
||||||
|
InnerBox
|
||||||
|
}
|
||||||
|
|
||||||
public enum HumanoidDisplayDistanceType
|
public enum HumanoidDisplayDistanceType
|
||||||
{
|
{
|
||||||
Viewer,
|
Viewer,
|
||||||
@ -875,6 +907,13 @@ namespace RobloxFiles.Enums
|
|||||||
Float
|
Float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum InlineAlignment
|
||||||
|
{
|
||||||
|
Bottom,
|
||||||
|
Center,
|
||||||
|
Top
|
||||||
|
}
|
||||||
|
|
||||||
public enum InputType
|
public enum InputType
|
||||||
{
|
{
|
||||||
NoInput,
|
NoInput,
|
||||||
@ -891,7 +930,7 @@ namespace RobloxFiles.Enums
|
|||||||
|
|
||||||
public enum JointType
|
public enum JointType
|
||||||
{
|
{
|
||||||
Weld,
|
Weld = 1,
|
||||||
Snap = 3,
|
Snap = 3,
|
||||||
Rotate = 7,
|
Rotate = 7,
|
||||||
RotateP,
|
RotateP,
|
||||||
@ -1169,6 +1208,13 @@ namespace RobloxFiles.Enums
|
|||||||
Default
|
Default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum LanguagePreference
|
||||||
|
{
|
||||||
|
SystemDefault,
|
||||||
|
English,
|
||||||
|
SimplifiedChinese
|
||||||
|
}
|
||||||
|
|
||||||
public enum LeftRight
|
public enum LeftRight
|
||||||
{
|
{
|
||||||
Left,
|
Left,
|
||||||
@ -1244,6 +1290,7 @@ namespace RobloxFiles.Enums
|
|||||||
Ice = 1536,
|
Ice = 1536,
|
||||||
Glacier = 1552,
|
Glacier = 1552,
|
||||||
Glass = 1568,
|
Glass = 1568,
|
||||||
|
ForceField = 1584,
|
||||||
Air = 1792,
|
Air = 1792,
|
||||||
Water = 2048
|
Water = 2048
|
||||||
}
|
}
|
||||||
@ -1253,7 +1300,8 @@ namespace RobloxFiles.Enums
|
|||||||
None,
|
None,
|
||||||
BuildersClub,
|
BuildersClub,
|
||||||
TurboBuildersClub,
|
TurboBuildersClub,
|
||||||
OutrageousBuildersClub
|
OutrageousBuildersClub,
|
||||||
|
Premium
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MeshType
|
public enum MeshType
|
||||||
@ -1619,7 +1667,7 @@ namespace RobloxFiles.Enums
|
|||||||
|
|
||||||
public enum ScrollingDirection
|
public enum ScrollingDirection
|
||||||
{
|
{
|
||||||
X,
|
X = 1,
|
||||||
Y,
|
Y,
|
||||||
XY = 4
|
XY = 4
|
||||||
}
|
}
|
||||||
@ -1688,6 +1736,13 @@ namespace RobloxFiles.Enums
|
|||||||
Confusion
|
Confusion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum StreamingPauseMode
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Disabled,
|
||||||
|
ClientPhysicsPause
|
||||||
|
}
|
||||||
|
|
||||||
public enum StudioStyleGuideColor
|
public enum StudioStyleGuideColor
|
||||||
{
|
{
|
||||||
MainBackground,
|
MainBackground,
|
||||||
@ -1773,7 +1828,15 @@ namespace RobloxFiles.Enums
|
|||||||
CheckedFieldIndicator,
|
CheckedFieldIndicator,
|
||||||
HeaderSection,
|
HeaderSection,
|
||||||
Midlight,
|
Midlight,
|
||||||
StatusBar
|
StatusBar,
|
||||||
|
DialogButton,
|
||||||
|
DialogButtonText,
|
||||||
|
DialogButtonBorder,
|
||||||
|
DialogMainButton,
|
||||||
|
DialogMainButtonText,
|
||||||
|
Merge3HighlightOriginal,
|
||||||
|
Merge3HighlightMine,
|
||||||
|
Merge3HighlightTheirs
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StudioStyleGuideModifier
|
public enum StudioStyleGuideModifier
|
||||||
@ -1800,6 +1863,12 @@ namespace RobloxFiles.Enums
|
|||||||
Motor
|
Motor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SurfaceGuiSizingMode
|
||||||
|
{
|
||||||
|
FixedSize,
|
||||||
|
PixelsPerStud
|
||||||
|
}
|
||||||
|
|
||||||
public enum SurfaceType
|
public enum SurfaceType
|
||||||
{
|
{
|
||||||
Smooth,
|
Smooth,
|
||||||
@ -1832,7 +1901,9 @@ namespace RobloxFiles.Enums
|
|||||||
public enum Technology
|
public enum Technology
|
||||||
{
|
{
|
||||||
Legacy,
|
Legacy,
|
||||||
Voxel
|
Voxel,
|
||||||
|
Compatibility,
|
||||||
|
ShadowMap
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TeleportResult
|
public enum TeleportResult
|
||||||
@ -1865,7 +1936,7 @@ namespace RobloxFiles.Enums
|
|||||||
|
|
||||||
public enum TextFilterContext
|
public enum TextFilterContext
|
||||||
{
|
{
|
||||||
PublicChat,
|
PublicChat = 1,
|
||||||
PrivateChat
|
PrivateChat
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2038,6 +2109,7 @@ namespace RobloxFiles.Enums
|
|||||||
Gamepad7,
|
Gamepad7,
|
||||||
Gamepad8,
|
Gamepad8,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
InputMethod,
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
BIN
Plugins/GenerateApiDump.rbxm
Normal file
BIN
Plugins/GenerateApiDump.rbxm
Normal file
Binary file not shown.
607
Plugins/GenerateApiDump/ApiPlugin.server.lua
Normal file
607
Plugins/GenerateApiDump/ApiPlugin.server.lua
Normal file
@ -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
|
273
Plugins/GenerateApiDump/Formatting.lua
Normal file
273
Plugins/GenerateApiDump/Formatting.lua
Normal file
@ -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
|
674
Plugins/GenerateApiDump/PropertyPatches.lua
Normal file
674
Plugins/GenerateApiDump/PropertyPatches.lua
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,6 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using RobloxFiles.BinaryFormat;
|
|
||||||
using RobloxFiles.XmlFormat;
|
|
||||||
|
|
||||||
namespace RobloxFiles
|
namespace RobloxFiles
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -71,9 +71,14 @@
|
|||||||
<Compile Include="BinaryFormat\Chunks\SSTR.cs" />
|
<Compile Include="BinaryFormat\Chunks\SSTR.cs" />
|
||||||
<Compile Include="BinaryFormat\IO\BinaryFileReader.cs" />
|
<Compile Include="BinaryFormat\IO\BinaryFileReader.cs" />
|
||||||
<Compile Include="BinaryFormat\IO\BinaryFileWriter.cs" />
|
<Compile Include="BinaryFormat\IO\BinaryFileWriter.cs" />
|
||||||
|
<Compile Include="DataTypes\Color3uint8.cs" />
|
||||||
|
<Compile Include="DataTypes\ProtectedString.cs" />
|
||||||
|
<Compile Include="DataTypes\Content.cs" />
|
||||||
|
<Compile Include="DataTypes\SharedString.cs" />
|
||||||
<Compile Include="Interfaces\IBinaryFileChunk.cs" />
|
<Compile Include="Interfaces\IBinaryFileChunk.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Tree\Enums.cs" />
|
<Compile Include="Generated\Classes.cs" />
|
||||||
|
<Compile Include="Generated\Enums.cs" />
|
||||||
<Compile Include="Tree\Property.cs" />
|
<Compile Include="Tree\Property.cs" />
|
||||||
<Compile Include="Tree\Instance.cs" />
|
<Compile Include="Tree\Instance.cs" />
|
||||||
<Compile Include="RobloxFile.cs" />
|
<Compile Include="RobloxFile.cs" />
|
||||||
@ -89,7 +94,6 @@
|
|||||||
<Compile Include="DataTypes\NumberSequenceKeypoint.cs" />
|
<Compile Include="DataTypes\NumberSequenceKeypoint.cs" />
|
||||||
<Compile Include="DataTypes\PhysicalProperties.cs" />
|
<Compile Include="DataTypes\PhysicalProperties.cs" />
|
||||||
<Compile Include="DataTypes\Ray.cs" />
|
<Compile Include="DataTypes\Ray.cs" />
|
||||||
<Compile Include="DataTypes\Region3int16.cs" />
|
|
||||||
<Compile Include="Interfaces\IXmlPropertyToken.cs" />
|
<Compile Include="Interfaces\IXmlPropertyToken.cs" />
|
||||||
<Compile Include="Utility\BrickColors.cs" />
|
<Compile Include="Utility\BrickColors.cs" />
|
||||||
<Compile Include="DataTypes\Vector3int16.cs" />
|
<Compile Include="DataTypes\Vector3int16.cs" />
|
||||||
@ -100,41 +104,43 @@
|
|||||||
<Compile Include="DataTypes\Vector2.cs" />
|
<Compile Include="DataTypes\Vector2.cs" />
|
||||||
<Compile Include="DataTypes\Vector3.cs" />
|
<Compile Include="DataTypes\Vector3.cs" />
|
||||||
<Compile Include="Utility\Formatting.cs" />
|
<Compile Include="Utility\Formatting.cs" />
|
||||||
|
<Compile Include="Utility\FontUtility.cs" />
|
||||||
<Compile Include="Utility\MaterialInfo.cs" />
|
<Compile Include="Utility\MaterialInfo.cs" />
|
||||||
<Compile Include="Utility\Quaternion.cs" />
|
<Compile Include="DataTypes\Quaternion.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\SharedString.cs" />
|
<Compile Include="XmlFormat\Tokens\SharedString.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector3int16.cs" />
|
<Compile Include="XmlFormat\Tokens\ProtectedString.cs" />
|
||||||
|
<Compile Include="XmlFormat\Tokens\Vector3int16.cs" />
|
||||||
<Compile Include="XmlFormat\IO\XmlFileWriter.cs" />
|
<Compile Include="XmlFormat\IO\XmlFileWriter.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\XmlPropertyTokens.cs" />
|
<Compile Include="XmlFormat\XmlPropertyTokens.cs" />
|
||||||
<Compile Include="XmlFormat\IO\XmlFileReader.cs" />
|
<Compile Include="XmlFormat\IO\XmlFileReader.cs" />
|
||||||
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
|
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Axes.cs" />
|
<Compile Include="XmlFormat\Tokens\Axes.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\BinaryString.cs" />
|
<Compile Include="XmlFormat\Tokens\BinaryString.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Boolean.cs" />
|
<Compile Include="XmlFormat\Tokens\Boolean.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\BrickColor.cs" />
|
<Compile Include="XmlFormat\Tokens\BrickColor.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\CFrame.cs" />
|
<Compile Include="XmlFormat\Tokens\CFrame.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Content.cs" />
|
<Compile Include="XmlFormat\Tokens\Content.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Color3.cs" />
|
<Compile Include="XmlFormat\Tokens\Color3.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Color3uint8.cs" />
|
<Compile Include="XmlFormat\Tokens\Color3uint8.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\ColorSequence.cs" />
|
<Compile Include="XmlFormat\Tokens\ColorSequence.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Double.cs" />
|
<Compile Include="XmlFormat\Tokens\Double.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Enum.cs" />
|
<Compile Include="XmlFormat\Tokens\Enum.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Faces.cs" />
|
<Compile Include="XmlFormat\Tokens\Faces.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Float.cs" />
|
<Compile Include="XmlFormat\Tokens\Float.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Int.cs" />
|
<Compile Include="XmlFormat\Tokens\Int.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Int64.cs" />
|
<Compile Include="XmlFormat\Tokens\Int64.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\NumberRange.cs" />
|
<Compile Include="XmlFormat\Tokens\NumberRange.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\NumberSequence.cs" />
|
<Compile Include="XmlFormat\Tokens\NumberSequence.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\PhysicalProperties.cs" />
|
<Compile Include="XmlFormat\Tokens\PhysicalProperties.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Ray.cs" />
|
<Compile Include="XmlFormat\Tokens\Ray.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Rect.cs" />
|
<Compile Include="XmlFormat\Tokens\Rect.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Ref.cs" />
|
<Compile Include="XmlFormat\Tokens\Ref.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\String.cs" />
|
<Compile Include="XmlFormat\Tokens\String.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\UDim.cs" />
|
<Compile Include="XmlFormat\Tokens\UDim.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\UDim2.cs" />
|
<Compile Include="XmlFormat\Tokens\UDim2.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector2.cs" />
|
<Compile Include="XmlFormat\Tokens\Vector2.cs" />
|
||||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector3.cs" />
|
<Compile Include="XmlFormat\Tokens\Vector3.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
|
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
|
||||||
|
441
Tree/Instance.cs
441
Tree/Instance.cs
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace RobloxFiles
|
namespace RobloxFiles
|
||||||
{
|
{
|
||||||
@ -11,15 +13,31 @@ namespace RobloxFiles
|
|||||||
|
|
||||||
public class Instance
|
public class Instance
|
||||||
{
|
{
|
||||||
|
public Instance()
|
||||||
|
{
|
||||||
|
Name = ClassName;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>The ClassName of this Instance.</summary>
|
/// <summary>The ClassName of this Instance.</summary>
|
||||||
public string ClassName;
|
public string ClassName => GetType().Name;
|
||||||
|
|
||||||
|
/// <summary>Internal list of Properties that are under this Instance.</summary>
|
||||||
|
private Dictionary<string, Property> props = new Dictionary<string, Property>();
|
||||||
|
|
||||||
/// <summary>A list of properties that are defined under this Instance.</summary>
|
/// <summary>A list of properties that are defined under this Instance.</summary>
|
||||||
private Dictionary<string, Property> props = new Dictionary<string, Property>();
|
|
||||||
public IReadOnlyDictionary<string, Property> Properties => props;
|
public IReadOnlyDictionary<string, Property> Properties => props;
|
||||||
|
|
||||||
protected List<Instance> Children = new List<Instance>();
|
/// <summary>The raw list of children for this Instance.</summary>
|
||||||
private Instance parent;
|
internal List<Instance> Children = new List<Instance>();
|
||||||
|
|
||||||
|
/// <summary>Raw value of the Instance's parent.</summary>
|
||||||
|
private Instance RawParent;
|
||||||
|
|
||||||
|
/// <summary>The name of this Instance.</summary>
|
||||||
|
public string Name;
|
||||||
|
|
||||||
|
/// <summary>Indicates whether this Instance should be serialized.</summary>
|
||||||
|
public bool Archivable = true;
|
||||||
|
|
||||||
/// <summary>The name of this Instance, if a Name property is defined.</summary>
|
/// <summary>The name of this Instance, if a Name property is defined.</summary>
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
@ -30,14 +48,56 @@ namespace RobloxFiles
|
|||||||
/// <summary>Indicates whether the parent of this object is locked.</summary>
|
/// <summary>Indicates whether the parent of this object is locked.</summary>
|
||||||
public bool ParentLocked { get; internal set; }
|
public bool ParentLocked { get; internal set; }
|
||||||
|
|
||||||
/// <summary>Indicates whether this Instance is marked as a Service in the binary file format.</summary>
|
/// <summary>Indicates whether this Instance is a Service.</summary>
|
||||||
public bool IsService { get; internal set; }
|
public bool IsService { get; internal set; }
|
||||||
|
|
||||||
/// <summary>If this instance is a service, this indicates whether the service should be loaded via GetService when Roblox loads the place file.</summary>
|
/// <summary>Raw list of CollectionService tags assigned to this Instance.</summary>
|
||||||
public bool IsRootedService { get; internal set; }
|
private List<string> RawTags = new List<string>();
|
||||||
|
|
||||||
/// <summary>Indicates whether this object should be serialized.</summary>
|
/// <summary>A list of CollectionService tags assigned to this Instance.</summary>
|
||||||
public bool Archivable = true;
|
public List<string> Tags => RawTags;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal format of the Instance's CollectionService tags.
|
||||||
|
/// Property objects will look to this member for serializing the Tags property.
|
||||||
|
/// </summary>
|
||||||
|
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<byte> buffer = new List<byte>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary>
|
/// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary>
|
||||||
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
|
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
|
||||||
@ -61,26 +121,20 @@ namespace RobloxFiles
|
|||||||
return ancestor.IsAncestorOf(this);
|
return ancestor.IsAncestorOf(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name
|
/// <summary>
|
||||||
|
/// Returns true if the provided instance inherits from the provided instance type.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsA<T>() where T : Instance
|
||||||
{
|
{
|
||||||
get
|
Type myType = GetType();
|
||||||
{
|
Type classType = typeof(T);
|
||||||
Property propName = GetProperty("Name");
|
return classType.IsAssignableFrom(myType);
|
||||||
|
|
||||||
if (propName == null)
|
|
||||||
SetProperty("Name", "Instance");
|
|
||||||
|
|
||||||
return propName.Value.ToString();
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
SetProperty("Name", value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The parent of this Instance, or null if the instance is the root of a tree.<para/>
|
/// The parent of this Instance, or null if the instance is the root of a tree.<para/>
|
||||||
/// Setting the value of this property will throw an exception if:<para/>
|
/// Setting the value of this property will throw an exception if:<para/>
|
||||||
|
/// - The parent is currently locked.<para/>
|
||||||
/// - The value is set to itself.<para/>
|
/// - The value is set to itself.<para/>
|
||||||
/// - The value is a descendant of the Instance.
|
/// - The value is a descendant of the Instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -88,7 +142,7 @@ namespace RobloxFiles
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return parent;
|
return RawParent;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@ -101,11 +155,10 @@ namespace RobloxFiles
|
|||||||
if (Parent == this)
|
if (Parent == this)
|
||||||
throw new Exception("Attempt to set parent to self.");
|
throw new Exception("Attempt to set parent to self.");
|
||||||
|
|
||||||
if (parent != null)
|
RawParent?.Children.Remove(this);
|
||||||
parent.Children.Remove(this);
|
value?.Children.Add(this);
|
||||||
|
|
||||||
value.Children.Add(this);
|
RawParent = value;
|
||||||
parent = value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,10 +196,14 @@ namespace RobloxFiles
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The Name of the Instance to find.</param>
|
/// <param name="name">The Name of the Instance to find.</param>
|
||||||
/// <param name="recursive">Indicates if we should search descendants as well.</param>
|
/// <param name="recursive">Indicates if we should search descendants as well.</param>
|
||||||
public Instance FindFirstChild(string name, bool recursive = false)
|
public T FindFirstChild<T>(string name, bool recursive = false) where T : Instance
|
||||||
{
|
{
|
||||||
Instance result = null;
|
T result = null;
|
||||||
var query = Children.Where((child) => name == child.Name);
|
|
||||||
|
var query = Children
|
||||||
|
.Where(child => child is T)
|
||||||
|
.Where(child => name == child.Name)
|
||||||
|
.Cast<T>();
|
||||||
|
|
||||||
if (query.Count() > 0)
|
if (query.Count() > 0)
|
||||||
{
|
{
|
||||||
@ -156,7 +213,7 @@ namespace RobloxFiles
|
|||||||
{
|
{
|
||||||
foreach (Instance child in Children)
|
foreach (Instance child in Children)
|
||||||
{
|
{
|
||||||
Instance found = child.FindFirstChild(name, true);
|
T found = child.FindFirstChild<T>(name, true);
|
||||||
|
|
||||||
if (found != null)
|
if (found != null)
|
||||||
{
|
{
|
||||||
@ -169,6 +226,37 @@ namespace RobloxFiles
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the first child of this Instance whose Name is the provided string name.
|
||||||
|
/// If the instance is not found, this returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The Name of the Instance to find.</param>
|
||||||
|
/// <param name="recursive">Indicates if we should search descendants as well.</param>
|
||||||
|
public Instance FindFirstChild(string name, bool recursive = false)
|
||||||
|
{
|
||||||
|
return FindFirstChild<Instance>(name, recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the first ancestor of this Instance whose Name is the provided string name.
|
||||||
|
/// If the instance is not found, this returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The Name of the Instance to find.</param>
|
||||||
|
public T FindFirstAncestor<T>(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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the first ancestor of this Instance whose Name is the provided string name.
|
/// Returns the first ancestor of this Instance whose Name is the provided string name.
|
||||||
/// If the instance is not found, this returns null.
|
/// If the instance is not found, this returns null.
|
||||||
@ -176,17 +264,7 @@ namespace RobloxFiles
|
|||||||
/// <param name="name">The Name of the Instance to find.</param>
|
/// <param name="name">The Name of the Instance to find.</param>
|
||||||
public Instance FindFirstAncestor(string name)
|
public Instance FindFirstAncestor(string name)
|
||||||
{
|
{
|
||||||
Instance ancestor = Parent;
|
return FindFirstAncestor<Instance>(name);
|
||||||
|
|
||||||
while (ancestor != null)
|
|
||||||
{
|
|
||||||
if (ancestor.Name == name)
|
|
||||||
break;
|
|
||||||
|
|
||||||
ancestor = ancestor.Parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ancestor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -194,18 +272,45 @@ namespace RobloxFiles
|
|||||||
/// If the instance is not found, this returns null.
|
/// If the instance is not found, this returns null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The Name of the Instance to find.</param>
|
/// <param name="name">The Name of the Instance to find.</param>
|
||||||
public Instance FindFirstAncestorOfClass(string className)
|
public T FindFirstAncestorOfClass<T>() where T : Instance
|
||||||
{
|
{
|
||||||
|
Type classType = typeof(T);
|
||||||
|
string className = classType.Name;
|
||||||
|
|
||||||
Instance ancestor = Parent;
|
Instance ancestor = Parent;
|
||||||
|
|
||||||
while (ancestor != null)
|
while (ancestor != null)
|
||||||
{
|
{
|
||||||
if (ancestor.ClassName == className)
|
if (ancestor is T)
|
||||||
break;
|
return (T)ancestor;
|
||||||
|
|
||||||
ancestor = ancestor.Parent;
|
ancestor = ancestor.Parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the first ancestor of this Instance which derives from the provided type T.
|
||||||
|
/// If the instance is not found, this returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The Name of the Instance to find.</param>
|
||||||
|
public T FindFirstAncestorWhichIsA<T>() where T : Instance
|
||||||
|
{
|
||||||
|
T ancestor = null;
|
||||||
|
Instance check = Parent;
|
||||||
|
|
||||||
|
while (check != null)
|
||||||
|
{
|
||||||
|
if (check.IsA<T>())
|
||||||
|
{
|
||||||
|
ancestor = (T)check;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
check = check.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
return ancestor;
|
return ancestor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,13 +319,65 @@ namespace RobloxFiles
|
|||||||
/// If the instance is not found, this returns null.
|
/// If the instance is not found, this returns null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="className">The ClassName of the Instance to find.</param>
|
/// <param name="className">The ClassName of the Instance to find.</param>
|
||||||
public Instance FindFirstChildOfClass(string className, bool recursive = false)
|
public T FindFirstChildOfClass<T>(bool recursive = false) where T : Instance
|
||||||
{
|
{
|
||||||
Instance result = null;
|
var query = Children
|
||||||
var query = Children.Where((child) => className == child.ClassName);
|
.Where(child => child is T)
|
||||||
|
.Cast<T>();
|
||||||
|
|
||||||
|
T result = null;
|
||||||
|
|
||||||
if (query.Count() > 0)
|
if (query.Count() > 0)
|
||||||
|
{
|
||||||
result = query.First();
|
result = query.First();
|
||||||
|
}
|
||||||
|
else if (recursive)
|
||||||
|
{
|
||||||
|
foreach (Instance child in Children)
|
||||||
|
{
|
||||||
|
T found = child.FindFirstChildOfClass<T>(true);
|
||||||
|
|
||||||
|
if (found != null)
|
||||||
|
{
|
||||||
|
result = found;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the first child of this Instance which derives from the provided type T.
|
||||||
|
/// If the instance is not found, this returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="recursive">Whether this should search descendants as well.</param>
|
||||||
|
public T FindFirstChildWhichIsA<T>(bool recursive = false) where T : Instance
|
||||||
|
{
|
||||||
|
var query = Children
|
||||||
|
.Where(child => child.IsA<T>())
|
||||||
|
.Cast<T>();
|
||||||
|
|
||||||
|
T result = null;
|
||||||
|
|
||||||
|
if (query.Count() > 0)
|
||||||
|
{
|
||||||
|
result = query.First();
|
||||||
|
}
|
||||||
|
else if (recursive)
|
||||||
|
{
|
||||||
|
foreach (Instance child in Children)
|
||||||
|
{
|
||||||
|
T found = child.FindFirstChildWhichIsA<T>(true);
|
||||||
|
|
||||||
|
if (found != null)
|
||||||
|
{
|
||||||
|
result = found;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -243,123 +400,18 @@ namespace RobloxFiles
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Property GetProperty(string name)
|
public Property GetProperty(string name)
|
||||||
{
|
{
|
||||||
Property result = null;
|
Property result = null;
|
||||||
|
|
||||||
if (Properties.ContainsKey(name))
|
if (props.ContainsKey(name))
|
||||||
result = Properties[name];
|
result = props[name];
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks for a property with the specified property name, and returns its value as an object.
|
|
||||||
/// <para/>The resulting value may be null if the property is not serialized.
|
|
||||||
/// <para/>You can use the templated ReadProperty overload to fetch it as a specific type with a default value provided.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">The name of the property to be fetched from this Instance.</param>
|
|
||||||
/// <returns>An object reference to the value of the specified property, if it exists.</returns>
|
|
||||||
public object ReadProperty(string propertyName)
|
|
||||||
{
|
|
||||||
Property property = GetProperty(propertyName);
|
|
||||||
return property?.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Looks for a property with the specified property name, and returns it as the specified type.<para/>
|
|
||||||
/// If it cannot be converted, the provided nullFallback value will be returned instead.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The value type to convert to when finding the specified property name.</typeparam>
|
|
||||||
/// <param name="propertyName">The name of the property to be fetched from this Instance.</param>
|
|
||||||
/// <param name="nullFallback">A fallback value to be returned if casting to T fails, or the property is not found.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public T ReadProperty<T>(string propertyName, T nullFallback)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
object result = ReadProperty(propertyName);
|
|
||||||
return (T)result;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return nullFallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.<para/>
|
|
||||||
/// Returns true if the property was found and its value was casted to the referenced outValue.<para/>
|
|
||||||
/// If it returns false, the outValue will not have its value set.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The value type to convert to when finding the specified property name.</typeparam>
|
|
||||||
/// <param name="propertyName">The name of the property to be fetched from this Instance.</param>
|
|
||||||
/// <param name="outValue">The value to write to if the property can be casted to T correctly.</param>
|
|
||||||
public bool TryReadProperty<T>(string propertyName, ref T outValue)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
object result = ReadProperty(propertyName);
|
|
||||||
outValue = (T)result;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a property by reference to this Instance's property list.
|
/// Adds a property by reference to this Instance's property list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -368,16 +420,8 @@ namespace RobloxFiles
|
|||||||
{
|
{
|
||||||
prop.Instance = this;
|
prop.Instance = this;
|
||||||
|
|
||||||
if (prop.Name == "Name")
|
if (props.ContainsKey(prop.Name))
|
||||||
{
|
props.Remove(prop.Name);
|
||||||
Property nameProp = GetProperty("Name");
|
|
||||||
|
|
||||||
if (nameProp != null)
|
|
||||||
{
|
|
||||||
nameProp.Value = prop.Value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
props.Add(prop.Name, prop);
|
props.Add(prop.Name, prop);
|
||||||
}
|
}
|
||||||
@ -387,14 +431,69 @@ namespace RobloxFiles
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the property to be removed.</param>
|
/// <param name="name">The name of the property to be removed.</param>
|
||||||
/// <returns>True if a property with the provided name was removed.</returns>
|
/// <returns>True if a property with the provided name was removed.</returns>
|
||||||
public bool RemoveProperty(string name)
|
internal bool RemoveProperty(string name)
|
||||||
{
|
{
|
||||||
Property prop = GetProperty(name);
|
if (props.ContainsKey(name))
|
||||||
|
{
|
||||||
if (prop != null)
|
Property prop = Properties[name];
|
||||||
prop.Instance = null;
|
prop.Instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
return props.Remove(name);
|
return props.Remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that all serializable properties of this Instance have
|
||||||
|
/// a registered Property object with the correct PropertyType.
|
||||||
|
/// </summary>
|
||||||
|
internal IReadOnlyDictionary<string, Property> 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<ObsoleteAttribute>() != 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
130
Tree/Property.cs
130
Tree/Property.cs
@ -1,8 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
using RobloxFiles.BinaryFormat;
|
using RobloxFiles.BinaryFormat;
|
||||||
using RobloxFiles.BinaryFormat.Chunks;
|
using RobloxFiles.BinaryFormat.Chunks;
|
||||||
|
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
using RobloxFiles.Utility;
|
||||||
|
|
||||||
namespace RobloxFiles
|
namespace RobloxFiles
|
||||||
{
|
{
|
||||||
public enum PropertyType
|
public enum PropertyType
|
||||||
@ -22,8 +27,7 @@ namespace RobloxFiles
|
|||||||
Color3,
|
Color3,
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
Vector2int16,
|
CFrame = 16,
|
||||||
CFrame,
|
|
||||||
Quaternion,
|
Quaternion,
|
||||||
Enum,
|
Enum,
|
||||||
Ref,
|
Ref,
|
||||||
@ -48,11 +52,57 @@ namespace RobloxFiles
|
|||||||
public string XmlToken = "";
|
public string XmlToken = "";
|
||||||
public byte[] RawBuffer;
|
public byte[] RawBuffer;
|
||||||
|
|
||||||
internal BinaryRobloxFileWriter CurrentWriter;
|
|
||||||
internal object RawValue;
|
internal object RawValue;
|
||||||
|
internal BinaryRobloxFileWriter CurrentWriter;
|
||||||
|
|
||||||
|
internal static BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.IgnoreCase;
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<Type, PropertyType> Types = new Dictionary<Type, PropertyType>()
|
||||||
|
{
|
||||||
|
{ 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()
|
private void ImproviseRawBuffer()
|
||||||
{
|
{
|
||||||
|
if (RawValue is byte[])
|
||||||
|
{
|
||||||
|
RawBuffer = RawValue as byte[];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (RawValue is SharedString)
|
||||||
|
{
|
||||||
|
var sharedString = CastValue<SharedString>();
|
||||||
|
RawBuffer = Convert.FromBase64String(sharedString.MD5_Key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (Type)
|
switch (Type)
|
||||||
{
|
{
|
||||||
case PropertyType.Int:
|
case PropertyType.Int:
|
||||||
@ -70,21 +120,85 @@ namespace RobloxFiles
|
|||||||
case PropertyType.Double:
|
case PropertyType.Double:
|
||||||
RawBuffer = BitConverter.GetBytes((double)Value);
|
RawBuffer = BitConverter.GetBytes((double)Value);
|
||||||
break;
|
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
|
public object Value
|
||||||
{
|
{
|
||||||
get
|
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;
|
return RawValue;
|
||||||
}
|
}
|
||||||
set
|
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;
|
RawValue = value;
|
||||||
RawBuffer = null;
|
RawBuffer = null;
|
||||||
|
|
||||||
@ -95,12 +209,10 @@ namespace RobloxFiles
|
|||||||
public bool HasRawBuffer
|
public bool HasRawBuffer
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
|
||||||
if (RawBuffer == null && Value != null)
|
|
||||||
{
|
{
|
||||||
// Improvise what the buffer should be if this is a primitive.
|
// Improvise what the buffer should be if this is a primitive.
|
||||||
|
if (RawBuffer == null && Value != null)
|
||||||
ImproviseRawBuffer();
|
ImproviseRawBuffer();
|
||||||
}
|
|
||||||
|
|
||||||
return (RawBuffer != null);
|
return (RawBuffer != null);
|
||||||
}
|
}
|
||||||
|
66
Utility/FontUtility.cs
Normal file
66
Utility/FontUtility.cs
Normal file
@ -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<int, FontSize> FontSizes = new Dictionary<int, FontSize>()
|
||||||
|
{
|
||||||
|
{ 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<int, FontSize> IntToFontSize = new Dictionary<int, FontSize>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System;
|
||||||
using System.Linq;
|
using System.Globalization;
|
||||||
|
|
||||||
// This global class defines extension methods to numeric types
|
|
||||||
// where I don't want system globalization to come into play.
|
|
||||||
|
|
||||||
internal static class Formatting
|
internal static class Formatting
|
||||||
{
|
{
|
||||||
@ -105,4 +102,14 @@ internal static class Formatting
|
|||||||
{
|
{
|
||||||
return int.Parse(s, invariant);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ namespace RobloxFiles.Utility
|
|||||||
{Material.DiamondPlate, 7.85f},
|
{Material.DiamondPlate, 7.85f},
|
||||||
{Material.Fabric, 0.70f},
|
{Material.Fabric, 0.70f},
|
||||||
{Material.Foil, 2.70f},
|
{Material.Foil, 2.70f},
|
||||||
|
{Material.ForceField, 2.40f},
|
||||||
{Material.Glacier, 0.92f},
|
{Material.Glacier, 0.92f},
|
||||||
{Material.Glass, 2.40f},
|
{Material.Glass, 2.40f},
|
||||||
{Material.Granite, 2.69f},
|
{Material.Granite, 2.69f},
|
||||||
@ -68,6 +69,7 @@ namespace RobloxFiles.Utility
|
|||||||
{Material.DiamondPlate, 0.25f},
|
{Material.DiamondPlate, 0.25f},
|
||||||
{Material.Fabric, 0.05f},
|
{Material.Fabric, 0.05f},
|
||||||
{Material.Foil, 0.25f},
|
{Material.Foil, 0.25f},
|
||||||
|
{Material.ForceField, 0.20f},
|
||||||
{Material.Glacier, 0.15f},
|
{Material.Glacier, 0.15f},
|
||||||
{Material.Glass, 0.20f},
|
{Material.Glass, 0.20f},
|
||||||
{Material.Granite, 0.20f},
|
{Material.Granite, 0.20f},
|
||||||
@ -111,6 +113,7 @@ namespace RobloxFiles.Utility
|
|||||||
{Material.DiamondPlate, 0.35f},
|
{Material.DiamondPlate, 0.35f},
|
||||||
{Material.Fabric, 0.35f},
|
{Material.Fabric, 0.35f},
|
||||||
{Material.Foil, 0.40f},
|
{Material.Foil, 0.40f},
|
||||||
|
{Material.ForceField, 0.25f},
|
||||||
{Material.Glacier, 0.05f},
|
{Material.Glacier, 0.05f},
|
||||||
{Material.Glass, 0.25f},
|
{Material.Glass, 0.25f},
|
||||||
{Material.Granite, 0.40f},
|
{Material.Granite, 0.40f},
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat
|
namespace RobloxFiles.XmlFormat
|
||||||
{
|
{
|
||||||
public static class XmlRobloxFileReader
|
public static class XmlRobloxFileReader
|
||||||
@ -35,11 +37,35 @@ namespace RobloxFiles.XmlFormat
|
|||||||
string key = md5Node.InnerText;
|
string key = md5Node.InnerText;
|
||||||
string value = sharedString.InnerText.Replace("\n", "");
|
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)
|
public static void ReadProperties(Instance instance, XmlNode propsNode)
|
||||||
{
|
{
|
||||||
var error = createErrorHandler("ReadProperties");
|
var error = createErrorHandler("ReadProperties");
|
||||||
@ -53,7 +79,12 @@ namespace RobloxFiles.XmlFormat
|
|||||||
XmlNode propName = propNode.Attributes.GetNamedItem("name");
|
XmlNode propName = propNode.Attributes.GetNamedItem("name");
|
||||||
|
|
||||||
if (propName == null)
|
if (propName == null)
|
||||||
|
{
|
||||||
|
if (propNode.Name == "Item")
|
||||||
|
continue;
|
||||||
|
|
||||||
throw error("Got a property node without a 'name' attribute!");
|
throw error("Got a property node without a 'name' attribute!");
|
||||||
|
}
|
||||||
|
|
||||||
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
|
IXmlPropertyToken tokenHandler = XmlPropertyTokens.GetHandler(propType);
|
||||||
|
|
||||||
@ -90,7 +121,11 @@ namespace RobloxFiles.XmlFormat
|
|||||||
if (classToken == null)
|
if (classToken == null)
|
||||||
throw error("Got an Item without a defined 'class' attribute!");
|
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.
|
// 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");
|
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
using RobloxFiles.XmlFormat.PropertyTokens;
|
using RobloxFiles.XmlFormat.PropertyTokens;
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat
|
namespace RobloxFiles.XmlFormat
|
||||||
@ -40,7 +39,7 @@ namespace RobloxFiles.XmlFormat
|
|||||||
foreach (Instance child in inst.GetChildren())
|
foreach (Instance child in inst.GetChildren())
|
||||||
RecordInstances(file, child);
|
RecordInstances(file, child);
|
||||||
|
|
||||||
if (inst.Referent.Length < 35)
|
if (inst.Referent == null || inst.Referent.Length < 35)
|
||||||
inst.Referent = CreateReferent();
|
inst.Referent = CreateReferent();
|
||||||
|
|
||||||
file.Instances.Add(inst.Referent, inst);
|
file.Instances.Add(inst.Referent, inst);
|
||||||
@ -102,19 +101,8 @@ namespace RobloxFiles.XmlFormat
|
|||||||
|
|
||||||
if (prop.Type == PropertyType.SharedString)
|
if (prop.Type == PropertyType.SharedString)
|
||||||
{
|
{
|
||||||
string data = prop.Value.ToString();
|
SharedString value = prop.CastValue<SharedString>();
|
||||||
byte[] buffer = Convert.FromBase64String(data);
|
file.SharedStrings.Add(value.MD5_Key);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return propNode;
|
return propNode;
|
||||||
@ -122,6 +110,9 @@ namespace RobloxFiles.XmlFormat
|
|||||||
|
|
||||||
public static XmlNode WriteInstance(Instance instance, XmlDocument doc, XmlRobloxFile file)
|
public static XmlNode WriteInstance(Instance instance, XmlDocument doc, XmlRobloxFile file)
|
||||||
{
|
{
|
||||||
|
if (!instance.Archivable)
|
||||||
|
return null;
|
||||||
|
|
||||||
XmlElement instNode = doc.CreateElement("Item");
|
XmlElement instNode = doc.CreateElement("Item");
|
||||||
instNode.SetAttribute("class", instance.ClassName);
|
instNode.SetAttribute("class", instance.ClassName);
|
||||||
instNode.SetAttribute("referent", instance.Referent);
|
instNode.SetAttribute("referent", instance.Referent);
|
||||||
@ -129,7 +120,7 @@ namespace RobloxFiles.XmlFormat
|
|||||||
XmlElement propsNode = doc.CreateElement("Properties");
|
XmlElement propsNode = doc.CreateElement("Properties");
|
||||||
instNode.AppendChild(propsNode);
|
instNode.AppendChild(propsNode);
|
||||||
|
|
||||||
var props = instance.Properties;
|
var props = instance.RefreshProperties();
|
||||||
|
|
||||||
foreach (string propName in props.Keys)
|
foreach (string propName in props.Keys)
|
||||||
{
|
{
|
||||||
@ -139,10 +130,13 @@ namespace RobloxFiles.XmlFormat
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (Instance child in instance.GetChildren())
|
foreach (Instance child in instance.GetChildren())
|
||||||
|
{
|
||||||
|
if (child.Archivable)
|
||||||
{
|
{
|
||||||
XmlNode childNode = WriteInstance(child, doc, file);
|
XmlNode childNode = WriteInstance(child, doc, file);
|
||||||
instNode.AppendChild(childNode);
|
instNode.AppendChild(childNode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return instNode;
|
return instNode;
|
||||||
}
|
}
|
||||||
@ -152,18 +146,15 @@ namespace RobloxFiles.XmlFormat
|
|||||||
XmlElement sharedStrings = doc.CreateElement("SharedStrings");
|
XmlElement sharedStrings = doc.CreateElement("SharedStrings");
|
||||||
|
|
||||||
var binaryWriter = XmlPropertyTokens.GetHandler<BinaryStringToken>();
|
var binaryWriter = XmlPropertyTokens.GetHandler<BinaryStringToken>();
|
||||||
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");
|
XmlElement sharedString = doc.CreateElement("SharedString");
|
||||||
sharedString.SetAttribute("md5", md5);
|
sharedString.SetAttribute("md5", md5);
|
||||||
|
|
||||||
string data = file.SharedStrings[md5];
|
binaryBuffer.RawBuffer = SharedString.FindRecord(md5);
|
||||||
byte[] buffer = Convert.FromBase64String(data);
|
binaryWriter.WriteProperty(binaryBuffer, doc, sharedString);
|
||||||
|
|
||||||
bufferProp.RawBuffer = buffer;
|
|
||||||
binaryWriter.WriteProperty(bufferProp, doc, sharedString);
|
|
||||||
|
|
||||||
sharedStrings.AppendChild(sharedString);
|
sharedStrings.AppendChild(sharedString);
|
||||||
}
|
}
|
||||||
|
@ -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<uint>(prop, PropertyType.Enum, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
|
||||||
{
|
|
||||||
node.InnerText = prop.Value.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Xml;
|
||||||
using System.Xml;
|
|
||||||
using RobloxFiles.DataTypes;
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||||
@ -7,25 +6,20 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
public class AxesToken : IXmlPropertyToken
|
public class AxesToken : IXmlPropertyToken
|
||||||
{
|
{
|
||||||
public string Token => "Axes";
|
public string Token => "Axes";
|
||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
public bool ReadProperty(Property prop, XmlNode token)
|
||||||
{
|
{
|
||||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Axes, token);
|
uint value;
|
||||||
|
|
||||||
if (success)
|
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||||
{
|
|
||||||
uint value = (uint)prop.Value;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Axes axes = (Axes)value;
|
Axes axes = (Axes)value;
|
||||||
prop.Value = axes;
|
prop.Value = axes;
|
||||||
}
|
|
||||||
catch
|
return true;
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
@ -33,7 +27,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
XmlElement axes = doc.CreateElement("axes");
|
XmlElement axes = doc.CreateElement("axes");
|
||||||
node.AppendChild(axes);
|
node.AppendChild(axes);
|
||||||
|
|
||||||
int value = (int)prop.Value;
|
int value = prop.CastValue<int>();
|
||||||
axes.InnerText = value.ToInvariantString();
|
axes.InnerText = value.ToInvariantString();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,8 +11,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
{
|
{
|
||||||
// BinaryStrings are encoded in base64
|
// BinaryStrings are encoded in base64
|
||||||
string base64 = token.InnerText.Replace("\n", "");
|
string base64 = token.InnerText.Replace("\n", "");
|
||||||
|
prop.Value = Convert.FromBase64String(base64);
|
||||||
prop.Type = PropertyType.String;
|
prop.Type = PropertyType.String;
|
||||||
prop.Value = base64;
|
|
||||||
|
|
||||||
byte[] buffer = Convert.FromBase64String(base64);
|
byte[] buffer = Convert.FromBase64String(base64);
|
||||||
prop.RawBuffer = buffer;
|
prop.RawBuffer = buffer;
|
@ -13,31 +13,23 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
public bool ReadProperty(Property prop, XmlNode token)
|
||||||
{
|
{
|
||||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.BrickColor, token);
|
int value;
|
||||||
|
|
||||||
if (success)
|
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||||
{
|
|
||||||
int value = (int)prop.Value;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
BrickColor brickColor = BrickColor.FromNumber(value);
|
BrickColor brickColor = BrickColor.FromNumber(value);
|
||||||
prop.XmlToken = "BrickColor";
|
prop.XmlToken = "BrickColor";
|
||||||
prop.Value = brickColor;
|
prop.Value = brickColor;
|
||||||
}
|
|
||||||
catch
|
return true;
|
||||||
{
|
|
||||||
// Invalid BrickColor Id?
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
BrickColor value = prop.Value as BrickColor;
|
BrickColor value = prop.CastValue<BrickColor>();
|
||||||
|
|
||||||
XmlElement brickColor = doc.CreateElement("int");
|
XmlElement brickColor = doc.CreateElement("int");
|
||||||
brickColor.InnerText = value.Number.ToInvariantString();
|
brickColor.InnerText = value.Number.ToInvariantString();
|
@ -46,7 +46,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
CFrame cf = prop.Value as CFrame;
|
CFrame cf = prop.CastValue<CFrame>();
|
||||||
float[] components = cf.GetComponents();
|
float[] components = cf.GetComponents();
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++)
|
for (int i = 0; i < 12; i++)
|
@ -51,14 +51,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
if (prop.Name == "Color3uint8")
|
Color3 color = prop.CastValue<Color3>();
|
||||||
{
|
|
||||||
var handler = XmlPropertyTokens.GetHandler<Color3uint8Token>();
|
|
||||||
handler.WriteProperty(prop, doc, node);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Color3 color = prop.Value as Color3;
|
|
||||||
float[] rgb = new float[3] { color.R, color.G, color.B };
|
float[] rgb = new float[3] { color.R, color.G, color.B };
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
@ -73,5 +66,4 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Xml;
|
||||||
using System.Xml;
|
|
||||||
using RobloxFiles.DataTypes;
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||||
@ -10,29 +9,30 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
public bool ReadProperty(Property prop, XmlNode token)
|
||||||
{
|
{
|
||||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(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 r = (value >> 16) & 0xFF;
|
||||||
uint g = (value >> 8) & 0xFF;
|
uint g = (value >> 8) & 0xFF;
|
||||||
uint b = value & 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)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
Color3 color = prop.Value as Color3;
|
Color3uint8 color = prop.CastValue<Color3uint8>();
|
||||||
|
|
||||||
uint r = (uint)(color.R * 256);
|
uint r = color.R,
|
||||||
uint g = (uint)(color.G * 256);
|
g = color.G,
|
||||||
uint b = (uint)(color.B * 256);
|
b = color.B;
|
||||||
|
|
||||||
uint rgb = (255u << 24) | (r << 16) | (g << 8) | b;
|
uint rgb = (255u << 24) | (r << 16) | (g << 8) | b;
|
||||||
node.InnerText = rgb.ToString();
|
node.InnerText = rgb.ToString();
|
@ -47,7 +47,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToString() + ' ';
|
ColorSequence value = prop.CastValue<ColorSequence>();
|
||||||
|
node.InnerText = value.ToString() + ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||||
{
|
{
|
||||||
public class ContentToken : IXmlPropertyToken
|
public class ContentToken : IXmlPropertyToken
|
||||||
@ -9,9 +11,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
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.Type = PropertyType.String;
|
||||||
prop.Value = content;
|
|
||||||
|
|
||||||
if (token.HasChildNodes)
|
if (token.HasChildNodes)
|
||||||
{
|
{
|
||||||
@ -23,12 +25,12 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Roblox technically doesn't support this anymore, but load it anyway :P
|
// 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;
|
prop.RawBuffer = buffer;
|
||||||
}
|
}
|
||||||
catch
|
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)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
string content = prop.Value.ToString();
|
string content = prop.CastValue<Content>();
|
||||||
string type = "null";
|
string type = "null";
|
||||||
|
|
||||||
if (prop.HasRawBuffer)
|
if (prop.HasRawBuffer)
|
@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToInvariantString();
|
double value = prop.CastValue<double>();
|
||||||
|
node.InnerText = value.ToInvariantString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
48
XmlFormat/Tokens/Enum.cs
Normal file
48
XmlFormat/Tokens/Enum.cs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,23 +9,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
public bool ReadProperty(Property prop, XmlNode token)
|
||||||
{
|
{
|
||||||
bool success = XmlPropertyTokens.ReadPropertyGeneric<uint>(prop, PropertyType.Faces, token);
|
uint value;
|
||||||
|
|
||||||
if (success)
|
if (XmlPropertyTokens.ReadPropertyGeneric(token, out value))
|
||||||
{
|
|
||||||
uint value = (uint)prop.Value;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Faces faces = (Faces)value;
|
Faces faces = (Faces)value;
|
||||||
prop.Value = faces;
|
prop.Value = faces;
|
||||||
}
|
|
||||||
catch
|
return true;
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
@ -33,7 +27,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
XmlElement faces = doc.CreateElement("faces");
|
XmlElement faces = doc.CreateElement("faces");
|
||||||
node.AppendChild(faces);
|
node.AppendChild(faces);
|
||||||
|
|
||||||
int value = (int)prop.Value;
|
int value = prop.CastValue<int>();
|
||||||
faces.InnerText = value.ToInvariantString();
|
faces.InnerText = value.ToInvariantString();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToInvariantString();
|
float value = prop.CastValue<float>();
|
||||||
|
node.InnerText = value.ToInvariantString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,7 +24,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToInvariantString();
|
int value = prop.CastValue<int>();
|
||||||
|
node.InnerText = value.ToInvariantString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,7 +13,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToString();
|
long value = prop.CastValue<long>();
|
||||||
|
node.InnerText = value.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -35,7 +35,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToString() + ' ';
|
NumberRange value = prop.CastValue<NumberRange>();
|
||||||
|
node.InnerText = value.ToString() + ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -44,7 +44,8 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
node.InnerText = prop.Value.ToString() + ' ';
|
NumberSequence value = prop.CastValue<NumberSequence>();
|
||||||
|
node.InnerText = value.ToString() + ' ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -61,7 +61,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
if (hasCustomPhysics)
|
if (hasCustomPhysics)
|
||||||
{
|
{
|
||||||
var customProps = prop.Value as PhysicalProperties;
|
var customProps = prop.CastValue<PhysicalProperties>();
|
||||||
|
|
||||||
var data = new Dictionary<string, float>()
|
var data = new Dictionary<string, float>()
|
||||||
{
|
{
|
34
XmlFormat/Tokens/ProtectedString.cs
Normal file
34
XmlFormat/Tokens/ProtectedString.cs
Normal file
@ -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<ProtectedString>();
|
||||||
|
|
||||||
|
if (value.Contains("\r") || value.Contains("\n"))
|
||||||
|
{
|
||||||
|
XmlCDataSection cdata = doc.CreateCDataSection(value);
|
||||||
|
node.AppendChild(cdata);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.InnerText = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
Ray ray = prop.Value as Ray;
|
Ray ray = prop.CastValue<Ray>();
|
||||||
|
|
||||||
XmlElement origin = doc.CreateElement("origin");
|
XmlElement origin = doc.CreateElement("origin");
|
||||||
XmlElement direction = doc.CreateElement("direction");
|
XmlElement direction = doc.CreateElement("direction");
|
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
Rect rect = prop.Value as Rect;
|
Rect rect = prop.CastValue<Rect>();
|
||||||
|
|
||||||
XmlElement min = doc.CreateElement("min");
|
XmlElement min = doc.CreateElement("min");
|
||||||
Vector2Token.WriteVector2(doc, min, rect.Min);
|
Vector2Token.WriteVector2(doc, min, rect.Min);
|
@ -10,7 +10,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
{
|
{
|
||||||
string refId = token.InnerText;
|
string refId = token.InnerText;
|
||||||
prop.Type = PropertyType.Ref;
|
prop.Type = PropertyType.Ref;
|
||||||
prop.Value = refId;
|
prop.XmlToken = refId;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -19,9 +19,9 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
{
|
{
|
||||||
string result = "null";
|
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<Instance>();
|
||||||
result = inst.Referent;
|
result = inst.Referent;
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System.Xml;
|
||||||
using System.Text;
|
using RobloxFiles.DataTypes;
|
||||||
using System.Xml;
|
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||||
{
|
{
|
||||||
@ -10,17 +9,17 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
public bool ReadProperty(Property prop, XmlNode token)
|
||||||
{
|
{
|
||||||
string contents = token.InnerText;
|
string md5 = token.InnerText;
|
||||||
prop.Type = PropertyType.SharedString;
|
prop.Type = PropertyType.SharedString;
|
||||||
prop.Value = contents;
|
prop.Value = new SharedString(md5);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
var BinaryStringToken = XmlPropertyTokens.GetHandler<BinaryStringToken>();
|
SharedString value = prop.CastValue<SharedString>();
|
||||||
BinaryStringToken.WriteProperty(prop, doc, node);
|
node.InnerText = value.MD5_Key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,10 @@
|
|||||||
using System;
|
using System.Xml;
|
||||||
using System.Text;
|
|
||||||
using System.Xml;
|
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat.PropertyTokens
|
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||||
{
|
{
|
||||||
public class StringToken : IXmlPropertyToken
|
public class StringToken : IXmlPropertyToken
|
||||||
{
|
{
|
||||||
public string Token => "string; ProtectedString";
|
public string Token => "string";
|
||||||
|
|
||||||
public bool ReadProperty(Property prop, XmlNode token)
|
public bool ReadProperty(Property prop, XmlNode token)
|
||||||
{
|
{
|
@ -53,7 +53,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
UDim value = prop.Value as UDim;
|
UDim value = prop.CastValue<UDim>();
|
||||||
WriteUDim(doc, node, value);
|
WriteUDim(doc, node, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
UDim2 value = prop.Value as UDim2;
|
UDim2 value = prop.CastValue<UDim2>();
|
||||||
|
|
||||||
UDim xUDim = value.X;
|
UDim xUDim = value.X;
|
||||||
UDimToken.WriteUDim(doc, node, xUDim, "X");
|
UDimToken.WriteUDim(doc, node, xUDim, "X");
|
@ -58,7 +58,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
Vector2 value = prop.Value as Vector2;
|
Vector2 value = prop.CastValue<Vector2>();
|
||||||
WriteVector2(doc, node, value);
|
WriteVector2(doc, node, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -62,7 +62,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
Vector3 value = prop.Value as Vector3;
|
Vector3 value = prop.CastValue<Vector3>();
|
||||||
WriteVector3(doc, node, value);
|
WriteVector3(doc, node, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
|||||||
|
|
||||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||||
{
|
{
|
||||||
Vector3int16 value = prop.Value as Vector3int16;
|
Vector3int16 value = prop.CastValue<Vector3int16>();
|
||||||
|
|
||||||
XmlElement x = doc.CreateElement("X");
|
XmlElement x = doc.CreateElement("X");
|
||||||
x.InnerText = value.X.ToString();
|
x.InnerText = value.X.ToString();
|
@ -35,38 +35,54 @@ namespace RobloxFiles.XmlFormat
|
|||||||
Handlers = tokenHandlers;
|
Handlers = tokenHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ReadPropertyGeneric<T>(Property prop, PropertyType propType, XmlNode token) where T : struct
|
public static bool ReadPropertyGeneric<T>(XmlNode token, out T outValue) where T : struct
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string value = token.InnerText;
|
string value = token.InnerText;
|
||||||
Type type = typeof(T);
|
Type type = typeof(T);
|
||||||
|
|
||||||
if (type == typeof(int))
|
object result = null;
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
Type resultType = typeof(T);
|
||||||
TypeConverter converter = TypeDescriptor.GetConverter(resultType);
|
var converter = TypeDescriptor.GetConverter(resultType);
|
||||||
|
result = converter.ConvertFromString(token.InnerText);
|
||||||
object result = converter.ConvertFromString(token.InnerText);
|
|
||||||
prop.Value = result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prop.Type = propType;
|
outValue = (T)result;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
outValue = default(T);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool ReadPropertyGeneric<T>(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)
|
public static IXmlPropertyToken GetHandler(string tokenName)
|
||||||
{
|
{
|
||||||
IXmlPropertyToken result = null;
|
IXmlPropertyToken result = null;
|
@ -6,20 +6,26 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
namespace RobloxFiles.XmlFormat
|
using RobloxFiles.DataTypes;
|
||||||
|
using RobloxFiles.XmlFormat;
|
||||||
|
|
||||||
|
namespace RobloxFiles
|
||||||
{
|
{
|
||||||
public class XmlRobloxFile : RobloxFile
|
public class XmlRobloxFile : RobloxFile
|
||||||
{
|
{
|
||||||
// Runtime Specific
|
public readonly XmlDocument XmlDocument = new XmlDocument();
|
||||||
public readonly XmlDocument Root = new XmlDocument();
|
|
||||||
|
|
||||||
internal Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
internal Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
||||||
internal Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
|
internal HashSet<string> SharedStrings = new HashSet<string>();
|
||||||
|
|
||||||
internal XmlRobloxFile()
|
private Dictionary<string, string> RawMetadata = new Dictionary<string, string>();
|
||||||
|
public Dictionary<string, string> Metadata => RawMetadata;
|
||||||
|
|
||||||
|
public XmlRobloxFile()
|
||||||
{
|
{
|
||||||
Name = "XmlRobloxFile";
|
Name = "XmlRobloxFile";
|
||||||
ParentLocked = true;
|
ParentLocked = true;
|
||||||
|
Referent = "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReadFile(byte[] buffer)
|
protected override void ReadFile(byte[] buffer)
|
||||||
@ -27,14 +33,14 @@ namespace RobloxFiles.XmlFormat
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
string xml = Encoding.UTF8.GetString(buffer);
|
string xml = Encoding.UTF8.GetString(buffer);
|
||||||
Root.LoadXml(xml);
|
XmlDocument.LoadXml(xml);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
throw new Exception("XmlRobloxFile: Could not read provided buffer as XML!");
|
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")
|
if (roblox != null && roblox.Name == "roblox")
|
||||||
{
|
{
|
||||||
@ -59,6 +65,10 @@ namespace RobloxFiles.XmlFormat
|
|||||||
{
|
{
|
||||||
XmlRobloxFileReader.ReadSharedStrings(child, this);
|
XmlRobloxFileReader.ReadSharedStrings(child, this);
|
||||||
}
|
}
|
||||||
|
else if (child.Name == "Meta")
|
||||||
|
{
|
||||||
|
XmlRobloxFileReader.ReadMetadata(child, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the properties.
|
// Query the properties.
|
||||||
@ -71,7 +81,8 @@ namespace RobloxFiles.XmlFormat
|
|||||||
|
|
||||||
foreach (Property refProp in refProps)
|
foreach (Property refProp in refProps)
|
||||||
{
|
{
|
||||||
string refId = refProp.Value as string;
|
string refId = refProp.XmlToken;
|
||||||
|
refProp.XmlToken = "Ref";
|
||||||
|
|
||||||
if (Instances.ContainsKey(refId))
|
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);
|
var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString);
|
||||||
|
|
||||||
foreach (Property sharedProp in sharedProps)
|
foreach (Property sharedProp in sharedProps)
|
||||||
{
|
{
|
||||||
string md5 = sharedProp.Value as string;
|
SharedString shared = sharedProp.CastValue<SharedString>();
|
||||||
|
SharedStrings.Add(shared.MD5_Key);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -125,18 +123,33 @@ namespace RobloxFiles.XmlFormat
|
|||||||
Instances.Clear();
|
Instances.Clear();
|
||||||
SharedStrings.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();
|
Instance[] children = GetChildren();
|
||||||
|
|
||||||
// First, record all of the instances.
|
// Record all of the instances.
|
||||||
foreach (Instance inst in children)
|
foreach (Instance inst in children)
|
||||||
XmlRobloxFileWriter.RecordInstances(this, inst);
|
XmlRobloxFileWriter.RecordInstances(this, inst);
|
||||||
|
|
||||||
// Now append them into the document.
|
// Now append them into the document.
|
||||||
foreach (Instance inst in children)
|
foreach (Instance inst in children)
|
||||||
|
{
|
||||||
|
if (inst.Archivable)
|
||||||
{
|
{
|
||||||
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
|
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
|
||||||
roblox.AppendChild(instNode);
|
roblox.AppendChild(instNode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append the shared strings.
|
// Append the shared strings.
|
||||||
if (SharedStrings.Count > 0)
|
if (SharedStrings.Count > 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user