452 lines
16 KiB
C#
452 lines
16 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
|
|
using RobloxFiles.Enums;
|
|
using RobloxFiles.DataTypes;
|
|
using RobloxFiles.Utility;
|
|
|
|
namespace RobloxFiles.BinaryFormat.Chunks
|
|
{
|
|
public class PROP
|
|
{
|
|
public readonly string Name;
|
|
public readonly int TypeIndex;
|
|
public readonly PropertyType Type;
|
|
|
|
private BinaryRobloxFileReader Reader;
|
|
|
|
public PROP(BinaryRobloxFileChunk chunk)
|
|
{
|
|
Reader = chunk.GetDataReader();
|
|
|
|
TypeIndex = Reader.ReadInt32();
|
|
Name = Reader.ReadString();
|
|
|
|
try
|
|
{
|
|
byte propType = Reader.ReadByte();
|
|
Type = (PropertyType)propType;
|
|
}
|
|
catch
|
|
{
|
|
Type = PropertyType.Unknown;
|
|
}
|
|
}
|
|
|
|
public void ReadProperties(BinaryRobloxFile file)
|
|
{
|
|
INST type = file.Types[TypeIndex];
|
|
Property[] props = new Property[type.NumInstances];
|
|
|
|
int[] ids = type.InstanceIds;
|
|
int instCount = type.NumInstances;
|
|
|
|
for (int i = 0; i < instCount; i++)
|
|
{
|
|
int id = ids[i];
|
|
Instance inst = file.Instances[id];
|
|
|
|
Property prop = new Property(inst, this);
|
|
props[i] = prop;
|
|
|
|
inst.AddProperty(ref prop);
|
|
}
|
|
|
|
// Setup some short-hand functions for actions used during the read procedure.
|
|
var readInts = new Func<int[]>(() => Reader.ReadInts(instCount));
|
|
var readFloats = new Func<float[]>(() => Reader.ReadFloats(instCount));
|
|
|
|
|
|
var loadProperties = new Action<Func<int, object>>(read =>
|
|
{
|
|
for (int i = 0; i < instCount; i++)
|
|
{
|
|
object result = read(i);
|
|
props[i].Value = result;
|
|
}
|
|
});
|
|
|
|
// Read the property data based on the property type.
|
|
switch (Type)
|
|
{
|
|
case PropertyType.String:
|
|
loadProperties(i =>
|
|
{
|
|
string result = Reader.ReadString();
|
|
|
|
// Leave an access point for the original byte sequence, in case this is a BinaryString.
|
|
// This will allow the developer to read the sequence without any mangling from C# strings.
|
|
byte[] buffer = Reader.GetLastStringBuffer();
|
|
props[i].RawBuffer = buffer;
|
|
|
|
return result;
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Bool:
|
|
loadProperties(i => Reader.ReadBoolean());
|
|
break;
|
|
case PropertyType.Int:
|
|
int[] ints = readInts();
|
|
loadProperties(i => ints[i]);
|
|
break;
|
|
case PropertyType.Float:
|
|
float[] floats = readFloats();
|
|
loadProperties(i => floats[i]);
|
|
break;
|
|
case PropertyType.Double:
|
|
loadProperties(i => Reader.ReadDouble());
|
|
break;
|
|
case PropertyType.UDim:
|
|
float[] UDim_Scales = readFloats();
|
|
int[] UDim_Offsets = readInts();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float scale = UDim_Scales[i];
|
|
int offset = UDim_Offsets[i];
|
|
return new UDim(scale, offset);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.UDim2:
|
|
float[] UDim2_Scales_X = readFloats(),
|
|
UDim2_Scales_Y = readFloats();
|
|
|
|
int[] UDim2_Offsets_X = readInts(),
|
|
UDim2_Offsets_Y = readInts();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float scaleX = UDim2_Scales_X[i],
|
|
scaleY = UDim2_Scales_Y[i];
|
|
|
|
int offsetX = UDim2_Offsets_X[i],
|
|
offsetY = UDim2_Offsets_Y[i];
|
|
|
|
return new UDim2(scaleX, offsetX, scaleY, offsetY);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Ray:
|
|
loadProperties(i =>
|
|
{
|
|
float[] rawOrigin = Reader.ReadFloats(3);
|
|
Vector3 origin = new Vector3(rawOrigin);
|
|
|
|
float[] rawDirection = Reader.ReadFloats(3);
|
|
Vector3 direction = new Vector3(rawDirection);
|
|
|
|
return new Ray(origin, direction);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Faces:
|
|
loadProperties(i =>
|
|
{
|
|
byte faces = Reader.ReadByte();
|
|
return (Faces)faces;
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Axes:
|
|
loadProperties(i =>
|
|
{
|
|
byte axes = Reader.ReadByte();
|
|
return (Axes)axes;
|
|
});
|
|
|
|
break;
|
|
case PropertyType.BrickColor:
|
|
int[] BrickColorIds = readInts();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
int number = BrickColorIds[i];
|
|
return BrickColor.FromNumber(number);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Color3:
|
|
float[] Color3_R = readFloats(),
|
|
Color3_G = readFloats(),
|
|
Color3_B = readFloats();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float r = Color3_R[i],
|
|
g = Color3_G[i],
|
|
b = Color3_B[i];
|
|
|
|
return new Color3(r, g, b);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Vector2:
|
|
float[] Vector2_X = readFloats(),
|
|
Vector2_Y = readFloats();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float x = Vector2_X[i],
|
|
y = Vector2_Y[i];
|
|
|
|
return new Vector2(x, y);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Vector3:
|
|
float[] Vector3_X = readFloats(),
|
|
Vector3_Y = readFloats(),
|
|
Vector3_Z = readFloats();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float x = Vector3_X[i],
|
|
y = Vector3_Y[i],
|
|
z = Vector3_Z[i];
|
|
|
|
return new Vector3(x, y, z);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.CFrame:
|
|
case PropertyType.Quaternion:
|
|
// Temporarily load the rotation matrices into their properties.
|
|
// We'll update them to CFrames once we iterate over the position data.
|
|
|
|
loadProperties(i =>
|
|
{
|
|
int normXY = Reader.ReadByte();
|
|
|
|
if (normXY > 0)
|
|
{
|
|
// Make sure this value is in a safe range.
|
|
normXY = (normXY - 1) % 36;
|
|
|
|
NormalId normX = (NormalId)(normXY / 6);
|
|
Vector3 R0 = Vector3.FromNormalId(normX);
|
|
|
|
NormalId normY = (NormalId)(normXY % 6);
|
|
Vector3 R1 = Vector3.FromNormalId(normY);
|
|
|
|
// 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]
|
|
{
|
|
R0.X, R0.Y, R0.Z,
|
|
R1.X, R1.Y, R1.Z,
|
|
R2.X, R2.Y, R2.Z,
|
|
};
|
|
}
|
|
else if (Type == PropertyType.Quaternion)
|
|
{
|
|
float qx = Reader.ReadFloat(), qy = Reader.ReadFloat(),
|
|
qz = Reader.ReadFloat(), qw = Reader.ReadFloat();
|
|
|
|
Quaternion quaternion = new Quaternion(qx, qy, qz, qw);
|
|
var rotation = quaternion.ToCFrame();
|
|
|
|
return rotation.GetComponents();
|
|
}
|
|
else
|
|
{
|
|
float[] matrix = new float[9];
|
|
|
|
for (int m = 0; m < 9; m++)
|
|
{
|
|
float value = Reader.ReadFloat();
|
|
matrix[m] = value;
|
|
}
|
|
|
|
return matrix;
|
|
}
|
|
});
|
|
|
|
float[] CFrame_X = readFloats(),
|
|
CFrame_Y = readFloats(),
|
|
CFrame_Z = readFloats();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float[] matrix = props[i].Value as float[];
|
|
|
|
float x = CFrame_X[i],
|
|
y = CFrame_Y[i],
|
|
z = CFrame_Z[i];
|
|
|
|
float[] position = new float[3] { x, y, z };
|
|
float[] components = position.Concat(matrix).ToArray();
|
|
|
|
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);
|
|
loadProperties(i => enums[i]);
|
|
|
|
break;
|
|
case PropertyType.Ref:
|
|
int[] instIds = Reader.ReadInstanceIds(instCount);
|
|
|
|
loadProperties(i =>
|
|
{
|
|
int instId = instIds[i];
|
|
return instId >= 0 ? file.Instances[instId] : null;
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Vector3int16:
|
|
loadProperties(i =>
|
|
{
|
|
short x = Reader.ReadInt16(),
|
|
y = Reader.ReadInt16(),
|
|
z = Reader.ReadInt16();
|
|
|
|
return new Vector3int16(x, y, z);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.NumberSequence:
|
|
loadProperties(i =>
|
|
{
|
|
int numKeys = Reader.ReadInt32();
|
|
var keypoints = new NumberSequenceKeypoint[numKeys];
|
|
|
|
for (int key = 0; key < numKeys; key++)
|
|
{
|
|
float Time = Reader.ReadFloat(),
|
|
Value = Reader.ReadFloat(),
|
|
Envelope = Reader.ReadFloat();
|
|
|
|
keypoints[key] = new NumberSequenceKeypoint(Time, Value, Envelope);
|
|
}
|
|
|
|
return new NumberSequence(keypoints);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.ColorSequence:
|
|
loadProperties(i =>
|
|
{
|
|
int numKeys = Reader.ReadInt32();
|
|
var keypoints = new ColorSequenceKeypoint[numKeys];
|
|
|
|
for (int key = 0; key < numKeys; key++)
|
|
{
|
|
float Time = Reader.ReadFloat(),
|
|
R = Reader.ReadFloat(),
|
|
G = Reader.ReadFloat(),
|
|
B = Reader.ReadFloat();
|
|
|
|
Color3 Value = new Color3(R, G, B);
|
|
byte[] Reserved = Reader.ReadBytes(4);
|
|
|
|
keypoints[key] = new ColorSequenceKeypoint(Time, Value, Reserved);
|
|
}
|
|
|
|
return new ColorSequence(keypoints);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.NumberRange:
|
|
loadProperties(i =>
|
|
{
|
|
float min = Reader.ReadFloat();
|
|
float max = Reader.ReadFloat();
|
|
|
|
return new NumberRange(min, max);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Rect:
|
|
float[] Rect_X0 = readFloats(), Rect_Y0 = readFloats(),
|
|
Rect_X1 = readFloats(), Rect_Y1 = readFloats();
|
|
|
|
loadProperties(i =>
|
|
{
|
|
float x0 = Rect_X0[i], y0 = Rect_Y0[i],
|
|
x1 = Rect_X1[i], y1 = Rect_Y1[i];
|
|
|
|
return new Rect(x0, y0, x1, y1);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.PhysicalProperties:
|
|
loadProperties(i =>
|
|
{
|
|
bool custom = Reader.ReadBoolean();
|
|
|
|
if (custom)
|
|
{
|
|
float Density = Reader.ReadFloat(),
|
|
Friction = Reader.ReadFloat(),
|
|
Elasticity = Reader.ReadFloat(),
|
|
FrictionWeight = Reader.ReadFloat(),
|
|
ElasticityWeight = Reader.ReadFloat();
|
|
|
|
return new PhysicalProperties
|
|
(
|
|
Density,
|
|
Friction,
|
|
Elasticity,
|
|
FrictionWeight,
|
|
ElasticityWeight
|
|
);
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Color3uint8:
|
|
byte[] Color3uint8_R = Reader.ReadBytes(instCount),
|
|
Color3uint8_G = Reader.ReadBytes(instCount),
|
|
Color3uint8_B = Reader.ReadBytes(instCount);
|
|
|
|
loadProperties(i =>
|
|
{
|
|
byte r = Color3uint8_R[i],
|
|
g = Color3uint8_G[i],
|
|
b = Color3uint8_B[i];
|
|
|
|
return Color3.FromRGB(r, g, b);
|
|
});
|
|
|
|
break;
|
|
case PropertyType.Int64:
|
|
long[] int64s = Reader.ReadInterleaved(instCount, (buffer, start) =>
|
|
{
|
|
long result = BitConverter.ToInt64(buffer, start);
|
|
return (long)((ulong)result >> 1) ^ (-(result & 1));
|
|
});
|
|
|
|
loadProperties(i => int64s[i]);
|
|
break;
|
|
case PropertyType.SharedString:
|
|
uint[] sharedKeys = Reader.ReadUInts(instCount);
|
|
|
|
loadProperties(i =>
|
|
{
|
|
uint key = sharedKeys[i];
|
|
return file.SharedStrings[key];
|
|
});
|
|
|
|
break;
|
|
default:
|
|
Console.WriteLine("Unhandled property type: {0}!", Type);
|
|
break;
|
|
}
|
|
|
|
Reader.Dispose();
|
|
}
|
|
}
|
|
}
|