using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using BinaryPack; using BinaryPack.Attributes; using NitroxModel.Networking; namespace NitroxModel.Packets { [Serializable] public abstract class Packet { private static readonly Dictionary cachedPropertiesByType = new(); private static readonly StringBuilder toStringBuilder = new(); private static readonly object lockObject = new(); public static void InitSerializer() { static IEnumerable FindTypesInModelAssemblies() { return AppDomain.CurrentDomain.GetAssemblies() .Where(assembly => new[] { nameof(NitroxModel), "NitroxModel-Subnautica" } .Contains(assembly.GetName().Name)) .SelectMany(assembly => { try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException e) { return e.Types.Where(t => t != null); } }); } static IEnumerable FindUnionBaseTypes() => FindTypesInModelAssemblies() .Where(t => t.IsAbstract && !t.IsSealed && (!t.BaseType?.IsAbstract ?? true) && !t.ContainsGenericParameters); lock (lockObject) { foreach (Type type in FindUnionBaseTypes()) { BinaryConverter.RegisterUnion(type, FindTypesInModelAssemblies() .Where(t => type.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface) .OrderByDescending(t => { Type current = t; int levels = 0; while (current != type && current != null) { current = current.BaseType; levels++; } return levels; }) .ThenBy(t => t.FullName, StringComparer.Ordinal) .ToArray()); } // This will initialize the processor for Wrapper which will initialize all the others _ = BinaryConverter.Serialize(new Wrapper()); } } [IgnoredMember] public NitroxDeliveryMethod.DeliveryMethod DeliveryMethod { get; protected set; } = NitroxDeliveryMethod.DeliveryMethod.RELIABLE_ORDERED; [IgnoredMember] public UdpChannelId UdpChannel { get; protected set; } = UdpChannelId.DEFAULT; public enum UdpChannelId : byte { DEFAULT = 0, MOVEMENTS = 1, } public byte[] Serialize() { return BinaryConverter.Serialize(new Wrapper(this)); } public static Packet Deserialize(byte[] data) { return BinaryConverter.Deserialize(data).Packet; } public override string ToString() { Type packetType = GetType(); if (!cachedPropertiesByType.TryGetValue(packetType, out PropertyInfo[] properties)) { properties = packetType.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(x => x.Name is not nameof(DeliveryMethod) and not nameof(UdpChannel)).ToArray(); cachedPropertiesByType.Add(packetType, properties); } toStringBuilder.Clear(); toStringBuilder.Append($"[{packetType.Name}: "); foreach (PropertyInfo property in properties) { object propertyValue = property.GetValue(this); if (propertyValue is IList propertyList) { toStringBuilder.Append($"{property.Name}: {propertyList.Count}, "); } else { toStringBuilder.Append($"{property.Name}: {propertyValue}, "); } } toStringBuilder.Remove(toStringBuilder.Length - 2, 2); toStringBuilder.Append(']'); return toStringBuilder.ToString(); } /// /// Wrapper which is used to serialize packets in BinaryPack. /// We cannot serialize Packets directly because ///

/// 1) We will not know what type to deserialize to and /// 2) The root object must have a callable constructor so it can't be abstract ///

/// This type solves both problems and only adds a single byte to the data. ///
public readonly struct Wrapper { public Packet Packet { get; init; } = null; public Wrapper(Packet packet) { Packet = packet; } } } }