Fixed some bugs, generally refining stuff.
This commit is contained in:
parent
ebd56d22a7
commit
2be61916de
@ -5,15 +5,20 @@ using LZ4;
|
|||||||
|
|
||||||
namespace RobloxFiles.BinaryFormat
|
namespace RobloxFiles.BinaryFormat
|
||||||
{
|
{
|
||||||
public class RobloxBinaryChunk
|
/// <summary>
|
||||||
|
/// BinaryRobloxChunk represents a generic LZ4-compressed chunk
|
||||||
|
/// of data in Roblox's Binary File Format.
|
||||||
|
/// </summary>
|
||||||
|
public class BinaryRobloxChunk
|
||||||
{
|
{
|
||||||
public readonly string ChunkType;
|
public readonly string ChunkType;
|
||||||
|
|
||||||
public readonly int CompressedSize;
|
public readonly int CompressedSize;
|
||||||
public readonly byte[] CompressedData;
|
|
||||||
|
|
||||||
public readonly int Size;
|
public readonly int Size;
|
||||||
|
|
||||||
public readonly byte[] Reserved;
|
public readonly byte[] Reserved;
|
||||||
|
|
||||||
|
public readonly byte[] CompressedData;
|
||||||
public readonly byte[] Data;
|
public readonly byte[] Data;
|
||||||
|
|
||||||
public bool HasCompressedData => (CompressedSize > 0);
|
public bool HasCompressedData => (CompressedSize > 0);
|
||||||
@ -23,18 +28,18 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
return ChunkType + " Chunk [" + Size + " bytes]";
|
return ChunkType + " Chunk [" + Size + " bytes]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public RobloxBinaryReader GetReader(string chunkType)
|
public BinaryRobloxReader GetReader(string chunkType)
|
||||||
{
|
{
|
||||||
if (ChunkType == chunkType)
|
if (ChunkType == chunkType)
|
||||||
{
|
{
|
||||||
MemoryStream buffer = new MemoryStream(Data);
|
MemoryStream buffer = new MemoryStream(Data);
|
||||||
return new RobloxBinaryReader(buffer);
|
return new BinaryRobloxReader(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception("Expected " + chunkType + " ChunkType from the input RobloxBinaryChunk");
|
throw new Exception("Expected " + chunkType + " ChunkType from the input RobloxBinaryChunk");
|
||||||
}
|
}
|
||||||
|
|
||||||
public RobloxBinaryChunk(RobloxBinaryReader reader)
|
public BinaryRobloxChunk(BinaryRobloxReader reader)
|
||||||
{
|
{
|
||||||
byte[] bChunkType = reader.ReadBytes(4);
|
byte[] bChunkType = reader.ReadBytes(4);
|
||||||
ChunkType = Encoding.ASCII.GetString(bChunkType);
|
ChunkType = Encoding.ASCII.GetString(bChunkType);
|
||||||
|
@ -5,9 +5,9 @@ using System.Text;
|
|||||||
|
|
||||||
namespace RobloxFiles.BinaryFormat
|
namespace RobloxFiles.BinaryFormat
|
||||||
{
|
{
|
||||||
public class RobloxBinaryReader : BinaryReader
|
public class BinaryRobloxReader : BinaryReader
|
||||||
{
|
{
|
||||||
public RobloxBinaryReader(Stream stream) : base(stream) { }
|
public BinaryRobloxReader(Stream stream) : base(stream) { }
|
||||||
private byte[] lastStringBuffer = new byte[0] { };
|
private byte[] lastStringBuffer = new byte[0] { };
|
||||||
|
|
||||||
public T[] ReadInterlaced<T>(int count, Func<byte[], int, T> decode) where T : struct
|
public T[] ReadInterlaced<T>(int count, Func<byte[], int, T> decode) where T : struct
|
||||||
|
@ -22,7 +22,7 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public Instance Contents => BinContents;
|
public Instance Contents => BinContents;
|
||||||
|
|
||||||
// Runtime Specific
|
// Runtime Specific
|
||||||
public List<RobloxBinaryChunk> Chunks = new List<RobloxBinaryChunk>();
|
public List<BinaryRobloxChunk> Chunks = new List<BinaryRobloxChunk>();
|
||||||
public override string ToString() => GetType().Name;
|
public override string ToString() => GetType().Name;
|
||||||
|
|
||||||
public Instance[] Instances;
|
public Instance[] Instances;
|
||||||
@ -32,7 +32,7 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
public void ReadFile(byte[] contents)
|
public void ReadFile(byte[] contents)
|
||||||
{
|
{
|
||||||
using (MemoryStream file = new MemoryStream(contents))
|
using (MemoryStream file = new MemoryStream(contents))
|
||||||
using (RobloxBinaryReader reader = new RobloxBinaryReader(file))
|
using (BinaryRobloxReader reader = new BinaryRobloxReader(file))
|
||||||
{
|
{
|
||||||
// Verify the signature of the file.
|
// Verify the signature of the file.
|
||||||
byte[] binSignature = reader.ReadBytes(14);
|
byte[] binSignature = reader.ReadBytes(14);
|
||||||
@ -57,7 +57,7 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RobloxBinaryChunk chunk = new RobloxBinaryChunk(reader);
|
BinaryRobloxChunk chunk = new BinaryRobloxChunk(reader);
|
||||||
Chunks.Add(chunk);
|
Chunks.Add(chunk);
|
||||||
|
|
||||||
switch (chunk.ChunkType)
|
switch (chunk.ChunkType)
|
||||||
@ -67,7 +67,8 @@ namespace RobloxFiles.BinaryFormat
|
|||||||
type.Allocate(this);
|
type.Allocate(this);
|
||||||
break;
|
break;
|
||||||
case "PROP":
|
case "PROP":
|
||||||
PROP.ReadProperties(this, chunk);
|
PROP prop = new PROP(chunk);
|
||||||
|
prop.ReadProperties(this);
|
||||||
break;
|
break;
|
||||||
case "PRNT":
|
case "PRNT":
|
||||||
PRNT hierarchy = new PRNT(chunk);
|
PRNT hierarchy = new PRNT(chunk);
|
||||||
|
@ -13,9 +13,9 @@
|
|||||||
return TypeName;
|
return TypeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public INST(RobloxBinaryChunk chunk)
|
public INST(BinaryRobloxChunk chunk)
|
||||||
{
|
{
|
||||||
using (RobloxBinaryReader reader = chunk.GetReader("INST"))
|
using (BinaryRobloxReader reader = chunk.GetReader("INST"))
|
||||||
{
|
{
|
||||||
TypeIndex = reader.ReadInt32();
|
TypeIndex = reader.ReadInt32();
|
||||||
TypeName = reader.ReadString();
|
TypeName = reader.ReadString();
|
||||||
|
@ -7,9 +7,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
public int NumEntries;
|
public int NumEntries;
|
||||||
public Dictionary<string, string> Entries;
|
public Dictionary<string, string> Entries;
|
||||||
|
|
||||||
public META(RobloxBinaryChunk chunk)
|
public META(BinaryRobloxChunk chunk)
|
||||||
{
|
{
|
||||||
using (RobloxBinaryReader reader = chunk.GetReader("META"))
|
using (BinaryRobloxReader reader = chunk.GetReader("META"))
|
||||||
{
|
{
|
||||||
NumEntries = reader.ReadInt32();
|
NumEntries = reader.ReadInt32();
|
||||||
Entries = new Dictionary<string, string>(NumEntries);
|
Entries = new Dictionary<string, string>(NumEntries);
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
public readonly int[] ChildrenIds;
|
public readonly int[] ChildrenIds;
|
||||||
public readonly int[] ParentIds;
|
public readonly int[] ParentIds;
|
||||||
|
|
||||||
public PRNT(RobloxBinaryChunk chunk)
|
public PRNT(BinaryRobloxChunk chunk)
|
||||||
{
|
{
|
||||||
using (RobloxBinaryReader reader = chunk.GetReader("PRNT"))
|
using (BinaryRobloxReader reader = chunk.GetReader("PRNT"))
|
||||||
{
|
{
|
||||||
Format = reader.ReadByte();
|
Format = reader.ReadByte();
|
||||||
NumRelations = reader.ReadInt32();
|
NumRelations = reader.ReadInt32();
|
||||||
|
@ -9,27 +9,34 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
{
|
{
|
||||||
public class PROP
|
public class PROP
|
||||||
{
|
{
|
||||||
public static void ReadProperties(BinaryRobloxFile file, RobloxBinaryChunk chunk)
|
public readonly string Name;
|
||||||
{
|
public readonly int TypeIndex;
|
||||||
RobloxBinaryReader reader = chunk.GetReader("PROP");
|
public readonly PropertyType Type;
|
||||||
|
|
||||||
// Read the property's header info.
|
private BinaryRobloxReader Reader;
|
||||||
int typeIndex = reader.ReadInt32();
|
|
||||||
string name = reader.ReadString();
|
public PROP(BinaryRobloxChunk chunk)
|
||||||
PropertyType propType;
|
{
|
||||||
|
Reader = chunk.GetReader("PROP");
|
||||||
|
|
||||||
|
TypeIndex = Reader.ReadInt32();
|
||||||
|
Name = Reader.ReadString();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte typeId = reader.ReadByte();
|
byte propType = Reader.ReadByte();
|
||||||
propType = (PropertyType)typeId;
|
Type = (PropertyType)propType;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
propType = PropertyType.Unknown;
|
Type = PropertyType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create access arrays for the objects we will be adding properties to.
|
}
|
||||||
INST type = file.Types[typeIndex];
|
|
||||||
|
public void ReadProperties(BinaryRobloxFile file)
|
||||||
|
{
|
||||||
|
INST type = file.Types[TypeIndex];
|
||||||
Property[] props = new Property[type.NumInstances];
|
Property[] props = new Property[type.NumInstances];
|
||||||
|
|
||||||
int[] ids = type.InstanceIds;
|
int[] ids = type.InstanceIds;
|
||||||
@ -37,16 +44,16 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
for (int i = 0; i < instCount; i++)
|
for (int i = 0; i < instCount; i++)
|
||||||
{
|
{
|
||||||
int instId = ids[i];
|
int id = ids[i];
|
||||||
Instance inst = file.Instances[instId];
|
Instance instance = file.Instances[id];
|
||||||
|
|
||||||
Property prop = new Property();
|
Property prop = new Property();
|
||||||
prop.Name = name;
|
prop.Name = Name;
|
||||||
prop.Type = propType;
|
prop.Type = Type;
|
||||||
prop.Instance = inst;
|
prop.Instance = instance;
|
||||||
|
|
||||||
props[i] = prop;
|
props[i] = prop;
|
||||||
inst.AddProperty(ref prop);
|
instance.AddProperty(ref prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup some short-hand functions for actions frequently used during the read procedure.
|
// Setup some short-hand functions for actions frequently used during the read procedure.
|
||||||
@ -59,20 +66,20 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var readInts = new Func<int[]>(() => reader.ReadInts(instCount));
|
var readInts = new Func<int[]>(() => Reader.ReadInts(instCount));
|
||||||
var readFloats = new Func<float[]>(() => reader.ReadFloats(instCount));
|
var readFloats = new Func<float[]>(() => Reader.ReadFloats(instCount));
|
||||||
|
|
||||||
// Read the property data based on the property type.
|
// Read the property data based on the property type.
|
||||||
switch (propType)
|
switch (Type)
|
||||||
{
|
{
|
||||||
case PropertyType.String:
|
case PropertyType.String:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
string result = reader.ReadString();
|
string result = Reader.ReadString();
|
||||||
|
|
||||||
// Leave an access point for the original byte sequence, in case this is a BinaryString.
|
// Leave an access point for the original byte sequence, in case this is a BinaryString.
|
||||||
// This will allow the developer to read the sequence without any mangling from C# strings.
|
// This will allow the developer to read the sequence without any mangling from C# strings.
|
||||||
byte[] buffer = reader.GetLastStringBuffer();
|
byte[] buffer = Reader.GetLastStringBuffer();
|
||||||
props[i].SetRawBuffer(buffer);
|
props[i].SetRawBuffer(buffer);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -80,7 +87,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Bool:
|
case PropertyType.Bool:
|
||||||
loadProperties(i => reader.ReadBoolean());
|
loadProperties(i => Reader.ReadBoolean());
|
||||||
break;
|
break;
|
||||||
case PropertyType.Int:
|
case PropertyType.Int:
|
||||||
int[] ints = readInts();
|
int[] ints = readInts();
|
||||||
@ -91,7 +98,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
loadProperties(i => floats[i]);
|
loadProperties(i => floats[i]);
|
||||||
break;
|
break;
|
||||||
case PropertyType.Double:
|
case PropertyType.Double:
|
||||||
loadProperties(i => reader.ReadDouble());
|
loadProperties(i => Reader.ReadDouble());
|
||||||
break;
|
break;
|
||||||
case PropertyType.UDim:
|
case PropertyType.UDim:
|
||||||
float[] UDim_Scales = readFloats();
|
float[] UDim_Scales = readFloats();
|
||||||
@ -127,10 +134,10 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.Ray:
|
case PropertyType.Ray:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
float[] rawOrigin = reader.ReadFloats(3);
|
float[] rawOrigin = Reader.ReadFloats(3);
|
||||||
Vector3 origin = new Vector3(rawOrigin);
|
Vector3 origin = new Vector3(rawOrigin);
|
||||||
|
|
||||||
float[] rawDirection = reader.ReadFloats(3);
|
float[] rawDirection = Reader.ReadFloats(3);
|
||||||
Vector3 direction = new Vector3(rawDirection);
|
Vector3 direction = new Vector3(rawDirection);
|
||||||
|
|
||||||
return new Ray(origin, direction);
|
return new Ray(origin, direction);
|
||||||
@ -140,7 +147,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.Faces:
|
case PropertyType.Faces:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
byte faces = reader.ReadByte();
|
byte faces = Reader.ReadByte();
|
||||||
return (Faces)faces;
|
return (Faces)faces;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,7 +155,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.Axes:
|
case PropertyType.Axes:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
byte axes = reader.ReadByte();
|
byte axes = Reader.ReadByte();
|
||||||
return (Axes)axes;
|
return (Axes)axes;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -213,7 +220,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
int normXY = reader.ReadByte();
|
int normXY = Reader.ReadByte();
|
||||||
|
|
||||||
if (normXY > 0)
|
if (normXY > 0)
|
||||||
{
|
{
|
||||||
@ -237,13 +244,13 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
R2.X, R2.Y, R2.Z,
|
R2.X, R2.Y, R2.Z,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (propType == PropertyType.Quaternion)
|
else if (Type == PropertyType.Quaternion)
|
||||||
{
|
{
|
||||||
float qx = reader.ReadFloat(), qy = reader.ReadFloat(),
|
float qx = Reader.ReadFloat(), qy = Reader.ReadFloat(),
|
||||||
qz = reader.ReadFloat(), qw = reader.ReadFloat();
|
qz = Reader.ReadFloat(), qw = Reader.ReadFloat();
|
||||||
|
|
||||||
Quaternion quat = new Quaternion(qx, qy, qz, qw);
|
Quaternion quaternion = new Quaternion(qx, qy, qz, qw);
|
||||||
var rotation = quat.ToCFrame();
|
var rotation = quaternion.ToCFrame();
|
||||||
|
|
||||||
return rotation.GetComponents();
|
return rotation.GetComponents();
|
||||||
}
|
}
|
||||||
@ -253,7 +260,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
for (int m = 0; m < 9; m++)
|
for (int m = 0; m < 9; m++)
|
||||||
{
|
{
|
||||||
float value = reader.ReadFloat();
|
float value = Reader.ReadFloat();
|
||||||
matrix[m] = value;
|
matrix[m] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,12 +291,12 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
// TODO: I want to map these values to actual Roblox enums, but I'll have to add an
|
// 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.
|
// interpreter for the JSON API Dump to do it properly.
|
||||||
|
|
||||||
uint[] enums = reader.ReadInterlaced(instCount, BitConverter.ToUInt32);
|
uint[] enums = Reader.ReadInterlaced(instCount, BitConverter.ToUInt32);
|
||||||
loadProperties(i => enums[i]);
|
loadProperties(i => enums[i]);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Ref:
|
case PropertyType.Ref:
|
||||||
int[] instIds = reader.ReadInstanceIds(instCount);
|
int[] instIds = Reader.ReadInstanceIds(instCount);
|
||||||
|
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
@ -301,9 +308,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.Vector3int16:
|
case PropertyType.Vector3int16:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
short x = reader.ReadInt16(),
|
short x = Reader.ReadInt16(),
|
||||||
y = reader.ReadInt16(),
|
y = Reader.ReadInt16(),
|
||||||
z = reader.ReadInt16();
|
z = Reader.ReadInt16();
|
||||||
|
|
||||||
return new Vector3int16(x, y, z);
|
return new Vector3int16(x, y, z);
|
||||||
});
|
});
|
||||||
@ -312,14 +319,14 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.NumberSequence:
|
case PropertyType.NumberSequence:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
int numKeys = reader.ReadInt32();
|
int numKeys = Reader.ReadInt32();
|
||||||
var keypoints = new NumberSequenceKeypoint[numKeys];
|
var keypoints = new NumberSequenceKeypoint[numKeys];
|
||||||
|
|
||||||
for (int key = 0; key < numKeys; key++)
|
for (int key = 0; key < numKeys; key++)
|
||||||
{
|
{
|
||||||
float Time = reader.ReadFloat(),
|
float Time = Reader.ReadFloat(),
|
||||||
Value = reader.ReadFloat(),
|
Value = Reader.ReadFloat(),
|
||||||
Envelope = reader.ReadFloat();
|
Envelope = Reader.ReadFloat();
|
||||||
|
|
||||||
keypoints[key] = new NumberSequenceKeypoint(Time, Value, Envelope);
|
keypoints[key] = new NumberSequenceKeypoint(Time, Value, Envelope);
|
||||||
}
|
}
|
||||||
@ -331,23 +338,20 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.ColorSequence:
|
case PropertyType.ColorSequence:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
int numKeys = reader.ReadInt32();
|
int numKeys = Reader.ReadInt32();
|
||||||
var keypoints = new ColorSequenceKeypoint[numKeys];
|
var keypoints = new ColorSequenceKeypoint[numKeys];
|
||||||
|
|
||||||
for (int key = 0; key < numKeys; key++)
|
for (int key = 0; key < numKeys; key++)
|
||||||
{
|
{
|
||||||
float Time = reader.ReadFloat(),
|
float Time = Reader.ReadFloat(),
|
||||||
R = reader.ReadFloat(),
|
R = Reader.ReadFloat(),
|
||||||
G = reader.ReadFloat(),
|
G = Reader.ReadFloat(),
|
||||||
B = reader.ReadFloat();
|
B = Reader.ReadFloat();
|
||||||
|
|
||||||
Color3 Color = new Color3(R, G, B);
|
Color3 Value = new Color3(R, G, B);
|
||||||
keypoints[key] = new ColorSequenceKeypoint(Time, Color);
|
byte[] Reserved = Reader.ReadBytes(4);
|
||||||
|
|
||||||
// ColorSequenceKeypoint has an unused `Envelope` float which has to be read.
|
keypoints[key] = new ColorSequenceKeypoint(Time, Value, Reserved);
|
||||||
// Roblox Studio writes it because it does an std::memcpy call to the C++ type.
|
|
||||||
// If we skip it, the stream will become misaligned.
|
|
||||||
reader.ReadBytes(4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ColorSequence(keypoints);
|
return new ColorSequence(keypoints);
|
||||||
@ -357,8 +361,8 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.NumberRange:
|
case PropertyType.NumberRange:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
float min = reader.ReadFloat();
|
float min = Reader.ReadFloat();
|
||||||
float max = reader.ReadFloat();
|
float max = Reader.ReadFloat();
|
||||||
|
|
||||||
return new NumberRange(min, max);
|
return new NumberRange(min, max);
|
||||||
});
|
});
|
||||||
@ -380,15 +384,15 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
case PropertyType.PhysicalProperties:
|
case PropertyType.PhysicalProperties:
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
bool custom = reader.ReadBoolean();
|
bool custom = Reader.ReadBoolean();
|
||||||
|
|
||||||
if (custom)
|
if (custom)
|
||||||
{
|
{
|
||||||
float Density = reader.ReadFloat(),
|
float Density = Reader.ReadFloat(),
|
||||||
Friction = reader.ReadFloat(),
|
Friction = Reader.ReadFloat(),
|
||||||
Elasticity = reader.ReadFloat(),
|
Elasticity = Reader.ReadFloat(),
|
||||||
FrictionWeight = reader.ReadFloat(),
|
FrictionWeight = Reader.ReadFloat(),
|
||||||
ElasticityWeight = reader.ReadFloat();
|
ElasticityWeight = Reader.ReadFloat();
|
||||||
|
|
||||||
return new PhysicalProperties
|
return new PhysicalProperties
|
||||||
(
|
(
|
||||||
@ -405,9 +409,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Color3uint8:
|
case PropertyType.Color3uint8:
|
||||||
byte[] color3uint8_R = reader.ReadBytes(instCount),
|
byte[] color3uint8_R = Reader.ReadBytes(instCount),
|
||||||
color3uint8_G = reader.ReadBytes(instCount),
|
color3uint8_G = Reader.ReadBytes(instCount),
|
||||||
color3uint8_B = reader.ReadBytes(instCount);
|
color3uint8_B = Reader.ReadBytes(instCount);
|
||||||
|
|
||||||
loadProperties(i =>
|
loadProperties(i =>
|
||||||
{
|
{
|
||||||
@ -420,7 +424,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case PropertyType.Int64:
|
case PropertyType.Int64:
|
||||||
long[] int64s = reader.ReadInterlaced(instCount, (buffer, start) =>
|
long[] int64s = Reader.ReadInterlaced(instCount, (buffer, start) =>
|
||||||
{
|
{
|
||||||
long result = BitConverter.ToInt64(buffer, start);
|
long result = BitConverter.ToInt64(buffer, start);
|
||||||
return (long)((ulong)result >> 1) ^ (-(result & 1));
|
return (long)((ulong)result >> 1) ^ (-(result & 1));
|
||||||
@ -430,7 +434,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.Dispose();
|
Reader.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using RobloxFiles.BinaryFormat;
|
|
||||||
using RobloxFiles.XmlFormat;
|
|
||||||
|
|
||||||
namespace RobloxFiles
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface which represents a RobloxFile implementation.
|
|
||||||
/// </summary>
|
|
||||||
public interface IRobloxFile
|
|
||||||
{
|
|
||||||
Instance Contents { get; }
|
|
||||||
void ReadFile(byte[] buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a loaded *.rbxl/*.rbxm Roblox file.
|
|
||||||
/// All of the surface-level Instances are stored in the RobloxFile's Trunk property.
|
|
||||||
/// </summary>
|
|
||||||
public class RobloxFile : IRobloxFile
|
|
||||||
{
|
|
||||||
public bool Initialized { get; private set; }
|
|
||||||
public IRobloxFile InnerFile { get; private set; }
|
|
||||||
|
|
||||||
public Instance Contents => InnerFile.Contents;
|
|
||||||
|
|
||||||
public void ReadFile(byte[] buffer)
|
|
||||||
{
|
|
||||||
if (!Initialized)
|
|
||||||
{
|
|
||||||
if (buffer.Length > 14)
|
|
||||||
{
|
|
||||||
string header = Encoding.UTF7.GetString(buffer, 0, 14);
|
|
||||||
IRobloxFile file = null;
|
|
||||||
|
|
||||||
if (header == BinaryRobloxFile.MagicHeader)
|
|
||||||
file = new BinaryRobloxFile();
|
|
||||||
else if (header.StartsWith("<roblox"))
|
|
||||||
file = new XmlRobloxFile();
|
|
||||||
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
file.ReadFile(buffer);
|
|
||||||
InnerFile = file;
|
|
||||||
|
|
||||||
Initialized = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception("Unrecognized header!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RobloxFile(byte[] buffer)
|
|
||||||
{
|
|
||||||
ReadFile(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RobloxFile(Stream stream)
|
|
||||||
{
|
|
||||||
byte[] buffer;
|
|
||||||
|
|
||||||
using (MemoryStream memoryStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
stream.CopyTo(memoryStream);
|
|
||||||
buffer = memoryStream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadFile(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RobloxFile(string filePath)
|
|
||||||
{
|
|
||||||
byte[] buffer = File.ReadAllBytes(filePath);
|
|
||||||
ReadFile(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Treats the provided string as if you were indexing a specific child or descendant of the `RobloxFile.Contents` folder.<para/>
|
|
||||||
/// The provided string can either be:<para/>
|
|
||||||
/// - The name of a child that is parented to RobloxFile.Contents ( Example: RobloxFile["Workspace"] )<para/>
|
|
||||||
/// - A period (.) separated path to a descendant of RobloxFile.Contents ( Example: RobloxFile["Workspace.Terrain"] )<para/>
|
|
||||||
/// This will throw an exception if any instance in the traversal is not found.
|
|
||||||
/// </summary>
|
|
||||||
public Instance this[string accessor] => Contents[accessor];
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,11 +4,13 @@
|
|||||||
{
|
{
|
||||||
public readonly float Time;
|
public readonly float Time;
|
||||||
public readonly Color3 Value;
|
public readonly Color3 Value;
|
||||||
|
public readonly byte[] Reserved;
|
||||||
|
|
||||||
public ColorSequenceKeypoint(float time, Color3 value)
|
public ColorSequenceKeypoint(float time, Color3 value, byte[] reserved = null)
|
||||||
{
|
{
|
||||||
Time = time;
|
Time = time;
|
||||||
Value = value;
|
Value = value;
|
||||||
|
Reserved = reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
@ -34,10 +34,10 @@
|
|||||||
public Vector3 ClosestPoint(Vector3 point)
|
public Vector3 ClosestPoint(Vector3 point)
|
||||||
{
|
{
|
||||||
Vector3 result = Origin;
|
Vector3 result = Origin;
|
||||||
float t = Direction.Dot(point - result);
|
float dist = Direction.Dot(point - result);
|
||||||
|
|
||||||
if (t >= 0)
|
if (dist >= 0)
|
||||||
result += (Direction * t);
|
result += (Direction * dist);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@
|
|||||||
public float Distance(Vector3 point)
|
public float Distance(Vector3 point)
|
||||||
{
|
{
|
||||||
Vector3 closestPoint = ClosestPoint(point);
|
Vector3 closestPoint = ClosestPoint(point);
|
||||||
return (closestPoint - point).Magnitude;
|
return (point - closestPoint).Magnitude;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
Interfaces/IRobloxFile.cs
Normal file
17
Interfaces/IRobloxFile.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace RobloxFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface which represents a RobloxFile implementation.
|
||||||
|
/// </summary>
|
||||||
|
public interface IRobloxFile
|
||||||
|
{
|
||||||
|
Instance Contents { get; }
|
||||||
|
void ReadFile(byte[] buffer);
|
||||||
|
}
|
||||||
|
}
|
15
Interfaces/IXmlPropertyToken.cs
Normal file
15
Interfaces/IXmlPropertyToken.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace RobloxFiles.XmlFormat
|
||||||
|
{
|
||||||
|
public interface IXmlPropertyToken
|
||||||
|
{
|
||||||
|
string Token { get; }
|
||||||
|
bool ReadToken(Property prop, XmlNode token);
|
||||||
|
}
|
||||||
|
}
|
149
RobloxFile.cs
Normal file
149
RobloxFile.cs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using RobloxFiles.BinaryFormat;
|
||||||
|
using RobloxFiles.XmlFormat;
|
||||||
|
|
||||||
|
namespace RobloxFiles
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a loaded *.rbxl/*.rbxm Roblox file.
|
||||||
|
/// All of the surface-level Instances are stored in the RobloxFile's 'Contents' property.
|
||||||
|
/// </summary>
|
||||||
|
public class RobloxFile : IRobloxFile
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if this RobloxFile has loaded data already.
|
||||||
|
/// </summary>
|
||||||
|
public bool Initialized { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A reference to the inner IRobloxFile implementation that this RobloxFile opened with.<para/>
|
||||||
|
/// It can be a BinaryRobloxFile, or an XmlRobloxFile.
|
||||||
|
/// </summary>
|
||||||
|
public IRobloxFile InnerFile { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A reference to a Folder Instance that stores all of the contents that were loaded.
|
||||||
|
/// </summary>
|
||||||
|
public Instance Contents => InnerFile.Contents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the RobloxFile from the provided buffer, if it hasn't been Initialized yet.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer"></param>
|
||||||
|
public void ReadFile(byte[] buffer)
|
||||||
|
{
|
||||||
|
if (!Initialized)
|
||||||
|
{
|
||||||
|
if (buffer.Length > 14)
|
||||||
|
{
|
||||||
|
string header = Encoding.UTF7.GetString(buffer, 0, 14);
|
||||||
|
IRobloxFile file = null;
|
||||||
|
|
||||||
|
if (header == BinaryRobloxFile.MagicHeader)
|
||||||
|
file = new BinaryRobloxFile();
|
||||||
|
else if (header.StartsWith("<roblox"))
|
||||||
|
file = new XmlRobloxFile();
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
{
|
||||||
|
file.ReadFile(buffer);
|
||||||
|
InnerFile = file;
|
||||||
|
|
||||||
|
Initialized = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("Unrecognized header!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a RobloxFile from a provided byte sequence that represents the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer"></param>
|
||||||
|
private RobloxFile(byte[] buffer)
|
||||||
|
{
|
||||||
|
ReadFile(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a Roblox file from a byte sequence that represents the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">A byte sequence that represents the file.</param>
|
||||||
|
public static RobloxFile Open(byte[] buffer)
|
||||||
|
{
|
||||||
|
return new RobloxFile(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a Roblox file by reading from a provided Stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read the Roblox file from.</param>
|
||||||
|
public static RobloxFile Open(Stream stream)
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
|
||||||
|
using (MemoryStream memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
stream.CopyTo(memoryStream);
|
||||||
|
buffer = memoryStream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Open(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a Roblox file from a provided file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">A path to a Roblox file to be opened.</param>
|
||||||
|
public static RobloxFile Open(string filePath)
|
||||||
|
{
|
||||||
|
byte[] buffer = File.ReadAllBytes(filePath);
|
||||||
|
return Open(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates and runs a Task to open a Roblox file from a byte sequence that represents the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">A byte sequence that represents the file.</param>
|
||||||
|
public static Task<RobloxFile> OpenAsync(byte[] buffer)
|
||||||
|
{
|
||||||
|
return Task.Run(() => Open(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates and runs a Task to open a Roblox file using a provided Stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to read the Roblox file from.</param>
|
||||||
|
public static Task<RobloxFile> OpenAsync(Stream stream)
|
||||||
|
{
|
||||||
|
return Task.Run(() => Open(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens a Roblox file from a provided file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">A path to a Roblox file to be opened.</param>
|
||||||
|
public static Task<RobloxFile> OpenAsync(string filePath)
|
||||||
|
{
|
||||||
|
return Task.Run(() => Open(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows you to access a child/descendant of this file's contents, and/or one of its properties.<para/>
|
||||||
|
/// The provided string should be a period-separated (.) path to what you wish to access.<para/>
|
||||||
|
/// This will throw an exception if any part of the path cannot be found.<para/>
|
||||||
|
///
|
||||||
|
/// ~ Examples ~<para/>
|
||||||
|
/// var terrain = robloxFile["Workspace.Terrain"] as Instance;<para/>
|
||||||
|
/// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;<para/>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public object this[string accessor] => Contents[accessor];
|
||||||
|
}
|
||||||
|
}
|
@ -69,10 +69,10 @@
|
|||||||
<Compile Include="BinaryFormat\ChunkTypes\META.cs" />
|
<Compile Include="BinaryFormat\ChunkTypes\META.cs" />
|
||||||
<Compile Include="BinaryFormat\ChunkTypes\PRNT.cs" />
|
<Compile Include="BinaryFormat\ChunkTypes\PRNT.cs" />
|
||||||
<Compile Include="BinaryFormat\ChunkTypes\PROP.cs" />
|
<Compile Include="BinaryFormat\ChunkTypes\PROP.cs" />
|
||||||
<Compile Include="Core\Enums.cs" />
|
<Compile Include="Tree\Enums.cs" />
|
||||||
<Compile Include="Core\Property.cs" />
|
<Compile Include="Tree\Property.cs" />
|
||||||
<Compile Include="Core\Instance.cs" />
|
<Compile Include="Tree\Instance.cs" />
|
||||||
<Compile Include="Core\RobloxFile.cs" />
|
<Compile Include="RobloxFile.cs" />
|
||||||
<Compile Include="DataTypes\Axes.cs" />
|
<Compile Include="DataTypes\Axes.cs" />
|
||||||
<Compile Include="DataTypes\BrickColor.cs" />
|
<Compile Include="DataTypes\BrickColor.cs" />
|
||||||
<Compile Include="DataTypes\CFrame.cs" />
|
<Compile Include="DataTypes\CFrame.cs" />
|
||||||
@ -86,6 +86,8 @@
|
|||||||
<Compile Include="DataTypes\PhysicalProperties.cs" />
|
<Compile Include="DataTypes\PhysicalProperties.cs" />
|
||||||
<Compile Include="DataTypes\Ray.cs" />
|
<Compile Include="DataTypes\Ray.cs" />
|
||||||
<Compile Include="DataTypes\Region3int16.cs" />
|
<Compile Include="DataTypes\Region3int16.cs" />
|
||||||
|
<Compile Include="Interfaces\IRobloxFile.cs" />
|
||||||
|
<Compile Include="Interfaces\IXmlPropertyToken.cs" />
|
||||||
<Compile Include="Utility\BrickColors.cs" />
|
<Compile Include="Utility\BrickColors.cs" />
|
||||||
<Compile Include="DataTypes\Vector3int16.cs" />
|
<Compile Include="DataTypes\Vector3int16.cs" />
|
||||||
<Compile Include="DataTypes\Rect.cs" />
|
<Compile Include="DataTypes\Rect.cs" />
|
||||||
@ -97,6 +99,7 @@
|
|||||||
<Compile Include="Utility\MaterialInfo.cs" />
|
<Compile Include="Utility\MaterialInfo.cs" />
|
||||||
<Compile Include="Utility\Quaternion.cs" />
|
<Compile Include="Utility\Quaternion.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="XmlFormat\PropertyTokens\Vector3int16.cs" />
|
||||||
<Compile Include="XmlFormat\XmlPropertyTokens.cs" />
|
<Compile Include="XmlFormat\XmlPropertyTokens.cs" />
|
||||||
<Compile Include="XmlFormat\XmlDataReader.cs" />
|
<Compile Include="XmlFormat\XmlDataReader.cs" />
|
||||||
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
|
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
|
||||||
|
@ -376,9 +376,10 @@ namespace RobloxFiles.Enums
|
|||||||
public enum ContextActionPriority
|
public enum ContextActionPriority
|
||||||
{
|
{
|
||||||
Low = 1000,
|
Low = 1000,
|
||||||
Default = 2000,
|
Medium = 2000,
|
||||||
Medium,
|
High = 3000,
|
||||||
High = 3000
|
|
||||||
|
Default = Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContextActionResult
|
public enum ContextActionResult
|
@ -20,6 +20,7 @@ namespace RobloxFiles
|
|||||||
private List<Instance> Children = new List<Instance>();
|
private List<Instance> Children = new List<Instance>();
|
||||||
private Instance rawParent;
|
private Instance rawParent;
|
||||||
|
|
||||||
|
/// <summary>The name of this Instance, if a Name property is defined.</summary>
|
||||||
public string Name => ReadProperty("Name", ClassName);
|
public string Name => ReadProperty("Name", ClassName);
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
|
||||||
@ -108,15 +109,19 @@ namespace RobloxFiles
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Instance[] GetDescendants()
|
public Instance[] GetDescendants()
|
||||||
{
|
{
|
||||||
Instance[] results = GetChildren();
|
List<Instance> results = new List<Instance>();
|
||||||
|
|
||||||
foreach (Instance child in results)
|
foreach (Instance child in Children)
|
||||||
{
|
{
|
||||||
Instance[] childResults = child.GetDescendants();
|
// Add this child to the results.
|
||||||
results = results.Concat(childResults).ToArray();
|
results.Add(child);
|
||||||
|
|
||||||
|
// Add its descendants to the results.
|
||||||
|
Instance[] descendants = child.GetDescendants();
|
||||||
|
results.AddRange(descendants);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -128,23 +133,73 @@ namespace RobloxFiles
|
|||||||
public Instance FindFirstChild(string name, bool recursive = false)
|
public Instance FindFirstChild(string name, bool recursive = false)
|
||||||
{
|
{
|
||||||
Instance result = null;
|
Instance result = null;
|
||||||
|
var query = Children.Where((child) => name == child.Name);
|
||||||
|
|
||||||
var query = Children.Where(child => child.Name == name);
|
|
||||||
if (query.Count() > 0)
|
if (query.Count() > 0)
|
||||||
|
{
|
||||||
result = query.First();
|
result = query.First();
|
||||||
|
}
|
||||||
|
else if (recursive)
|
||||||
|
{
|
||||||
|
foreach (Instance child in Children)
|
||||||
|
{
|
||||||
|
Instance found = child.FindFirstChild(name, true);
|
||||||
|
|
||||||
|
if (found != null)
|
||||||
|
{
|
||||||
|
result = found;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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 Instance FindFirstAncestor(string name)
|
||||||
|
{
|
||||||
|
Instance ancestor = Parent;
|
||||||
|
|
||||||
|
while (ancestor != null)
|
||||||
|
{
|
||||||
|
if (ancestor.Name == name)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ancestor = ancestor.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instance FindFirstAncestorOfClass(string className)
|
||||||
|
{
|
||||||
|
Instance ancestor = Parent;
|
||||||
|
|
||||||
|
while (ancestor != null)
|
||||||
|
{
|
||||||
|
if (ancestor.ClassName == className)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ancestor = ancestor.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestor;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the first Instance whose ClassName is the provided string className. If the instance is not found, this returns null.
|
/// Returns the first Instance whose ClassName is the provided string className. If the instance is not found, this returns null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="className">The ClassName of the Instance to find.</param>
|
/// <param name="className">The ClassName of the Instance to find.</param>
|
||||||
public Instance FindFirstChildOfClass(string className)
|
public Instance FindFirstChildOfClass(string className, bool recursive = false)
|
||||||
{
|
{
|
||||||
Instance result = null;
|
Instance result = null;
|
||||||
|
var query = Children.Where((child) => className == child.ClassName);
|
||||||
|
|
||||||
var query = Children.Where(child => child.ClassName == className);
|
|
||||||
if (query.Count() > 0)
|
if (query.Count() > 0)
|
||||||
result = query.First();
|
result = query.First();
|
||||||
|
|
||||||
@ -242,13 +297,16 @@ namespace RobloxFiles
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Treats the provided string as if you were indexing a specific child or descendant of this Instance.<para/>
|
/// Allows you to access a child/descendant of this Instance, and/or one of its properties.<para/>
|
||||||
/// The provided string can either be:<para/>
|
/// The provided string should be a period-separated (.) path to what you wish to access.<para/>
|
||||||
/// - The name of a child that is parented to this Instance. ( Example: game["Workspace"] )<para/>
|
/// This will throw an exception if any part of the path cannot be found.<para/>
|
||||||
/// - A period-separated path to a descendant of this Instance. ( Example: game["Workspace.Terrain"] )<para/>
|
///
|
||||||
/// This will throw an exception if any instance in the traversal is not found.
|
/// ~ Examples ~<para/>
|
||||||
|
/// var terrain = robloxFile["Workspace.Terrain"] as Instance;<para/>
|
||||||
|
/// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;<para/>
|
||||||
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Instance this[string accessor]
|
public object this[string accessor]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@ -259,7 +317,21 @@ namespace RobloxFiles
|
|||||||
Instance next = result.FindFirstChild(name);
|
Instance next = result.FindFirstChild(name);
|
||||||
|
|
||||||
if (next == null)
|
if (next == null)
|
||||||
throw new Exception(name + " is not a valid member of " + result.Name);
|
{
|
||||||
|
// Check if there is any property with this name.
|
||||||
|
var propQuery = result.Properties
|
||||||
|
.Where((prop) => name == prop.Name);
|
||||||
|
|
||||||
|
if (propQuery.Count() > 0)
|
||||||
|
{
|
||||||
|
var prop = propQuery.First();
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception(name + " is not a valid member of " + result.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result = next;
|
result = next;
|
||||||
}
|
}
|
@ -36,8 +36,9 @@ namespace RobloxFiles
|
|||||||
|
|
||||||
public class Property
|
public class Property
|
||||||
{
|
{
|
||||||
public Instance Instance;
|
|
||||||
public string Name;
|
public string Name;
|
||||||
|
public Instance Instance;
|
||||||
|
|
||||||
public PropertyType Type;
|
public PropertyType Type;
|
||||||
public object Value;
|
public object Value;
|
||||||
|
|
@ -39,6 +39,7 @@ namespace RobloxFiles.Utility
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This contains a list of all defined BrickColors on Roblox.
|
/// This contains a list of all defined BrickColors on Roblox.
|
||||||
|
/// There are some name duplicates, but that's an issue on Roblox's end.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
public static IReadOnlyList<BrickColor> ColorMap = new List<BrickColor>()
|
public static IReadOnlyList<BrickColor> ColorMap = new List<BrickColor>()
|
||||||
|
@ -140,7 +140,8 @@ namespace RobloxFiles.Utility
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dictionary mapping materials to their default Friction.<para/>
|
/// A dictionary mapping materials to their default Friction.<para/>
|
||||||
/// NOTE: This only maps materials that have different FrictionWeights. If it isn't in here, assume their FrictionWeight is 1.
|
/// NOTE: This only maps materials that have different FrictionWeights.<para/>
|
||||||
|
/// If it isn't in here, assume their FrictionWeight is 1.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyDictionary<Material, float> FrictionWeightMap = new Dictionary<Material, float>()
|
public static IReadOnlyDictionary<Material, float> FrictionWeightMap = new Dictionary<Material, float>()
|
||||||
{
|
{
|
||||||
|
40
XmlFormat/PropertyTokens/Vector3int16.cs
Normal file
40
XmlFormat/PropertyTokens/Vector3int16.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System.Xml;
|
||||||
|
using RobloxFiles.DataTypes;
|
||||||
|
|
||||||
|
namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||||
|
{
|
||||||
|
public class Vector3int16Token : IXmlPropertyToken
|
||||||
|
{
|
||||||
|
public string Token => "Vector3int16";
|
||||||
|
private static string[] Coords = new string[3] { "X", "Y", "Z" };
|
||||||
|
|
||||||
|
public bool ReadToken(Property property, XmlNode token)
|
||||||
|
{
|
||||||
|
short[] xyz = new short[3];
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
string key = Coords[i];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var coord = token[key];
|
||||||
|
xyz[i] = short.Parse(coord.InnerText);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
short x = xyz[0],
|
||||||
|
y = xyz[1],
|
||||||
|
z = xyz[2];
|
||||||
|
|
||||||
|
property.Type = PropertyType.Vector3int16;
|
||||||
|
property.Value = new Vector3int16(x, y, z);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ using System.Xml;
|
|||||||
|
|
||||||
namespace RobloxFiles.XmlFormat
|
namespace RobloxFiles.XmlFormat
|
||||||
{
|
{
|
||||||
static class XmlDataReader
|
public static class XmlDataReader
|
||||||
{
|
{
|
||||||
public static void ReadProperties(Instance instance, XmlNode propsNode)
|
public static void ReadProperties(Instance instance, XmlNode propsNode)
|
||||||
{
|
{
|
||||||
@ -39,7 +39,7 @@ namespace RobloxFiles.XmlFormat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Instance ReadInstance(XmlNode instNode, ref Dictionary<string, Instance> instances)
|
public static Instance ReadInstance(XmlNode instNode, XmlRobloxFile file = null)
|
||||||
{
|
{
|
||||||
// Process the instance itself
|
// Process the instance itself
|
||||||
if (instNode.Name != "Item")
|
if (instNode.Name != "Item")
|
||||||
@ -54,14 +54,14 @@ namespace RobloxFiles.XmlFormat
|
|||||||
// The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
|
// The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
|
||||||
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
|
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
|
||||||
|
|
||||||
if (refToken != null && instances != null)
|
if (refToken != null && file != null)
|
||||||
{
|
{
|
||||||
string refId = refToken.InnerText;
|
string refId = refToken.InnerText;
|
||||||
|
|
||||||
if (instances.ContainsKey(refId))
|
if (file.Instances.ContainsKey(refId))
|
||||||
throw new Exception("XmlDataReader.ReadItem: Got an Item with a duplicate 'referent' attribute!");
|
throw new Exception("XmlDataReader.ReadItem: Got an Item with a duplicate 'referent' attribute!");
|
||||||
|
|
||||||
instances.Add(refId, inst);
|
file.Instances.Add(refId, inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process the child nodes of this instance.
|
// Process the child nodes of this instance.
|
||||||
@ -73,7 +73,7 @@ namespace RobloxFiles.XmlFormat
|
|||||||
}
|
}
|
||||||
else if (childNode.Name == "Item")
|
else if (childNode.Name == "Item")
|
||||||
{
|
{
|
||||||
Instance child = ReadInstance(childNode, ref instances);
|
Instance child = ReadInstance(childNode, file);
|
||||||
child.Parent = inst;
|
child.Parent = inst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,6 @@ using System.Xml;
|
|||||||
|
|
||||||
namespace RobloxFiles.XmlFormat
|
namespace RobloxFiles.XmlFormat
|
||||||
{
|
{
|
||||||
public interface IXmlPropertyToken
|
|
||||||
{
|
|
||||||
string Token { get; }
|
|
||||||
bool ReadToken(Property prop, XmlNode token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class XmlPropertyTokens
|
public static class XmlPropertyTokens
|
||||||
{
|
{
|
||||||
public static IReadOnlyDictionary<string, IXmlPropertyToken> Handlers;
|
public static IReadOnlyDictionary<string, IXmlPropertyToken> Handlers;
|
||||||
|
@ -47,12 +47,12 @@ namespace RobloxFiles.XmlFormat
|
|||||||
{
|
{
|
||||||
if (child.Name == "Item")
|
if (child.Name == "Item")
|
||||||
{
|
{
|
||||||
Instance item = XmlDataReader.ReadInstance(child, ref Instances);
|
Instance item = XmlDataReader.ReadInstance(child, this);
|
||||||
item.Parent = XmlContents;
|
item.Parent = XmlContents;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve references for Ref properties.
|
// Resolve referent properties.
|
||||||
var refProps = Instances.Values
|
var refProps = Instances.Values
|
||||||
.SelectMany(inst => inst.Properties)
|
.SelectMany(inst => inst.Properties)
|
||||||
.Where(prop => prop.Type == PropertyType.Ref);
|
.Where(prop => prop.Type == PropertyType.Ref);
|
||||||
|
Loading…
Reference in New Issue
Block a user