public void Load(BinaryRobloxFileReader reader) { BinaryRobloxFile file = reader.File; int format = reader.ReadInt32(); int numHashes = reader.ReadInt32(); if (format != FORMAT) { throw new Exception($"Unexpected SSTR format: {format} (expected {FORMAT}!)"); } for (uint id = 0; id < numHashes; id++) { byte[] hash = reader.ReadBytes(16); string key = Convert.ToBase64String(hash); byte[] data = reader.ReadBuffer(); SharedString value = SharedString.FromBuffer(data); Lookup[key] = id; Strings[id] = value; } file.SSTR = this; }
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node) { var value = prop.CastValue <SharedString>(); string key = value.Key; if (value.ComputedKey == null) { var newShared = SharedString.FromBuffer(value.SharedValue); key = newShared.ComputedKey; } node.InnerText = key; }
protected override void ReadFile(byte[] buffer) { try { string xml = Encoding.UTF8.GetString(buffer); var settings = new XmlReaderSettings() { XmlResolver = null }; using (StringReader reader = new StringReader(xml)) { XmlReader xmlReader = XmlReader.Create(reader, settings); XmlDocument.Load(xmlReader); xmlReader.Dispose(); } } catch { throw new Exception("XmlRobloxFile: Could not read provided buffer as XML!"); } XmlNode roblox = XmlDocument.FirstChild; if (roblox != null && roblox.Name == "roblox") { // Verify the version we are using. XmlNode version = roblox.Attributes.GetNamedItem("version"); if (version == null || !int.TryParse(version.Value, out int schemaVersion)) { throw new Exception("XmlRobloxFile: No version number defined!"); } else if (schemaVersion < 4) { throw new Exception("XmlRobloxFile: Provided version must be at least 4!"); } // Process the instances. foreach (XmlNode child in roblox.ChildNodes) { if (child.Name == "Item") { Instance item = XmlRobloxFileReader.ReadInstance(child, this); item.Parent = this; } else if (child.Name == "SharedStrings") { XmlRobloxFileReader.ReadSharedStrings(child, this); } else if (child.Name == "Meta") { XmlRobloxFileReader.ReadMetadata(child, this); } } // Query the properties. var allProps = Instances.Values .SelectMany(inst => inst.Properties) .Select(pair => pair.Value); // Resolve referent properties. var refProps = allProps.Where(prop => prop.Type == PropertyType.Ref); foreach (Property refProp in refProps) { string refId = refProp.XmlToken; refProp.XmlToken = "Ref"; if (Instances.ContainsKey(refId)) { Instance refInst = Instances[refId]; refProp.Value = refInst; } else if (refId != "null") { LogError($"XmlRobloxFile: Could not resolve reference for {refProp.GetFullName()}"); refProp.Value = null; } } // Record shared strings. var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString); foreach (Property sharedProp in sharedProps) { SharedString shared = sharedProp.CastValue <SharedString>(); if (shared == null) { var nullBuffer = Array.Empty <byte>(); shared = SharedString.FromBuffer(nullBuffer); sharedProp.Value = shared; } SharedStrings.Add(shared.Key); } } else { throw new Exception("XmlRobloxFile: No 'roblox' element found!"); } }
public void Save(BinaryRobloxFileWriter writer) { BinaryRobloxFile file = writer.File; File = file; INST inst = file.Classes[ClassIndex]; var props = new List <Property>(); foreach (int instId in inst.InstanceIds) { Instance instance = file.Instances[instId]; var instProps = instance.Properties; if (!instProps.TryGetValue(Name, out Property prop)) { throw new Exception($"Property {Name} must be defined in {instance.GetFullName()}!"); } else if (prop.Type != Type) { throw new Exception($"Property {Name} is not using the correct type in {instance.GetFullName()}!"); } props.Add(prop); } writer.Write(ClassIndex); writer.WriteString(Name); writer.Write(TypeId); switch (Type) { case PropertyType.String: props.ForEach(prop => { byte[] buffer = prop.HasRawBuffer ? prop.RawBuffer : null; if (buffer == null) { string value = prop.CastValue <string>(); buffer = Encoding.UTF8.GetBytes(value); } writer.Write(buffer.Length); writer.Write(buffer); }); break; case PropertyType.Bool: { props.ForEach(prop => { bool value = prop.CastValue <bool>(); writer.Write(value); }); break; } case PropertyType.Int: { var ints = props .Select(prop => prop.CastValue <int>()) .ToList(); writer.WriteInts(ints); break; } case PropertyType.Float: { var floats = props .Select(prop => prop.CastValue <float>()) .ToList(); writer.WriteFloats(floats); break; } case PropertyType.Double: { props.ForEach(prop => { double value = prop.CastValue <double>(); writer.Write(BinaryRobloxFileWriter.GetBytes(value)); }); break; } case PropertyType.UDim: { var UDim_Scales = new List <float>(); var UDim_Offsets = new List <int>(); props.ForEach(prop => { UDim value = prop.CastValue <UDim>(); UDim_Scales.Add(value.Scale); UDim_Offsets.Add(value.Offset); }); writer.WriteFloats(UDim_Scales); writer.WriteInts(UDim_Offsets); break; } case PropertyType.UDim2: { var UDim2_Scales_X = new List <float>(); var UDim2_Scales_Y = new List <float>(); var UDim2_Offsets_X = new List <int>(); var UDim2_Offsets_Y = new List <int>(); props.ForEach(prop => { UDim2 value = prop.CastValue <UDim2>(); UDim2_Scales_X.Add(value.X.Scale); UDim2_Scales_Y.Add(value.Y.Scale); UDim2_Offsets_X.Add(value.X.Offset); UDim2_Offsets_Y.Add(value.Y.Offset); }); writer.WriteFloats(UDim2_Scales_X); writer.WriteFloats(UDim2_Scales_Y); writer.WriteInts(UDim2_Offsets_X); writer.WriteInts(UDim2_Offsets_Y); break; } case PropertyType.Ray: { props.ForEach(prop => { Ray ray = prop.CastValue <Ray>(); Vector3 pos = ray.Origin; writer.Write(pos.X); writer.Write(pos.Y); writer.Write(pos.Z); Vector3 dir = ray.Direction; writer.Write(dir.X); writer.Write(dir.Y); writer.Write(dir.Z); }); break; } case PropertyType.Faces: case PropertyType.Axes: { props.ForEach(prop => { byte value = prop.CastValue <byte>(); writer.Write(value); }); break; } case PropertyType.BrickColor: { var brickColorIds = props .Select(prop => prop.CastValue <BrickColor>()) .Select(value => value.Number) .ToList(); writer.WriteInts(brickColorIds); break; } case PropertyType.Color3: { var Color3_R = new List <float>(); var Color3_G = new List <float>(); var Color3_B = new List <float>(); props.ForEach(prop => { Color3 value = prop.CastValue <Color3>(); Color3_R.Add(value.R); Color3_G.Add(value.G); Color3_B.Add(value.B); }); writer.WriteFloats(Color3_R); writer.WriteFloats(Color3_G); writer.WriteFloats(Color3_B); break; } case PropertyType.Vector2: { var Vector2_X = new List <float>(); var Vector2_Y = new List <float>(); props.ForEach(prop => { Vector2 value = prop.CastValue <Vector2>(); Vector2_X.Add(value.X); Vector2_Y.Add(value.Y); }); writer.WriteFloats(Vector2_X); writer.WriteFloats(Vector2_Y); break; } case PropertyType.Vector3: { var Vector3_X = new List <float>(); var Vector3_Y = new List <float>(); var Vector3_Z = new List <float>(); props.ForEach(prop => { Vector3 value = prop.CastValue <Vector3>(); Vector3_X.Add(value.X); Vector3_Y.Add(value.Y); Vector3_Z.Add(value.Z); }); writer.WriteFloats(Vector3_X); writer.WriteFloats(Vector3_Y); writer.WriteFloats(Vector3_Z); break; } case PropertyType.CFrame: case PropertyType.Quaternion: case PropertyType.OptionalCFrame: { var CFrame_X = new List <float>(); var CFrame_Y = new List <float>(); var CFrame_Z = new List <float>(); if (Type == PropertyType.OptionalCFrame) { writer.Write((byte)PropertyType.CFrame); } props.ForEach(prop => { CFrame value = null; if (prop.Value is Quaternion q) { value = q.ToCFrame(); } else { value = prop.CastValue <CFrame>(); } if (value == null) { value = new CFrame(); } Vector3 pos = value.Position; CFrame_X.Add(pos.X); CFrame_Y.Add(pos.Y); CFrame_Z.Add(pos.Z); int orientId = value.GetOrientId(); writer.Write((byte)(orientId + 1)); if (orientId == -1) { if (Type == PropertyType.Quaternion) { Quaternion quat = new Quaternion(value); writer.Write(quat.X); writer.Write(quat.Y); writer.Write(quat.Z); writer.Write(quat.W); } else { float[] components = value.GetComponents(); for (int i = 3; i < 12; i++) { float component = components[i]; writer.Write(component); } } } }); writer.WriteFloats(CFrame_X); writer.WriteFloats(CFrame_Y); writer.WriteFloats(CFrame_Z); if (Type == PropertyType.OptionalCFrame) { writer.Write((byte)PropertyType.Bool); props.ForEach(prop => { if (prop.Value is null) { writer.Write(false); return; } if (prop.Value is Optional <CFrame> optional) { writer.Write(optional.HasValue); return; } var cf = prop.Value as CFrame; writer.Write(cf != null); }); } break; } case PropertyType.Enum: { var enums = new List <uint>(); props.ForEach(prop => { if (prop.Value is uint raw) { enums.Add(raw); return; } int signed = (int)prop.Value; uint value = (uint)signed; enums.Add(value); }); writer.WriteInterleaved(enums); break; } case PropertyType.Ref: { var InstanceIds = new List <int>(); props.ForEach(prop => { int referent = -1; if (prop.Value != null) { Instance value = prop.CastValue <Instance>(); if (value.IsDescendantOf(File)) { string refValue = value.Referent; int.TryParse(refValue, out referent); } } InstanceIds.Add(referent); }); writer.WriteInstanceIds(InstanceIds); break; } case PropertyType.Vector3int16: { props.ForEach(prop => { Vector3int16 value = prop.CastValue <Vector3int16>(); writer.Write(value.X); writer.Write(value.Y); writer.Write(value.Z); }); break; } case PropertyType.NumberSequence: { props.ForEach(prop => { NumberSequence value = prop.CastValue <NumberSequence>(); var keyPoints = value.Keypoints; writer.Write(keyPoints.Length); foreach (var keyPoint in keyPoints) { writer.Write(keyPoint.Time); writer.Write(keyPoint.Value); writer.Write(keyPoint.Envelope); } }); break; } case PropertyType.ColorSequence: { props.ForEach(prop => { ColorSequence value = prop.CastValue <ColorSequence>(); var keyPoints = value.Keypoints; writer.Write(keyPoints.Length); foreach (var keyPoint in keyPoints) { Color3 color = keyPoint.Value; writer.Write(keyPoint.Time); writer.Write(color.R); writer.Write(color.G); writer.Write(color.B); writer.Write(keyPoint.Envelope); } }); break; } case PropertyType.NumberRange: { props.ForEach(prop => { NumberRange value = prop.CastValue <NumberRange>(); writer.Write(value.Min); writer.Write(value.Max); }); break; } case PropertyType.Rect: { var Rect_X0 = new List <float>(); var Rect_Y0 = new List <float>(); var Rect_X1 = new List <float>(); var Rect_Y1 = new List <float>(); props.ForEach(prop => { Rect value = prop.CastValue <Rect>(); Vector2 min = value.Min; Rect_X0.Add(min.X); Rect_Y0.Add(min.Y); Vector2 max = value.Max; Rect_X1.Add(max.X); Rect_Y1.Add(max.Y); }); writer.WriteFloats(Rect_X0); writer.WriteFloats(Rect_Y0); writer.WriteFloats(Rect_X1); writer.WriteFloats(Rect_Y1); break; } case PropertyType.PhysicalProperties: { props.ForEach(prop => { bool custom = (prop.Value != null); writer.Write(custom); if (custom) { PhysicalProperties value = prop.CastValue <PhysicalProperties>(); writer.Write(value.Density); writer.Write(value.Friction); writer.Write(value.Elasticity); writer.Write(value.FrictionWeight); writer.Write(value.ElasticityWeight); } }); break; } case PropertyType.Color3uint8: { var Color3uint8_R = new List <byte>(); var Color3uint8_G = new List <byte>(); var Color3uint8_B = new List <byte>(); props.ForEach(prop => { Color3uint8 value = prop.CastValue <Color3uint8>(); Color3uint8_R.Add(value.R); Color3uint8_G.Add(value.G); Color3uint8_B.Add(value.B); }); 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: { var longs = new List <long>(); props.ForEach(prop => { long value = prop.CastValue <long>(); longs.Add(value); }); writer.WriteInterleaved(longs, value => { // Move the sign bit to the front. return((value << 1) ^ (value >> 63)); }); break; } case PropertyType.SharedString: { var sharedKeys = new List <uint>(); SSTR sstr = file.SSTR; if (sstr == null) { sstr = new SSTR(); file.SSTR = sstr; } props.ForEach(prop => { var shared = prop.CastValue <SharedString>(); if (shared == null) { byte[] empty = Array.Empty <byte>(); shared = SharedString.FromBuffer(empty); } string key = shared.Key; if (!sstr.Lookup.ContainsKey(key)) { uint id = (uint)sstr.Lookup.Count; sstr.Strings.Add(id, shared); sstr.Lookup.Add(key, id); } uint hashId = sstr.Lookup[key]; sharedKeys.Add(hashId); }); writer.WriteInterleaved(sharedKeys); break; } case PropertyType.ProtectedString: { props.ForEach(prop => { var protect = prop.CastValue <ProtectedString>(); byte[] buffer = protect.RawBuffer; writer.Write(buffer.Length); writer.Write(buffer); }); break; } case PropertyType.UniqueId: { props.ForEach(prop => { var guid = prop.CastValue <Guid>(); byte[] buffer = guid.ToByteArray(); writer.Write(buffer); }); break; } default: { RobloxFile.LogError($"Unhandled property type: {Type} in {this}!"); break; } } }
public static XmlNode WriteProperty(Property prop, XmlDocument doc, XmlRobloxFile file) { if (prop.Name == "Archivable") { return(null); } string propType = prop.XmlToken; if (propType == null) { propType = ""; } if (propType.Length == 0) { propType = GetEnumName(prop.Type); switch (prop.Type) { case PropertyType.CFrame: case PropertyType.Quaternion: propType = "CoordinateFrame"; break; case PropertyType.Enum: propType = "token"; break; case PropertyType.Rect: propType = "Rect2D"; break; case PropertyType.Int: case PropertyType.Bool: case PropertyType.Float: case PropertyType.Int64: case PropertyType.Double: propType = propType.ToLower(CultureInfo.InvariantCulture); break; case PropertyType.String: propType = (prop.HasRawBuffer ? "BinaryString" : "string"); break; default: break; } } IXmlPropertyToken handler = XmlPropertyTokens.GetHandler(propType); if (handler == null) { if (RobloxFile.LogErrors) { Console.Error.WriteLine("XmlDataWriter.WriteProperty: No token handler found for property type: {0}", propType); } return(null); } if (prop.Type == PropertyType.SharedString) { SharedString str = prop.CastValue <SharedString>(); if (str == null) { byte[] value = prop.CastValue <byte[]>(); str = SharedString.FromBuffer(value); } if (str.ComputedKey == null) { var newShared = SharedString.FromBuffer(str.SharedValue); str.Key = newShared.ComputedKey; } file.SharedStrings.Add(str.Key); } XmlElement propElement = doc.CreateElement(propType); propElement.SetAttribute("name", prop.Name); XmlNode propNode = propElement; handler.WriteProperty(prop, doc, propNode); if (propNode.ParentNode != null) { XmlNode newNode = propNode.ParentNode; newNode.RemoveChild(propNode); propNode = newNode; } return(propNode); }