static void AddItem(Dictionary <int, PackedData.Property> children, int fieldID, PackedData.Type type, PackedData.Descriptor childDescriptor, object value) { if (children.ContainsKey(fieldID) == false) { children[fieldID] = new PackedData.Property() { Descriptor = childDescriptor, Type = type, Value = value }; } else if (children[fieldID].Type == type) { var array = Array.CreateInstance(ArrayType(type), 2); array.SetValue(children[fieldID].Value, 0); array.SetValue(value, 1); children[fieldID] = new PackedData.Property() { Descriptor = childDescriptor, Type = ConvertToArray(type), Value = array }; } else if (children[fieldID].Type == ConvertToArray(type)) { // TODO: This is expensive. We should preprocess the data stream // beforehand to know how many array elements must be allocated in advance. var previous = (Array)children[fieldID].Value; var next = Array.CreateInstance(ArrayType(type), previous.Length + 1); previous.CopyTo(next, 0); next.SetValue(value, next.Length - 1); children[fieldID] = new PackedData.Property() { Descriptor = childDescriptor, Type = ConvertToArray(type), Value = next }; } else { throw new Exception($"Inconsistent data types ({type} in a {children[fieldID].Type}) for {childDescriptor}"); } }
/// <summary> /// Unpack a packed binary stream into a set of description objects contained /// inside the 'PackedData' class. This should be called only with valid streams /// (top level are always valid; if the stream is inside a property, /// IsValidContainer should be called to ensure no errors). /// </summary> /// <returns>An array of Property[] objects</returns> public static PackedData.Property[] Unpack(ReadOnlySpan <byte> data, PackedData.Descriptor descriptor) { var children = new Dictionary <int, PackedData.Property>(); int index = 0; while (index < data.Length) { int chunk = ReadInt(data, ref index); int fieldID = (chunk >> 3); int encoding = (chunk & 0x7); var childDescriptor = new PackedData.Descriptor { id = fieldID, parent = descriptor }; switch (encoding) { case 0: // Integer { int value = ReadInt(data, ref index); AddItem(children, fieldID, PackedData.Type.Integer, childDescriptor, value); break; } case 2: // Struct or string { var length = ReadInt(data, ref index); var slice = data.Slice(index, length); index += length; // Force string interpretation if we already have objets bool isString = IsString(slice) || !IsValidContainer(slice) || length == 0; if (children.ContainsKey(fieldID) && children[fieldID].Type == PackedData.Type.StringArray) { isString = true; } if (isString) // String { // Detect internal string encoding. Actually, this could be a case of // trying to deserialize a string[] instead of a string. Further tests needed! if (slice.Length > 1 && slice[0] == 10) { int subindex = 1; while (ReadInt(slice, ref subindex) == slice.Length - subindex && subindex <= slice.Length) { slice = slice.Slice(subindex); if (slice.Length < 2 || slice[0] != 10) { break; } subindex = 1; } } var value = Encoding.UTF8.GetString(slice); AddItem(children, fieldID, PackedData.Type.String, childDescriptor, value); } else { var value = Unpack(slice, childDescriptor); AddItem(children, fieldID, PackedData.Type.Object, childDescriptor, value); } break; } case 5: // Float { var value = BitConverter.ToSingle(data.Slice(index, 4)); index += 4; AddItem(children, fieldID, PackedData.Type.Float, childDescriptor, value); break; } } } return(children.Values.ToArray());