// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; namespace EpicGames.Core { /// /// Reads data from a binary output stream. Similar to the NET Framework BinaryReader class, but supports fast serialization of object graphs and container types, and supports nullable objects. /// Significantly faster than BinaryReader due to the expectation that the whole stream is in memory before deserialization. /// public sealed class BinaryArchiveReader : IDisposable { /// /// The input stream. /// Stream? _stream; /// /// The input buffer /// byte[]? _buffer; /// /// Current position within the buffer /// int _bufferPos; /// /// List of previously serialized objects /// readonly List _objects = []; /// /// Constructor /// /// The buffer to read from public BinaryArchiveReader(byte[] buffer) { _buffer = buffer; } /// /// Constructor /// /// File to read from public BinaryArchiveReader(FileReference fileName) { _buffer = FileReference.ReadAllBytes(fileName); } /// /// Constructor /// /// Stream to read from public BinaryArchiveReader(Stream stream) { _buffer = new byte[stream.Length]; stream.Read(_buffer, 0, _buffer.Length); } /// /// Dispose of the stream owned by this reader /// public void Dispose() { if (_stream != null) { _stream.Dispose(); _stream = null; } _buffer = null; } /// /// Reads a bool from the stream /// /// The value that was read public bool ReadBool() { return ReadByte() != 0; } /// /// Reads a single byte from the stream /// /// The value that was read public byte ReadByte() { byte value = _buffer![_bufferPos]; _bufferPos++; return value; } /// /// Reads a single signed byte from the stream /// /// The value that was read public sbyte ReadSignedByte() { return (sbyte)ReadByte(); } /// /// Reads a single short from the stream /// /// The value that was read public short ReadShort() { return (short)ReadUnsignedShort(); } /// /// Reads a single unsigned short from the stream /// /// The value that was read public ushort ReadUnsignedShort() { ushort value = (ushort)(_buffer![_bufferPos + 0] | (_buffer[_bufferPos + 1] << 8)); _bufferPos += 2; return value; } /// /// Reads a single int from the stream /// /// The value that was read public int ReadInt() { return (int)ReadUnsignedInt(); } /// /// Reads a single unsigned int from the stream /// /// The value that was read public uint ReadUnsignedInt() { uint value = (uint)(_buffer![_bufferPos + 0] | (_buffer[_bufferPos + 1] << 8) | (_buffer[_bufferPos + 2] << 16) | (_buffer[_bufferPos + 3] << 24)); _bufferPos += 4; return value; } /// /// Reads a single long from the stream /// /// The value that was read public long ReadLong() { return (long)ReadUnsignedLong(); } /// /// Reads a single unsigned long from the stream /// /// The value that was read public ulong ReadUnsignedLong() { ulong value = (ulong)ReadUnsignedInt(); value |= (ulong)ReadUnsignedInt() << 32; return value; } /// /// Reads a double (64 bit floating point value) from the stream /// /// The value that was read public double ReadDouble() { return BitConverter.Int64BitsToDouble(ReadLong()); } /// /// Reads a string from the stream /// /// The value that was read public string? ReadString() { // ReadPrimitiveArray has been inlined here to avoid the transient byte array allocation int length = ReadInt(); if (length < 0) { return null; } else { int offset = _bufferPos; _bufferPos += length; return Encoding.UTF8.GetString(_buffer!, offset, length); } } /// /// Reads a byte array from the stream /// /// The data that was read public byte[]? ReadByteArray() { return ReadPrimitiveArray(sizeof(byte)); } /// /// Reads a short array from the stream /// /// The data that was read public short[]? ReadShortArray() { return ReadPrimitiveArray(sizeof(short)); } /// /// Reads an int array from the stream /// /// The data that was read public int[]? ReadIntArray() { return ReadPrimitiveArray(sizeof(int)); } /// /// Reads an array of primitive types from the stream /// /// Size of a single element /// The data that was read private T[]? ReadPrimitiveArray(int elementSize) where T : struct { int length = ReadInt(); if (length < 0) { return null; } else { T[] result = new T[length]; ReadBulkData(result, length * elementSize); return result; } } /// /// Reads a byte array from the stream /// /// Length of the array to read /// The data that was read public byte[] ReadFixedSizeByteArray(int length) { return ReadFixedSizePrimitiveArray(sizeof(byte), length); } /// /// Reads a short array from the stream /// /// Length of the array to read /// The data that was read public short[] ReadFixedSizeShortArray(int length) { return ReadFixedSizePrimitiveArray(sizeof(short), length); } /// /// Reads an int array from the stream /// /// Length of the array to read /// The data that was read public int[] ReadFixedSizeIntArray(int length) { return ReadFixedSizePrimitiveArray(sizeof(int), length); } /// /// Reads an array of primitive types from the stream /// /// Size of a single element /// Number of elements to read /// The data that was read private T[] ReadFixedSizePrimitiveArray(int elementSize, int elementCount) where T : struct { T[] result = new T[elementCount]; ReadBulkData(result, elementSize * elementCount); return result; } /// /// Reads bulk data from the stream into the given buffer /// /// Array which receives the data that was read /// Size of data to read private void ReadBulkData(Array data, int size) { System.Buffer.BlockCopy(_buffer!, _bufferPos, data, 0, size); _bufferPos += size; } /// /// Reads an array of items /// /// New array public T[]? ReadArray(Func readElement) { int count = ReadInt(); if (count < 0) { return null; } else { T[] result = new T[count]; for (int idx = 0; idx < count; idx++) { result[idx] = readElement(); } return result; } } /// /// Reads a list of items /// /// The element type for the list /// Delegate used to read a single element /// List of items public List? ReadList(Func readElement) { int count = ReadInt(); if (count < 0) { return null; } else { List result = new List(count); for (int idx = 0; idx < count; idx++) { result.Add(readElement()); } return result; } } /// /// Reads a hashset of items /// /// The element type for the set /// Delegate used to read a single element /// Set of items public HashSet? ReadHashSet(Func readElement) { int count = ReadInt(); if (count < 0) { return null; } else { HashSet result = []; for (int idx = 0; idx < count; idx++) { result.Add(readElement()); } return result; } } /// /// Reads a hashset of items /// /// The element type for the set /// Delegate used to read a single element /// Comparison function for the set /// Set of items public HashSet? ReadHashSet(Func readElement, IEqualityComparer comparer) { int count = ReadInt(); if (count < 0) { return null; } else { HashSet result = new HashSet(comparer); for (int idx = 0; idx < count; idx++) { result.Add(readElement()); } return result; } } /// /// Reads a sortedset of items /// /// The element type for the sortedset /// Delegate used to read a single element /// SortedSet of items public SortedSet? ReadSortedSet(Func readElement) { int count = ReadInt(); if (count < 0) { return null; } else { SortedSet result = []; for (int idx = 0; idx < count; idx++) { result.Add(readElement()); } return result; } } /// /// Reads a sortedset of items /// /// The element type for the sortedset /// Delegate used to read a single element /// Comparison function for the set /// SortedSet of items public SortedSet? ReadSortedSet(Func readElement, IComparer comparer) { int count = ReadInt(); if (count < 0) { return null; } else { SortedSet result = new SortedSet(comparer); for (int idx = 0; idx < count; idx++) { result.Add(readElement()); } return result; } } /// /// Reads a dictionary of items /// /// Type of the dictionary key /// Type of the dictionary value /// Delegate used to read a single key /// Delegate used to read a single value /// New dictionary instance public Dictionary? ReadDictionary(Func readKey, Func readValue) where TK : notnull { int count = ReadInt(); if (count < 0) { return null; } else { Dictionary result = new Dictionary(count); for (int idx = 0; idx < count; idx++) { result.Add(readKey(), readValue()); } return result; } } /// /// Reads a dictionary of items /// /// Type of the dictionary key /// Type of the dictionary value /// Delegate used to read a single key /// Delegate used to read a single value /// Comparison function for keys in the dictionary /// New dictionary instance public Dictionary? ReadDictionary(Func readKey, Func readValue, IEqualityComparer comparer) where TK : notnull { int count = ReadInt(); if (count < 0) { return null; } else { Dictionary result = new Dictionary(count, comparer); for (int idx = 0; idx < count; idx++) { result.Add(readKey(), readValue()); } return result; } } /// /// Reads a nullable object from the archive /// /// The nullable type /// Delegate used to read a value public Nullable ReadNullable(Func readValue) where T : struct { if (ReadBool()) { return new Nullable(readValue()); } else { return null; } } /// /// Reads an object, which may be null, from the archive. Does not handle de-duplicating object references. /// /// Type of the object to read /// Delegate used to read the object /// The object instance public T? ReadOptionalObject(Func read) where T : class { if (ReadBool()) { return read(); } else { return null; } } /// /// Reads an object reference from the stream. Each referenced object will only be serialized once using the supplied delegates. Reading an object instance is /// done in two phases; first the object is created and its reference stored in the unique object list, then the object contents are read. This allows the object to /// serialize a reference to itself. /// /// Type of the object to read. /// Delegate used to create an object instance /// Delegate used to read an object instance /// Object instance public T? ReadObjectReference(Func createObject, Action readObject) where T : class { int index = ReadInt(); if (index < 0) { return null; } else { if (index == _objects.Count) { T obj = createObject(); _objects.Add(obj); readObject(obj); } return (T?)_objects[index]; } } /// /// Reads an object reference from the stream. Each object will only be serialized once using the supplied delegate; subsequent reads reference the original. /// Since the reader only receives the object reference when the CreateObject delegate returns, it is not possible for the object to serialize a reference to itself. /// /// Delegate used to create an object instance. The object may not reference itself recursively. /// Object instance public object? ReadUntypedObjectReference(Func readObject) { int index = ReadInt(); if (index < 0) { return null; } else { // Temporarily add the reader to the object list, so we can detect invalid recursive references. if (index == _objects.Count) { _objects.Add(null); _objects[index] = readObject(); } if (_objects[index] == null) { throw new InvalidOperationException("Attempt to serialize reference to object recursively."); } return _objects[index]; } } /// /// Reads an object reference from the stream. Each object will only be serialized once using the supplied delegate; subsequent reads reference the original. /// Since the reader only receives the object reference when the CreateObject delegate returns, it is not possible for the object to serialize a reference to itself. /// /// Delegate used to create an object instance. The object may not reference itself recursively. /// Object instance public object? ReadUntypedObjectReference(Func readObject) { int index = ReadInt(); if (index < 0) { return null; } else { // Temporarily add the reader to the object list, so we can detect invalid recursive references. if (index == _objects.Count) { _objects.Add(null); _objects[index] = readObject(this); } if (_objects[index] == null) { throw new InvalidOperationException("Attempt to serialize reference to object recursively."); } return _objects[index]; } } /// /// Reads an object reference from the stream. Each object will only be serialized once using the supplied delegate; subsequent reads reference the original. /// Since the reader only receives the object reference when the CreateObject delegate returns, it is not possible for the object to serialize a reference to itself. /// /// Type of the object to read. /// Delegate used to create an object instance. The object may not reference itself recursively. /// Object instance public T? ReadObjectReference(Func readObject) where T : class => (T?)ReadUntypedObjectReference(readObject); /// /// Reads an object reference from the stream. Each object will only be serialized once using the supplied delegate; subsequent reads reference the original. /// Since the reader only receives the object reference when the CreateObject delegate returns, it is not possible for the object to serialize a reference to itself. /// /// Type of the object to read. /// Delegate used to create an object instance. The object may not reference itself recursively. /// Object instance public T? ReadObjectReference(Func readObject) where T : class => (T?)ReadUntypedObjectReference(readObject); /// /// Helper method for validating that deserialized objects are not null /// /// Type of the deserialized object /// The object instance /// The object instance [return: NotNull] public static T NotNull(T? param) where T : class { if (param == null) { throw new InvalidDataException("Object stored in archive is not allowed to be null."); } return param; } /// /// Helper method for validating that deserialized objects are not null /// /// Type of the deserialized object /// The object instance /// The object instance public static T NotNullStruct(T? param) where T : struct { if (param == null) { throw new InvalidDataException("Object stored in archive is not allowed to be null."); } return param.Value; } } }