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