public bool ReadProperty(Property prop, XmlNode token) { if (XmlPropertyTokens.ReadPropertyGeneric(token, out uint value)) { uint r = (value >> 16) & 0xFF; uint g = (value >> 8) & 0xFF; uint b = value & 0xFF; Color3uint8 result = Color3.FromRGB(r, g, b); prop.Value = result; return(true); } return(false); }
public void Load(BinaryRobloxFileReader reader) { File = reader.File; ClassIndex = reader.ReadInt32(); Name = reader.ReadString(); try { byte propType = reader.ReadByte(); Type = (PropertyType)propType; } catch (EndOfStreamException) { RobloxFile.LogError($"Got corrupted PROP chunk (@ {this})!"); return; } if (Class == null) { RobloxFile.LogError($"Unknown class index {ClassIndex} (@ {this})!"); return; } var ids = Class.InstanceIds; int instCount = Class.NumInstances; var props = new Property[instCount]; for (int i = 0; i < instCount; i++) { int id = ids[i]; Instance instance = File.Instances[id]; if (instance == null) { RobloxFile.LogError($"PROP: No instance @{id} for property {ClassName}.{Name}"); continue; } var prop = new Property(instance, this); props[i] = prop; instance.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 readProperties = new Action <Func <int, object> >(read => { for (int i = 0; i < instCount; i++) { var prop = props[i]; if (prop == null) { continue; } prop.Value = read(i); } }); switch (Type) { case PropertyType.String: { readProperties(i => { 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. switch (Name) { case "Tags": case "AttributesSerialize": { return(buffer); } default: { Property prop = props[i]; Instance instance = prop.Instance; Type instType = instance.GetType(); var member = ImplicitMember.Get(instType, Name); if (member != null) { object result = value; Type memberType = member.MemberType; if (memberType == typeof(byte[])) { result = buffer; } return(result); } return(value); } } }); break; } case PropertyType.Bool: { readProperties(i => reader.ReadBoolean()); break; } case PropertyType.Int: { int[] ints = readInts(); readProperties(i => ints[i]); break; } case PropertyType.Float: { float[] floats = readFloats(); readProperties(i => floats[i]); break; } case PropertyType.Double: { readProperties(i => reader.ReadDouble()); break; } case PropertyType.UDim: { float[] UDim_Scales = readFloats(); int[] UDim_Offsets = readInts(); readProperties(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(); readProperties(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: { readProperties(i => { float posX = reader.ReadFloat(), posY = reader.ReadFloat(), posZ = reader.ReadFloat(); float dirX = reader.ReadFloat(), dirY = reader.ReadFloat(), dirZ = reader.ReadFloat(); var origin = new Vector3(posX, posY, posZ); var direction = new Vector3(dirX, dirY, dirZ); return(new Ray(origin, direction)); }); break; } case PropertyType.Faces: { readProperties(i => { byte faces = reader.ReadByte(); return((Faces)faces); }); break; } case PropertyType.Axes: { readProperties(i => { byte axes = reader.ReadByte(); return((Axes)axes); }); break; } case PropertyType.BrickColor: { int[] BrickColorIds = readInts(); readProperties(i => { BrickColor color = BrickColorIds[i]; return(color); }); break; } case PropertyType.Color3: { float[] Color3_R = readFloats(), Color3_G = readFloats(), Color3_B = readFloats(); readProperties(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(); readProperties(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(); readProperties(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: case PropertyType.OptionalCFrame: { float[][] matrices = new float[instCount][]; if (Type == PropertyType.OptionalCFrame) { byte cframeType = (byte)PropertyType.CFrame; byte readType = reader.ReadByte(); if (readType != cframeType) { RobloxFile.LogError($"Unexpected property type in OptionalCFrame (expected {cframeType}, got {readType})"); readProperties(i => null); break; } } for (int i = 0; i < instCount; i++) { byte rawOrientId = reader.ReadByte(); if (rawOrientId > 0) { // Make sure this value is in a safe range. int orientId = (rawOrientId - 1) % 36; NormalId xColumn = (NormalId)(orientId / 6); Vector3 R0 = Vector3.FromNormalId(xColumn); NormalId yColumn = (NormalId)(orientId % 6); Vector3 R1 = Vector3.FromNormalId(yColumn); // Compute R2 using the cross product of R0 and R1. Vector3 R2 = R0.Cross(R1); // Generate the rotation matrix. matrices[i] = 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(); var quaternion = new Quaternion(qx, qy, qz, qw); var rotation = quaternion.ToCFrame(); matrices[i] = rotation.GetComponents(); } else { float[] matrix = new float[9]; for (int m = 0; m < 9; m++) { float value = reader.ReadFloat(); matrix[m] = value; } matrices[i] = matrix; } } float[] CFrame_X = readFloats(), CFrame_Y = readFloats(), CFrame_Z = readFloats(); var CFrames = new CFrame[instCount]; for (int i = 0; i < instCount; i++) { 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 }; components = position.Concat(matrix).ToArray(); } CFrames[i] = new CFrame(components); } if (Type == PropertyType.OptionalCFrame) { byte boolType = (byte)PropertyType.Bool; byte readType = reader.ReadByte(); if (readType != boolType) { RobloxFile.LogError($"Unexpected property type in OptionalCFrame (expected {boolType}, got {readType})"); readProperties(i => null); break; } for (int i = 0; i < instCount; i++) { CFrame cf = CFrames[i]; bool archivable = reader.ReadBoolean(); if (!archivable) { cf = null; } CFrames[i] = new Optional <CFrame>(cf); } } readProperties(i => CFrames[i]); break; } case PropertyType.Enum: { uint[] enums = reader.ReadUInts(instCount); readProperties(i => { Property prop = props[i]; Instance instance = prop.Instance; Type instType = instance.GetType(); uint value = enums[i]; try { var info = ImplicitMember.Get(instType, Name); if (info == null) { RobloxFile.LogError($"Enum cast failed for {ClassName}.{Name} using value {value}!"); return(value); } return(Enum.Parse(info.MemberType, value.ToInvariantString())); } catch { RobloxFile.LogError($"Enum cast failed for {ClassName}.{Name} using value {value}!"); return(value); } }); break; } case PropertyType.Ref: { var instIds = reader.ReadInstanceIds(instCount); readProperties(i => { int instId = instIds[i]; if (instId >= File.NumInstances) { RobloxFile.LogError($"Got out of bounds referent index in {ClassName}.{Name}!"); return(null); } return(instId >= 0 ? File.Instances[instId] : null); }); break; } case PropertyType.Vector3int16: { readProperties(i => { short x = reader.ReadInt16(), y = reader.ReadInt16(), z = reader.ReadInt16(); return(new Vector3int16(x, y, z)); }); break; } case PropertyType.NumberSequence: { readProperties(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: { readProperties(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); int Envelope = reader.ReadInt32(); keypoints[key] = new ColorSequenceKeypoint(Time, Value, Envelope); } return(new ColorSequence(keypoints)); }); break; } case PropertyType.NumberRange: { readProperties(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(); readProperties(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: { readProperties(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); readProperties(i => { byte r = Color3uint8_R[i], g = Color3uint8_G[i], b = Color3uint8_B[i]; Color3uint8 result = Color3.FromRGB(r, g, b); return(result); }); break; } case PropertyType.Int64: { long[] longs = reader.ReadInterleaved(instCount, (buffer, start) => { long result = BitConverter.ToInt64(buffer, start); return((long)((ulong)result >> 1) ^ (-(result & 1))); }); readProperties(i => longs[i]); break; } case PropertyType.SharedString: { uint[] SharedKeys = reader.ReadUInts(instCount); readProperties(i => { uint key = SharedKeys[i]; return(File.SharedStrings[key]); }); break; } case PropertyType.ProtectedString: { readProperties(i => { int length = reader.ReadInt32(); byte[] buffer = reader.ReadBytes(length); return(new ProtectedString(buffer)); }); break; } case PropertyType.UniqueId: { readProperties(i => { var buffer = reader.ReadBytes(16); return(new Guid(buffer)); }); break; } default: { RobloxFile.LogError($"Unhandled property type: {Type} in {this}!"); break; } } reader.Dispose(); }
static void Main(string[] args) { Console.BackgroundColor = ConsoleColor.DarkBlue; Console.Clear(); Console.WriteLine("> Welcome to plentiBlox"); bool success = true; // Choose image path string imagePath = ""; while (imagePath == "") { Console.WriteLine("> Locate the image you want to port over to Roblox"); Console.WriteLine("> Example: C:\\Users\\Username\\Pictures\\image.png"); string tempPath = Console.ReadLine(); if (File.Exists(tempPath) == true) { if (Path.GetExtension(tempPath) == ".png" || Path.GetExtension(tempPath) == ".jpg" || Path.GetExtension(tempPath) == ".jpeg" || Path.GetExtension(tempPath) == ".bmp") { imagePath = tempPath; } else { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! The file you located isn't an image (or it is but doesn't use a supported format)"); } } else { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! The image you located doesn't exist"); } Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; } //Choose path where to save the Roblox place file string rbxlPath = ""; while (rbxlPath == "") { Console.WriteLine("> Locate where you would like the Roblox place file to be saved"); Console.WriteLine("> Example: C:\\Users\\Username\\Documents\\place.rbxl"); string tempPath = Console.ReadLine(); if (File.Exists(tempPath) == false) { if (Path.GetExtension(tempPath) == ".rbxl") { rbxlPath = tempPath; } else { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! You must locate a file with the .rbxl extension"); } } else { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! The file located already exists"); } Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; } // Choose compression level int compressionLevel = -1; int colorTolerance = 0; while (compressionLevel == -1) { Console.WriteLine("> Choose compression level"); Console.WriteLine(" > 0 - Uncompressed (Not recommended, use only if you have to edit individual pixels in Studio)"); Console.WriteLine(" > 1 - Lossless compression (Compresses groups of pixels into bigger frames)"); Console.WriteLine(" > 2 - Low lossy compression (Makes 2 similar colors next to eachother turn into 1 color) [Color Tolerance Level 5]"); Console.WriteLine(" > 3 - Medium lossy compression (Low lossy compression but more tolerant) [Color Tolerance Level 20]"); Console.WriteLine(" > 4 - High lossy compression (Medium lossy compression but more tolerant) [Color Tolerance Level 50]"); Console.WriteLine(" > 5 - Custom lossy compression (Set color tolerance level to whatever you want)"); int tempLevel = Convert.ToInt16(Console.ReadLine()); if (tempLevel >= 0 && tempLevel <= 5) { compressionLevel = tempLevel; if (compressionLevel == 2) { colorTolerance = 5; } else if (compressionLevel == 3) { colorTolerance = 20; } else if (compressionLevel == 4) { colorTolerance = 50; } else if (compressionLevel == 5) { Console.WriteLine("> Choose your color tolerance level:"); tempLevel = Convert.ToInt32(Console.ReadLine()); colorTolerance = tempLevel; } } else { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! You must choose a number from 0 to 5"); } Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; } Console.WriteLine("> Reading image file..."); Bitmap bitmap = new Bitmap(imagePath); Console.WriteLine("> Creating Roblox place file..."); // Create Roblox place file and such BinaryRobloxFile testFile = new BinaryRobloxFile(); StarterGui starterGui = new StarterGui(); starterGui.Parent = testFile; ScreenGui screenGui = new ScreenGui(); screenGui.Name = "plentiBlox"; screenGui.Parent = starterGui; Frame imageFrame = new Frame(); imageFrame.Name = "Image"; imageFrame.Size = new UDim2(0, bitmap.Width, 0, bitmap.Height); imageFrame.BorderSizePixel = 0; imageFrame.BackgroundTransparency = 1; imageFrame.Parent = screenGui; Console.WriteLine("> Porting over image..."); // Create frames for each pixel/group of pixels depending on the compression level chosen Frame[,] pixelsContainer = new Frame[bitmap.Width, bitmap.Height]; for (int i = 0; i < bitmap.Width; ++i) { for (int j = 0; j < bitmap.Height; ++j) { Color pixelColor = bitmap.GetPixel(i, j); if (pixelColor.A == 0) { continue; } if (i > 0 && compressionLevel >= 1) { Color previousPixelColor = bitmap.GetPixel(i - 1, j); if (previousPixelColor == pixelColor || isCloseColor(previousPixelColor, pixelColor, colorTolerance) == true) { if (colorTolerance > 0) { Color avgColor = getAverageColor(pixelColor, previousPixelColor); pixelsContainer[i - 1, j].BackgroundColor3 = Color3.FromRGB(avgColor.R, avgColor.G, avgColor.B); pixelsContainer[i - 1, j].BackgroundTransparency = 1f - (float)avgColor.A / 255f; } pixelsContainer[i - 1, j].Size = new UDim2(0, pixelsContainer[i - 1, j].Size.X.Offset + 1, 0, pixelsContainer[i - 1, j].Size.Y.Offset); pixelsContainer[i, j] = pixelsContainer[i - 1, j]; continue; } } Frame pixel = new Frame(); pixel.Name = "Pixel(" + i + "," + j + ")"; pixel.BorderSizePixel = 0; pixel.Size = new UDim2(0, 1, 0, 1); pixel.Position = new UDim2(0, i, 0, j); pixel.BackgroundColor3 = Color3.FromRGB(pixelColor.R, pixelColor.G, pixelColor.B); pixel.BackgroundTransparency = 1f - (float)pixelColor.A / 255f; pixel.Parent = imageFrame; pixelsContainer[i, j] = pixel; } } if (compressionLevel >= 1) { for (int i = 0; i < bitmap.Width; ++i) { for (int j = 0; j < bitmap.Height - 1; ++j) { if (pixelsContainer[i, j] != null && pixelsContainer[i, j + 1] != null && i == pixelsContainer[i, j].Position.X.Offset && pixelsContainer[i, j].Position.X.Offset == pixelsContainer[i, j + 1].Position.X.Offset && pixelsContainer[i, j].Size.X.Offset == pixelsContainer[i, j + 1].Size.X.Offset) { Color pixelColor = bitmap.GetPixel(i, j); Color nextPixelColor = bitmap.GetPixel(i, j + 1); if (pixelColor != nextPixelColor || isCloseColor(nextPixelColor, pixelColor, colorTolerance) == false) { continue; } if (colorTolerance > 0) { Color avgColor = getAverageColor(pixelColor, nextPixelColor); pixelsContainer[i, j].BackgroundColor3 = Color3.FromRGB(avgColor.R, avgColor.G, avgColor.B); pixelsContainer[i, j].BackgroundTransparency = 1f - (float)avgColor.A / 255f; } pixelsContainer[i, j].Size = new UDim2(0, pixelsContainer[i, j].Size.X.Offset, 0, pixelsContainer[i, j].Size.Y.Offset + 1); pixelsContainer[i, j + 1].Parent = null; pixelsContainer[i, j + 1] = pixelsContainer[i, j]; } } } } Console.WriteLine("> Saving Roblox place file..."); //Catching exception just in case saving isn't allowed try { FileStream stream = File.OpenWrite(rbxlPath); testFile.Save(stream); } catch (Exception e) { success = false; Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! " + e.Message); Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; } // End of the program if (success == true) { Console.BackgroundColor = ConsoleColor.DarkGreen; Console.WriteLine("> Done! Press any key to open the Roblox place file"); Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ReadKey(); Process.Start(rbxlPath); } else { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.Black; Console.WriteLine("! Porting failed! Please try again."); Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("> Press any key to exit"); Console.ReadKey(); } }
public void Load(BinaryRobloxFileReader reader) { BinaryRobloxFile file = reader.File; ClassIndex = reader.ReadInt32(); Name = reader.ReadString(); byte propType = reader.ReadByte(); Type = (PropertyType)propType; 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 instance = file.Instances[id]; Property prop = new Property(instance, this); props[i] = prop; instance.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 readProperties = new Action <Func <int, object> >(read => { for (int i = 0; i < instCount; i++) { object result = read(i); props[i].Value = result; } }); switch (Type) { case PropertyType.String: readProperties(i => { 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. if (Name == "AttributesSerialize") { return(buffer); } 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; case PropertyType.Bool: readProperties(i => reader.ReadBoolean()); break; case PropertyType.Int: int[] ints = readInts(); readProperties(i => ints[i]); break; case PropertyType.Float: float[] floats = readFloats(); readProperties(i => floats[i]); break; case PropertyType.Double: readProperties(i => reader.ReadDouble()); break; case PropertyType.UDim: float[] UDim_Scales = readFloats(); int[] UDim_Offsets = readInts(); readProperties(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(); readProperties(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: readProperties(i => { float posX = reader.ReadFloat(), posY = reader.ReadFloat(), posZ = reader.ReadFloat(); float dirX = reader.ReadFloat(), dirY = reader.ReadFloat(), dirZ = reader.ReadFloat(); Vector3 origin = new Vector3(posX, posY, posZ); Vector3 direction = new Vector3(dirX, dirY, dirZ); return(new Ray(origin, direction)); }); break; case PropertyType.Faces: readProperties(i => { byte faces = reader.ReadByte(); return((Faces)faces); }); break; case PropertyType.Axes: readProperties(i => { byte axes = reader.ReadByte(); return((Axes)axes); }); break; case PropertyType.BrickColor: int[] BrickColorIds = readInts(); readProperties(i => { int number = BrickColorIds[i]; return(BrickColor.FromNumber(number)); }); break; case PropertyType.Color3: float[] Color3_R = readFloats(), Color3_G = readFloats(), Color3_B = readFloats(); readProperties(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(); readProperties(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(); readProperties(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. float[][] matrices = new float[instCount][]; for (int i = 0; i < instCount; i++) { byte rawOrientId = reader.ReadByte(); if (rawOrientId > 0) { // Make sure this value is in a safe range. int orientId = (rawOrientId - 1) % 36; NormalId xColumn = (NormalId)(orientId / 6); Vector3 R0 = Vector3.FromNormalId(xColumn); NormalId yColumn = (NormalId)(orientId % 6); Vector3 R1 = Vector3.FromNormalId(yColumn); // Compute R2 using the cross product of R0 and R1. Vector3 R2 = R0.Cross(R1); // Generate the rotation matrix. matrices[i] = 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(); matrices[i] = rotation.GetComponents(); } else { float[] matrix = new float[9]; for (int m = 0; m < 9; m++) { float value = reader.ReadFloat(); matrix[m] = value; } matrices[i] = matrix; } } float[] CFrame_X = readFloats(), CFrame_Y = readFloats(), CFrame_Z = readFloats(); readProperties(i => { 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 }; components = position.Concat(matrix).ToArray(); } return(new CFrame(components)); }); break; case PropertyType.Enum: uint[] enums = reader.ReadUInts(instCount); 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}!"); pretty annoying for output return(value); } }); break; case PropertyType.Ref: var instIds = reader.ReadInstanceIds(instCount); readProperties(i => { int instId = instIds[i]; return(instId >= 0 ? file.Instances[instId] : null); }); break; case PropertyType.Vector3int16: readProperties(i => { short x = reader.ReadInt16(), y = reader.ReadInt16(), z = reader.ReadInt16(); return(new Vector3int16(x, y, z)); }); break; case PropertyType.NumberSequence: readProperties(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: readProperties(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); int Envelope = reader.ReadInt32(); keypoints[key] = new ColorSequenceKeypoint(Time, Value, Envelope); } return(new ColorSequence(keypoints)); }); break; case PropertyType.NumberRange: readProperties(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(); readProperties(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: readProperties(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); readProperties(i => { byte r = Color3uint8_R[i], g = Color3uint8_G[i], b = Color3uint8_B[i]; Color3uint8 result = Color3.FromRGB(r, g, b); return(result); }); break; case PropertyType.Int64: long[] Int64s = reader.ReadInterleaved(instCount, (buffer, start) => { long result = BitConverter.ToInt64(buffer, start); return((long)((ulong)result >> 1) ^ (-(result & 1))); }); readProperties(i => Int64s[i]); break; case PropertyType.SharedString: uint[] SharedKeys = reader.ReadUInts(instCount); readProperties(i => { uint key = SharedKeys[i]; return(file.SharedStrings[key]); }); break; case PropertyType.ProtectedString: readProperties(i => { int length = reader.ReadInt32(); byte[] buffer = reader.ReadBytes(length); return(new ProtectedString(buffer)); }); break; default: Console.Error.WriteLine("Unhandled property type: {0}!", Type); break; // } reader.Dispose(); }