using FishNet.Connection; using FishNet.Documenting; using FishNet.Managing; using FishNet.Object; using FishNet.Object.Prediction; using FishNet.Serializing.Helping; using FishNet.Transporting; using FishNet.Utility.Constant; using FishNet.Utility.Extension; using FishNet.Utility.Performance; using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using UnityEngine; [assembly: InternalsVisibleTo(UtilityConstants.GENERATED_ASSEMBLY_NAME)] //Required for internal tests. [assembly: InternalsVisibleTo(UtilityConstants.TEST_ASSEMBLY_NAME)] namespace FishNet.Serializing { /// <summary> /// Used for read references to generic types. /// </summary> /// <typeparam name="T"></typeparam> [APIExclude] public static class GenericReader<T> { public static Func<Reader, T> Read { internal get; set; } public static Func<Reader, AutoPackType, T> ReadAutoPack { internal get; set; } } /// <summary> /// Reads data from a buffer. /// </summary> public class Reader { #region Public. /// <summary> /// Capacity of the buffer. /// </summary> public int Capacity => _buffer.Length; /// <summary> /// NetworkManager for this reader. Used to lookup objects. /// </summary> public NetworkManager NetworkManager; /// <summary> /// Offset within the buffer when the reader was created. /// </summary> public int Offset { get; private set; } /// <summary> /// Position for the next read. /// </summary> public int Position; /// <summary> /// Total number of bytes available within the buffer. /// </summary> public int Length { get; private set; } /// <summary> /// Bytes remaining to be read. This value is Length - Position. /// </summary> public int Remaining => ((Length + Offset) - Position); #endregion #region Internal. /// <summary> /// NetworkConnection that this data came from. /// Value may not always be set. /// </summary> public NetworkConnection NetworkConnection { get; private set; } #if UNITY_EDITOR || DEVELOPMENT_BUILD /// <summary> /// Last NetworkObject parsed. /// </summary> public static NetworkObject LastNetworkObject { get; private set; } /// <summary> /// Last NetworkBehaviour parsed. /// </summary> public static NetworkBehaviour LastNetworkBehaviour { get; private set; } #endif #endregion #region Private. /// <summary> /// Data being read. /// </summary> private byte[] _buffer; #endregion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Reader(byte[] bytes, NetworkManager networkManager, NetworkConnection networkConnection = null) { Initialize(bytes, networkManager, networkConnection); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Reader(ArraySegment<byte> segment, NetworkManager networkManager, NetworkConnection networkConnection = null) { Initialize(segment, networkManager, networkConnection); } /// <summary> /// Outputs reader to string. /// </summary> /// <returns></returns> public override string ToString() { return $"Position: {Position}, Length: {Length}, Buffer: {BitConverter.ToString(_buffer, Offset, Length)}."; } /// <summary> /// Initializes this reader with data. /// </summary> /// <param name="bytes"></param> /// <param name="networkManager"></param> internal void Initialize(ArraySegment<byte> bytes, NetworkManager networkManager, NetworkConnection networkConnection = null) { if (bytes.Array == null) { if (_buffer == null) _buffer = new byte[0]; } else { _buffer = bytes.Array; } Position = bytes.Offset; Offset = bytes.Offset; Length = bytes.Count; NetworkManager = networkManager; NetworkConnection = networkConnection; } /// <summary> /// Initializes this reader with data. /// </summary> /// <param name="bytes"></param> /// <param name="networkManager"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Initialize(byte[] bytes, NetworkManager networkManager, NetworkConnection networkConnection = null) { Initialize(new ArraySegment<byte>(bytes), networkManager, networkConnection); } /// <summary> /// Reads a dictionary. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] [Obsolete("Use ReadDictionaryAllocated.")] public Dictionary<TKey, TValue> ReadDictionary<TKey, TValue>() { return ReadDictionaryAllocated<TKey, TValue>(); } /// <summary> /// Reads a dictionary. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Dictionary<TKey, TValue> ReadDictionaryAllocated<TKey, TValue>() { bool isNull = ReadBoolean(); if (isNull) return null; int count = ReadInt32(); Dictionary<TKey, TValue> result = new Dictionary<TKey, TValue>(count); for (int i = 0; i < count; i++) { TKey key = Read<TKey>(); TValue value = Read<TValue>(); result.Add(key, value); } return result; } /// <summary> /// Reads length. This method is used to make debugging easier. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int ReadLength() { return ReadInt32(); } /// <summary> /// Reads a packetId. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal PacketId ReadPacketId() { return (PacketId)ReadUInt16(); } /// <summary> /// Returns a ushort without advancing the reader. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal PacketId PeekPacketId() { int currentPosition = Position; PacketId result = ReadPacketId(); Position = currentPosition; return result; } /// <summary> /// Skips a number of bytes in the reader. /// </summary> /// <param name="value">Number of bytes to skip.</param> [CodegenExclude] public void Skip(int value) { if (value < 1 || Remaining < value) return; Position += value; } /// <summary> /// Clears remaining bytes to be read. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { if (Remaining > 0) Skip(Remaining); } /// <summary> /// Returns the buffer as an ArraySegment. /// </summary> /// <returns></returns> public ArraySegment<byte> GetArraySegmentBuffer() { return new ArraySegment<byte>(_buffer, Offset, Length); } /// <summary> /// Returns the buffer as bytes. This does not trim excessive bytes. /// </summary> /// <returns></returns> public byte[] GetByteBuffer() { return _buffer; } /// <summary> /// Returns the buffer as bytes and allocates into a new array. /// </summary> /// <returns></returns> public byte[] GetByteBufferAllocated() { byte[] result = new byte[Length]; Buffer.BlockCopy(_buffer, Offset, result, 0, Length); return result; } /// <summary> /// BlockCopies data from the reader to target and advances reader. /// </summary> /// <param name="target"></param> /// <param name="targetOffset"></param> /// <param name="count"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void BlockCopy(ref byte[] target, int targetOffset, int count) { Buffer.BlockCopy(_buffer, Position, target, targetOffset, count); Position += count; } /// <summary> /// Reads a byte. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() { byte r = _buffer[Position]; Position += 1; return r; } /// <summary> /// Read bytes from position into target. /// </summary> /// <param name="buffer">Buffer to read bytes into.</param> /// <param name="count">Number of bytes to read.</param> [CodegenExclude] public void ReadBytes(ref byte[] buffer, int count) { if (buffer == null) throw new EndOfStreamException($"Target is null."); //Target isn't large enough. if (count > buffer.Length) throw new EndOfStreamException($"Count of {count} exceeds target length of {buffer.Length}."); BlockCopy(ref buffer, 0, count); } /// <summary> /// Creates an ArraySegment by reading a number of bytes from position. /// </summary> /// <param name="count"></param> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArraySegment<byte> ReadArraySegment(int count) { ArraySegment<byte> result = new ArraySegment<byte>(_buffer, Position, count); Position += count; return result; } /// <summary> /// Reads a sbyte. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public sbyte ReadSByte() { return (sbyte)ReadByte(); } /// <summary> /// Reads a char. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public char ReadChar() => (char)ReadUInt16(); /// <summary> /// Reads a boolean. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ReadBoolean() { byte result = ReadByte(); return (result == 1) ? true : false; } /// <summary> /// Reads an int16. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort ReadUInt16() { ushort result = 0; result |= _buffer[Position++]; result |= (ushort)(_buffer[Position++] << 8); return result; } /// <summary> /// Reads a uint16. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public short ReadInt16() => (short)ReadUInt16(); /// <summary> /// Reads an int32. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public uint ReadUInt32(AutoPackType packType = AutoPackType.Packed) { if (packType == AutoPackType.Packed) return (uint)ReadPackedWhole(); uint result = 0; result |= _buffer[Position++]; result |= (uint)_buffer[Position++] << 8; result |= (uint)_buffer[Position++] << 16; result |= (uint)_buffer[Position++] << 24; return result; } /// <summary> /// Reads a uint32. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadInt32(AutoPackType packType = AutoPackType.Packed) { if (packType == AutoPackType.Packed) return (int)(long)ZigZagDecode(ReadPackedWhole()); return (int)ReadUInt32(packType); } /// <summary> /// Reads a uint64. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public long ReadInt64(AutoPackType packType = AutoPackType.Packed) { if (packType == AutoPackType.Packed) return (long)ZigZagDecode(ReadPackedWhole()); return (long)ReadUInt64(packType); } /// <summary> /// Reads an int64. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadUInt64(AutoPackType packType = AutoPackType.Packed) { if (packType == AutoPackType.Packed) return (ulong)ReadPackedWhole(); ulong result = 0; result |= _buffer[Position++]; result |= (ulong)_buffer[Position++] << 8; result |= (ulong)_buffer[Position++] << 16; result |= (ulong)_buffer[Position++] << 24; result |= (ulong)_buffer[Position++] << 32; result |= (ulong)_buffer[Position++] << 40; result |= (ulong)_buffer[Position++] << 48; result |= (ulong)_buffer[Position++] << 56; return result; } /// <summary> /// Reads a single. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public float ReadSingle(AutoPackType packType = AutoPackType.Unpacked) { if (packType == AutoPackType.Unpacked) { UIntFloat converter = new UIntFloat(); converter.UIntValue = ReadUInt32(AutoPackType.Unpacked); return converter.FloatValue; } else { long converter = (long)ReadPackedWhole(); return (float)(converter / 100f); } } /// <summary> /// Reads a double. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public double ReadDouble() { UIntDouble converter = new UIntDouble(); converter.LongValue = ReadUInt64(AutoPackType.Unpacked); return converter.DoubleValue; } /// <summary> /// Reads a decimal. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public decimal ReadDecimal() { UIntDecimal converter = new UIntDecimal(); converter.LongValue1 = ReadUInt64(AutoPackType.Unpacked); converter.LongValue2 = ReadUInt64(AutoPackType.Unpacked); return converter.DecimalValue; } /// <summary> /// Reads a string. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadString() { int size = ReadInt32(); //Null string. if (size == Writer.UNSET_COLLECTION_SIZE_VALUE) return null; else if (size == 0) return string.Empty; if (!CheckAllocationAttack(size)) return string.Empty; ArraySegment<byte> data = ReadArraySegment(size); return ReaderStatics.GetString(data); } /// <summary> /// Creates a byte array and reads bytes and size into it. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte[] ReadBytesAndSizeAllocated() { int size = ReadInt32(); if (size == Writer.UNSET_COLLECTION_SIZE_VALUE) return null; else return ReadBytesAllocated(size); } /// <summary> /// Reads bytes and size and copies results into target. Returns UNSET if null was written. /// </summary> /// <returns>Bytes read.</returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadBytesAndSize(ref byte[] target) { int size = ReadInt32(); if (size > 0) ReadBytes(ref target, size); return size; } /// <summary> /// Reads bytes and size and returns as an ArraySegment. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArraySegment<byte> ReadArraySegmentAndSize() { int size = ReadInt32(); /* UNSET would be written for null. But since * ArraySegments cannot be null return default if * length is unset or 0. */ if (size == Writer.UNSET_COLLECTION_SIZE_VALUE || size == 0) return default; return ReadArraySegment(size); } /// <summary> /// Reads a Vector2. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2 ReadVector2() { return new Vector2(ReadSingle(), ReadSingle()); } /// <summary> /// Reads a Vector3. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3 ReadVector3() { return new Vector3(ReadSingle(), ReadSingle(), ReadSingle()); } /// <summary> /// Reads a Vector4. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ReadVector4() { return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); } /// <summary> /// Reads a Vector2Int. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector2Int ReadVector2Int(AutoPackType packType = AutoPackType.Packed) { return new Vector2Int(ReadInt32(packType), ReadInt32(packType)); } /// <summary> /// Reads a Vector3Int. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector3Int ReadVector3Int(AutoPackType packType = AutoPackType.Packed) { return new Vector3Int(ReadInt32(packType), ReadInt32(packType), ReadInt32(packType)); } /// <summary> /// Reads a color. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Color ReadColor(AutoPackType packType = AutoPackType.Packed) { float r, g, b, a; if (packType == AutoPackType.Unpacked) { r = ReadSingle(); g = ReadSingle(); b = ReadSingle(); a = ReadSingle(); } else { r = (float)(ReadByte() / 100f); g = (float)(ReadByte() / 100f); b = (float)(ReadByte() / 100f); a = (float)(ReadByte() / 100f); } return new Color(r, g, b, a); } /// <summary> /// Reads a Color32. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Color32 ReadColor32() { return new Color32(ReadByte(), ReadByte(), ReadByte(), ReadByte()); } /// <summary> /// Reads a Quaternion. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Quaternion ReadQuaternion(AutoPackType packType = AutoPackType.Packed) { if (packType == AutoPackType.Packed) { uint result = ReadUInt32(AutoPackType.Unpacked); return Quaternion32Compression.Decompress(result); } else if (packType == AutoPackType.PackedLess) { ulong result = ReadUInt64(AutoPackType.Unpacked); return Quaternion64Compression.Decompress(result); } else { return new Quaternion( ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle() ); } } /// <summary> /// Reads a Rect. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rect ReadRect() { return new Rect(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle()); } /// <summary> /// Plane. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Plane ReadPlane() { return new Plane(ReadVector3(), ReadSingle()); } /// <summary> /// Reads a Ray. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Ray ReadRay() { Vector3 position = ReadVector3(); Vector3 direction = ReadVector3(); return new Ray(position, direction); } /// <summary> /// Reads a Ray. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Ray2D ReadRay2D() { Vector3 position = ReadVector2(); Vector2 direction = ReadVector2(); return new Ray2D(position, direction); } /// <summary> /// Reads a Matrix4x4. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Matrix4x4 ReadMatrix4x4() { Matrix4x4 result = new Matrix4x4 { m00 = ReadSingle(), m01 = ReadSingle(), m02 = ReadSingle(), m03 = ReadSingle(), m10 = ReadSingle(), m11 = ReadSingle(), m12 = ReadSingle(), m13 = ReadSingle(), m20 = ReadSingle(), m21 = ReadSingle(), m22 = ReadSingle(), m23 = ReadSingle(), m30 = ReadSingle(), m31 = ReadSingle(), m32 = ReadSingle(), m33 = ReadSingle() }; return result; } /// <summary> /// Creates a new byte array and reads bytes into it. /// </summary> /// <param name="count"></param> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte[] ReadBytesAllocated(int count) { byte[] bytes = new byte[count]; ReadBytes(ref bytes, count); return bytes; } /// <summary> /// Reads a Guid. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public System.Guid ReadGuid() { byte[] buffer = ReaderStatics.GetGuidBuffer(); ReadBytes(ref buffer, 16); return new System.Guid(buffer); } /// <summary> /// Reads a GameObject. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public GameObject ReadGameObject() { NetworkObject nob = ReadNetworkObject(); return (nob == null) ? null : nob.gameObject; } /// <summary> /// Reads a Transform. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Transform ReadTransform() { NetworkObject nob = ReadNetworkObject(); return (nob == null) ? null : nob.transform; } /// <summary> /// Reads a NetworkObject. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public NetworkObject ReadNetworkObject() { return ReadNetworkObject(out _); } /// <summary> /// Reads a NetworkObject. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public NetworkObject ReadNetworkObject(out int objectOrPrefabId) { #if UNITY_EDITOR || DEVELOPMENT_BUILD LastNetworkBehaviour = null; #endif objectOrPrefabId = ReadNetworkObjectId(); bool isSpawned; /* UNSET indicates that the object * is null or no PrefabId is set. * PrefabIds are set in Awake within * the NetworkManager so that should * never happen so long as nob isn't null. */ if (objectOrPrefabId == NetworkObject.UNSET_OBJECTID_VALUE) return null; else isSpawned = ReadBoolean(); bool isServer = NetworkManager.ServerManager.Started; bool isClient = NetworkManager.ClientManager.Started; NetworkObject result; //Is spawned. if (isSpawned) { result = null; /* Try to get the object client side first if client * is running. When acting as a host generally the object * will be available in the server and client list * but there can be occasions where the server side * deinitializes the object, making it unavailable, while * it is still available in the client side. Since FishNet doesn't * use a fake host connection like some lesser solutions the client * has to always be treated as it's own entity. */ if (isClient) NetworkManager.ClientManager.Objects.Spawned.TryGetValueIL2CPP(objectOrPrefabId, out result); //If not found on client and server is running then try server. if (result == null && isServer) NetworkManager.ServerManager.Objects.Spawned.TryGetValueIL2CPP(objectOrPrefabId, out result); } //Not spawned. else { //Only look up asServer if not client, otherwise use client. bool asServer = !isClient; //Look up prefab. result = NetworkManager.GetPrefab(objectOrPrefabId, asServer); } #if UNITY_EDITOR || DEVELOPMENT_BUILD LastNetworkObject = result; #endif return result; } /// <summary> /// Reads a NetworkObjectId and nothing else. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadNetworkObjectId() { return ReadUInt16(); } /// <summary> /// Reads the Id for a NetworkObject and outputs spawn settings. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int ReadNetworkObjectForSpawn(out sbyte initializeOrder, out ushort collectionid, out bool spawned) { int objectId = ReadNetworkObjectId(); bool isNull = (objectId == NetworkObject.UNSET_OBJECTID_VALUE); if (isNull) { initializeOrder = 0; collectionid = 0; spawned = false; } else { collectionid = ReadUInt16(); initializeOrder = ReadSByte(); spawned = ReadBoolean(); } return objectId; } /// <summary> /// Reads the Id for a NetworkObject and outputs despawn settings. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int ReadNetworkObjectForDepawn(out DespawnType dt) { int objectId = ReadNetworkObjectId(); dt = (DespawnType)ReadByte(); return objectId; } /// <summary> /// Reads a NetworkBehaviourId and ObjectId. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal byte ReadNetworkBehaviourId(out int objectId) { objectId = ReadNetworkObjectId(); if (objectId != NetworkObject.UNSET_OBJECTID_VALUE) return ReadByte(); else return 0; } /// <summary> /// Reads a NetworkBehaviour. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public NetworkBehaviour ReadNetworkBehaviour(out int objectId, out byte componentIndex) { NetworkObject nob = ReadNetworkObject(out objectId); componentIndex = ReadByte(); NetworkBehaviour result; if (nob == null) { result = null; } else { if (componentIndex >= nob.NetworkBehaviours.Length) { NetworkManager.LogError($"ComponentIndex of {componentIndex} is out of bounds on {nob.gameObject.name} [id {nob.ObjectId}]. This may occur if you have modified your gameObject/prefab without saving it, or the scene."); result = null; } else { result = nob.NetworkBehaviours[componentIndex]; } } #if UNITY_EDITOR || DEVELOPMENT_BUILD LastNetworkBehaviour = result; #endif return result; } /// <summary> /// Reads a NetworkBehaviour. /// </summary> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public NetworkBehaviour ReadNetworkBehaviour() { return ReadNetworkBehaviour(out _, out _); } /// <summary> /// Reads a DateTime. /// </summary> /// <param name="dt"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public DateTime ReadDateTime() { DateTime result = DateTime.FromBinary(ReadInt64()); return result; } /// <summary> /// Reads a transport channel. /// </summary> /// <param name="channel"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public Channel ReadChannel() { return (Channel)ReadByte(); } /// <summary> /// Reads the Id for a NetworkConnection. /// </summary> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadNetworkConnectionId() { return ReadInt16(); } /// <summary> /// Writes a NetworkConnection. /// </summary> /// <param name="conn"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public NetworkConnection ReadNetworkConnection() { int value = ReadNetworkConnectionId(); if (value == NetworkConnection.UNSET_CLIENTID_VALUE) { return FishNet.Managing.NetworkManager.EmptyConnection; } else { //Prefer server. if (NetworkManager.IsServer) { NetworkConnection result; if (NetworkManager.ServerManager.Clients.TryGetValueIL2CPP(value, out result)) { return result; } //If also client then try client side data. else if (NetworkManager.IsClient) { //If found in client collection then return. if (NetworkManager.ClientManager.Clients.TryGetValueIL2CPP(value, out result)) return result; /* Otherwise make a new instance. * We do not know if this is for the server or client so * initialize it either way. Connections rarely come through * without being in server/client side collection. */ else return new NetworkConnection(NetworkManager, value, true); } //Only server and not found. else { NetworkManager.LogWarning($"Unable to find connection for read Id {value}. An empty connection will be returned."); return FishNet.Managing.NetworkManager.EmptyConnection; } } //Try client side, will only be able to fetch against local connection. else { //If value is self then return self. if (value == NetworkManager.ClientManager.Connection.ClientId) return NetworkManager.ClientManager.Connection; //Try client side dictionary. else if (NetworkManager.ClientManager.Clients.TryGetValueIL2CPP(value, out NetworkConnection result)) return result; /* Otherwise make a new instance. * We do not know if this is for the server or client so * initialize it either way. Connections rarely come through * without being in server/client side collection. */ else return new NetworkConnection(NetworkManager, value, true); } } } /// <summary> /// Checks if the size could possibly be an allocation attack. /// </summary> /// <param name="size"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CheckAllocationAttack(int size) { /* Possible attacks. Impossible size, or size indicates * more elements in collection or more bytes needed * than what bytes are available. */ if (size != Writer.UNSET_COLLECTION_SIZE_VALUE && size < 0) { NetworkManager.LogError($"Size of {size} is invalid."); return false; } if (size > Remaining) { NetworkManager.LogError($"Read size of {size} is larger than remaining data of {Remaining}."); return false; } //Checks pass. return true; } #region Packed readers. /// <summary> /// ZigZag decode an integer. Move the sign bit back to the left. /// </summary> public ulong ZigZagDecode(ulong value) { ulong sign = value << 63; if (sign > 0) return ~(value >> 1) | sign; return value >> 1; } /// <summary> /// Reads a packed whole number. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadPackedWhole() { byte data = ReadByte(); ulong result = (ulong)(data & 0x7F); if ((data & 0x80) == 0) return result; data = ReadByte(); result |= (ulong)(data & 0x7F) << 7; if ((data & 0x80) == 0) return result; data = ReadByte(); result |= (ulong)(data & 0x7F) << 14; if ((data & 0x80) == 0) return result; data = ReadByte(); result |= (ulong)(data & 0x7F) << 21; if ((data & 0x80) == 0) return result; data = ReadByte(); result |= (ulong)(data & 0x0F) << 28; int extraBytes = data >> 4; switch (extraBytes) { case 0: break; case 1: result |= (ulong)ReadByte() << 32; break; case 2: result |= (ulong)ReadByte() << 32; result |= (ulong)ReadByte() << 40; break; case 3: result |= (ulong)ReadByte() << 32; result |= (ulong)ReadByte() << 40; result |= (ulong)ReadByte() << 48; break; case 4: result |= (ulong)ReadByte() << 32; result |= (ulong)ReadByte() << 40; result |= (ulong)ReadByte() << 48; result |= (ulong)ReadByte() << 56; break; } return result; } #endregion #region Generators. /// <summary> /// Reads a replicate into collection and returns item count read. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int ReadReplicate<T>(ref T[] collection, uint tick) where T : IReplicateData { //Number of entries written. int count = (int)ReadByte(); if (collection == null || collection.Length < count) collection = new T[count]; /* Subtract count total minus 1 * from starting tick. This sets the tick to what the first entry would be. * EG packet came in as tick 100, so that was passed as tick. * if there are 3 replicates then 2 would be subtracted (count - 1). * The new tick would be 98. * Ticks would be assigned to read values from oldest to * newest as 98, 99, 100. Which is the correct result. In order for this to * work properly past replicates cannot skip ticks. This will be ensured * in another part of the code. */ tick -= (uint)(count - 1); int fullPackType = ReadByte(); //Read once and apply to all entries. if (fullPackType > 0) { T value; if (fullPackType == Writer.REPLICATE_ALL_DEFAULT_BYTE) { value = default(T); } else if (fullPackType == Writer.REPLICATE_REPEATING_BYTE) { value = Read<T>(); } else { value = default(T); NetworkManager?.LogError($"Unhandled Replicate pack type {fullPackType}."); } for (int i = 0; i < count; i++) { collection[i] = value; collection[i].SetTick(tick + (uint)i); } } //Values vary, read each indicator. else { T lastData = default; for (int i = 0; i < count; i++) { T value = default; byte indicatorB = ReadByte(); if (indicatorB == Writer.REPLICATE_DUPLICATE_BYTE) { value = lastData; } else if (indicatorB == Writer.REPLICATE_UNIQUE_BYTE) { value = Read<T>(); lastData = value; } else if (indicatorB == Writer.REPLICATE_DEFAULT_BYTE) { value = default(T); } //Apply tick. value.SetTick(tick + (uint)i); //Assign to collection. collection[i] = value; } } return count; } /// <summary> /// Reads a ListCache with allocations. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public ListCache<T> ReadListCacheAllocated<T>() { List<T> lst = ReadListAllocated<T>(); ListCache<T> lc = new ListCache<T>(); lc.Collection = lst; return lc; } /// <summary> /// Reads a ListCache and returns the item count read. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadListCache<T>(ref ListCache<T> listCache) { listCache.Collection = ReadListAllocated<T>(); return listCache.Collection.Count; } /// <summary> /// Reads a list with allocations. /// </summary> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public List<T> ReadListAllocated<T>() { List<T> result = null; ReadList<T>(ref result); return result; } /// <summary> /// Reads into collection and returns item count read. /// </summary> /// <param name="collection"></param> /// <param name="allowNullification">True to allow the referenced collection to be nullified when receiving a null collection read.</param> /// <returns>Number of values read into the collection. UNSET is returned if the collection were read as null.</returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadList<T>(ref List<T> collection, bool allowNullification = false) { int count = ReadInt32(); if (count == Writer.UNSET_COLLECTION_SIZE_VALUE) { if (allowNullification) collection = null; return Writer.UNSET_COLLECTION_SIZE_VALUE; } else { if (collection == null) collection = new List<T>(count); else collection.Clear(); for (int i = 0; i < count; i++) collection.Add(Read<T>()); return count; } } /// <summary> /// Reads an array. /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public T[] ReadArrayAllocated<T>() { T[] result = null; ReadArray<T>(ref result); return result; } /// <summary> /// Reads into collection and returns amount read. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="collection"></param> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public int ReadArray<T>(ref T[] collection) { int count = ReadInt32(); if (count == Writer.UNSET_COLLECTION_SIZE_VALUE) { return 0; } else if (count == 0) { if (collection == null) collection = new T[0]; return 0; } else { //Initialize buffer if not already done. if (collection == null) collection = new T[count]; else if (collection.Length < count) Array.Resize(ref collection, count); for (int i = 0; i < count; i++) collection[i] = Read<T>(); return count; } } /// <summary> /// Reads any supported type. /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> [CodegenExclude] [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Read<T>() { System.Type type = typeof(T); if (IsAutoPackType(type, out AutoPackType packType)) { Func<Reader, AutoPackType, T> autopackDel = GenericReader<T>.ReadAutoPack; if (autopackDel == null) { LogError(GetLogMessage()); return default; } else { return autopackDel.Invoke(this, packType); } } else { Func<Reader, T> del = GenericReader<T>.Read; if (del == null) { LogError(GetLogMessage()); return default; } else { return del.Invoke(this); } } string GetLogMessage() => $"Read method not found for {type.FullName}. Use a supported type or create a custom serializer."; } /// <summary> /// Logs an error. /// </summary> /// <param name="msg"></param> private void LogError(string msg) { if (NetworkManager == null) NetworkManager.StaticLogError(msg); else NetworkManager.LogError(msg); } /// <summary> /// Returns if T takes AutoPackType argument. /// </summary> /// <param name="packType">Outputs the default pack type for T.</param> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool IsAutoPackType<T>(out AutoPackType packType) => Writer.IsAutoPackType<T>(out packType); /// <summary> /// Returns if T takes AutoPackType argument. /// </summary> /// <param name="packType">Outputs the default pack type for T.</param> /// <returns></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool IsAutoPackType(Type type, out AutoPackType packType) => Writer.IsAutoPackType(type, out packType); #endregion } }