public void Save(Stream stream) { var writer = new EndianAwareBinaryWriter(stream); var serializer = new RobloxSerializer(this); ReferentProvider.ClearCache(); // Clearing existing referent cache guarantees that referents won't be fragmented var instances = GetChildFirstInstanceEnumerator().ToArray(); var typeGroups = instances.GroupBy(n => n.ClassName).OrderBy(n => n.Key).ToDictionary(n => n.Key, n => n.ToArray()); var typeCount = typeGroups.Count; var objectCount = typeGroups.Aggregate(0, (acc, pair) => acc + pair.Value.Length); writer.WriteBytes(Signatures.Signature); // File signature writer.WriteInt32(typeCount); // Generic header values writer.WriteInt32(objectCount); writer.WriteInt32(0); // Reserved writer.WriteInt32(0); // Reserved // Write type headers var typeHeaders = new TypeHeader[typeCount]; var nextTypeId = 0; foreach (var typeGroup in typeGroups) { var typeHeader = new TypeHeader(typeGroup.Key, nextTypeId, typeGroup.Value.Select(n => ReferentProvider.GetReferent(n)).ToArray()); if (IsSingleton(typeGroup)) { typeHeader.AdditionalData = new byte[typeHeader.InstanceCount]; for (var i = 0; i < typeHeader.InstanceCount; i++) { typeHeader.AdditionalData[i] = 0x1; } } typeHeaders[nextTypeId] = typeHeader; var bytes = typeHeader.Serialize(); writer.WriteBytes(Signatures.TypeHeaderSignature); RobloxLZ4.WriteBlock(stream, bytes); nextTypeId++; } // Write property data foreach (var typeGroup in typeGroups) { var typeHeader = typeHeaders.First(n => n.Name == typeGroup.Key); var instanceTypes = serializer.GetUniqueProperties(typeGroup.Value); var propertyBlocks = instanceTypes.Select(propertyDescriptor => serializer.FillPropertyBlock(propertyDescriptor.Name, propertyDescriptor.Type, typeHeader.TypeId, typeGroup.Value, ReferentProvider)).ToList(); foreach (var propertyBlock in propertyBlocks) { var bytes = propertyBlock.Serialize(); writer.WriteBytes(Signatures.PropBlockSignature); RobloxLZ4.WriteBlock(stream, bytes); } } // Build parent child referent arrays var parentData = Util.BuildParentData(instances, ReferentProvider); var parentDataBytes = Util.SerializeParentData(parentData); writer.WriteBytes(Signatures.ParentDataSignature); RobloxLZ4.WriteBlock(stream, parentDataBytes); // Write ending signature writer.WriteBytes(Signatures.EndSignature); writer.WriteBytes(Signatures.FileEndSignature); }
internal static void ReadRaw(EndianAwareBinaryReader reader, out int typeCount, out int objectCount, out TypeHeader[] typeHeaders, out Dictionary <int, List <PropertyBlock> > propertyData, out Tuple <int, int>[] childParentPairs) { // Check file signature var signatureBytes = reader.ReadBytes(Signatures.Signature.Length); if (!signatureBytes.SequenceEqual(Signatures.Signature)) { throw new InvalidRobloxFileException("The file signature does not match."); } typeCount = reader.ReadInt32(); objectCount = reader.ReadInt32(); reader.ReadInt32(); // Reserved reader.ReadInt32(); // Reserved // Deserialize type headers typeHeaders = new TypeHeader[typeCount]; for (var i = 0; i < typeCount; i++) { var typeHeaderSignature = reader.ReadBytes(Signatures.TypeHeaderSignature.Length); if (!typeHeaderSignature.SequenceEqual(Signatures.TypeHeaderSignature)) { throw new InvalidRobloxFileException("Invalid type header signature."); } var decompressedBytes = RobloxLZ4.ReadBlock(reader.Stream); var typeHeader = TypeHeader.Deserialize(decompressedBytes); typeHeaders[i] = typeHeader; } // Read property data propertyData = new Dictionary <int, List <PropertyBlock> >(); // Key is type id byte[] lastPropSignature; while (true) { lastPropSignature = reader.ReadBytes(Signatures.PropBlockSignature.Length); if (!lastPropSignature.SequenceEqual(Signatures.PropBlockSignature)) { break; } var decompressedBytes = RobloxLZ4.ReadBlock(reader.Stream); var propertyBlock = PropertyBlock.Deserialize(decompressedBytes, typeHeaders); if (propertyBlock == null) { continue; } if (!propertyData.ContainsKey(propertyBlock.TypeId)) { propertyData.Add(propertyBlock.TypeId, new List <PropertyBlock>()); } propertyData[propertyBlock.TypeId].Add(propertyBlock); } if (!lastPropSignature.SequenceEqual(Signatures.ParentDataSignature)) { throw new InvalidRobloxFileException("Missing parent data section."); } var parentData = RobloxLZ4.ReadBlock(reader.Stream); childParentPairs = Util.ReadParentData(parentData); var endSignature = reader.ReadBytes(Signatures.EndSignature.Length); if (!endSignature.SequenceEqual(Signatures.EndSignature)) { throw new InvalidRobloxFileException("End signature is missing or invalid."); } }