// 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;
}
}
}