// WingsEmu // // Developed by NosWings Team using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; namespace WingsEmu.Packets { public class PacketDeserializer : IPacketDeserializer { private static readonly IEnumerable EnumerableOfAcceptedTypes = new[] { typeof(int), typeof(double), typeof(long), typeof(short), typeof(int?), typeof(double?), typeof(long?), typeof(short?) }; private readonly Dictionary _headersToType = new(); private readonly Dictionary> _packetSerializationInformations = new(); public PacketDeserializer(IEnumerable registeredPackets) { // Iterate thru all PacketDefinition implementations foreach (ClientPacketRegistered registeredPacket in registeredPackets) { try { GenerateSerializationInformations(registeredPacket.PacketType); } catch (Exception e) { Console.WriteLine($"Error registering : {registeredPacket} : {e.Message}"); } // add to serialization informations } } /// /// Deserializes a string into a PacketDefinition /// /// The content to deseralize /// The type of the packet to deserialize to /// /// Include the keep alive identity or exclude it /// /// The deserialized packet. public (IClientPacket, Type) Deserialize(string packetContent, bool includesKeepAliveIdentity = false) { try { string packetHeader = packetContent.Split(' ')[includesKeepAliveIdentity ? 1 : 0]; Type packetType = GetPacketTypeByHeader(packetHeader.StartsWith("#") ? packetHeader.Substring(1) : packetHeader); if (packetType == null) { var unresolvedPacket = new UnresolvedPacket { OriginalHeader = packetHeader, OriginalContent = packetContent }; return (unresolvedPacket, typeof(UnresolvedPacket)); } Dictionary serializationInformation = GetSerializationInformation(packetType); var deserializedPacket = (ClientPacket)Activator.CreateInstance(packetType); // reflection is bad, improve? SetDeserializationInformation(deserializedPacket, packetContent, packetHeader); deserializedPacket = Deserialize(packetContent, deserializedPacket, serializationInformation, includesKeepAliveIdentity); return (deserializedPacket, packetType); } catch (Exception e) { Console.WriteLine($"The serialized packet has the wrong format. Packet: {packetContent}"); Console.WriteLine(e); return (null, null); } } private Type GetPacketTypeByHeader(string packetHeader) { if (_headersToType.TryGetValue(packetHeader, out Type packetType)) { return packetType; } return null; } private ClientPacket Deserialize(string packetContent, ClientPacket deserializedPacket, Dictionary serializationInformation, bool includesKeepAliveIdentity) { MatchCollection matches = Regex.Matches(packetContent, @"([^\040]+[\.][^\040]+[\040]?)+((?=\040)|$)|([^\040]+)((?=\040)|$)"); if (matches.Count <= 0) { return deserializedPacket; } foreach (KeyValuePair packetBasePropertyInfo in serializationInformation) { int currentIndex = packetBasePropertyInfo.Key.Index + (includesKeepAliveIdentity ? 2 : 1); // adding 2 because we need to skip incrementing number and packet header if (currentIndex < matches.Count) { if (packetBasePropertyInfo.Key.SerializeToEnd) { // get the value to the end and stop deserialization string valueToEnd = packetContent.Substring(matches[currentIndex].Index, packetContent.Length - matches[currentIndex].Index); packetBasePropertyInfo.Value.SetValue(deserializedPacket, DeserializeValue(packetBasePropertyInfo.Value.PropertyType, valueToEnd, packetBasePropertyInfo.Key, matches, includesKeepAliveIdentity)); break; } string currentValue = matches[currentIndex].Value; if (packetBasePropertyInfo.Value.PropertyType == typeof(string) && string.IsNullOrEmpty(currentValue)) { throw new NullReferenceException(); } // set the value & convert currentValue packetBasePropertyInfo.Value.SetValue(deserializedPacket, DeserializeValue(packetBasePropertyInfo.Value.PropertyType, currentValue, packetBasePropertyInfo.Key, matches, includesKeepAliveIdentity)); } else { break; } } return deserializedPacket; } /// /// Converts for instance -1.12.1.8.-1.-1.-1.-1.-1 to eg. List /// String to convert /// /// Type /// of the property to convert /// /// The string as converted List private IList DeserializeSimpleList(string currentValues, Type genericListType) { var subpackets = (IList)Convert.ChangeType(Activator.CreateInstance(genericListType), genericListType); foreach (string currentValue in currentValues.Split('.')) { object value = DeserializeValue(genericListType.GenericTypeArguments[0], currentValue, null, null); subpackets.Add(value); } return subpackets; } private object DeserializeSubpacket(string currentSubValues, Type packetBasePropertyType, Dictionary subpacketSerializationInfo, bool isReturnPacket = false) { string[] subpacketValues = currentSubValues.Split(isReturnPacket ? '^' : '.'); object newSubpacket = Activator.CreateInstance(packetBasePropertyType); foreach (KeyValuePair subpacketPropertyInfo in subpacketSerializationInfo) { int currentSubIndex = isReturnPacket ? subpacketPropertyInfo.Key.Index + 1 : subpacketPropertyInfo.Key.Index; // return packets do include header string currentSubValue = subpacketValues[currentSubIndex]; subpacketPropertyInfo.Value.SetValue(newSubpacket, DeserializeValue(subpacketPropertyInfo.Value.PropertyType, currentSubValue, subpacketPropertyInfo.Key, null)); } return newSubpacket; } /// /// Converts a Sublist of Packets, For instance 0.4903.5.0.0 2.340.0.0.0 /// 3.720.0.0.0 5.4912.6.0.0 9.227.0.0.0 10.803.0.0.0 to /// /// The value as String /// Type of the Property to convert to /// /// /// /// /// private IList DeserializeSubpackets(string currentValue, Type packetBasePropertyType, bool shouldRemoveSeparator, MatchCollection packetMatchCollections, int? currentIndex, bool includesKeepAliveIdentity) { // split into single values var splitSubPacket = currentValue.Split(' ').ToList(); // generate new list var subPackets = (IList)Convert.ChangeType(Activator.CreateInstance(packetBasePropertyType), packetBasePropertyType); Type subPacketType = packetBasePropertyType.GetGenericArguments()[0]; Dictionary subpacketSerializationInfo = GetSerializationInformation(subPacketType); // handle subpackets with separator if (shouldRemoveSeparator) { if (!currentIndex.HasValue || packetMatchCollections == null) { return subPackets; } var splittedSubpacketParts = packetMatchCollections.Select(m => m.Value).ToList(); splitSubPacket = new List(); string generatedPseudoDelimitedString = string.Empty; int subPacketTypePropertiesCount = subpacketSerializationInfo.Count; // check if the amount of properties can be serialized properly if (((splittedSubpacketParts.Count + (includesKeepAliveIdentity ? 1 : 0)) % subPacketTypePropertiesCount) == 0) // amount of properties per subpacket does match the given value amount in % { for (int i = currentIndex.Value + 1 + (includesKeepAliveIdentity ? 1 : 0); i < splittedSubpacketParts.Count; i++) { int j; for (j = i; j < i + subPacketTypePropertiesCount; j++) { // add delimited value generatedPseudoDelimitedString += splittedSubpacketParts[j] + "."; } i = j - 1; //remove last added separator generatedPseudoDelimitedString = generatedPseudoDelimitedString.Substring(0, generatedPseudoDelimitedString.Length - 1); // add delimited values to list of values to serialize splitSubPacket.Add(generatedPseudoDelimitedString); generatedPseudoDelimitedString = string.Empty; } } else { throw new Exception("The amount of splitted subpacket values without delimiter do not match the % property amount of the serialized type."); } } foreach (string subpacket in splitSubPacket) { subPackets.Add(DeserializeSubpacket(subpacket, subPacketType, subpacketSerializationInfo)); } return subPackets; } private object DeserializeValue(Type packetPropertyType, string currentValue, PacketIndexAttribute packetIndexAttribute, MatchCollection packetMatches, bool includesKeepAliveIdentity = false) { // check for empty value and cast it to null if (currentValue == "-1" || currentValue == "-") { currentValue = null; } // enum should be casted to number if (packetPropertyType.BaseType != null && packetPropertyType.BaseType == typeof(Enum)) { object convertedValue = null; try { if (currentValue != null && packetPropertyType.IsEnumDefined(Enum.Parse(packetPropertyType, currentValue))) { convertedValue = Enum.Parse(packetPropertyType, currentValue); } } catch (Exception) { //Log.Warn($"Could not convert value {currentValue} to type {packetPropertyType.Name}"); } return convertedValue; } if (packetPropertyType == typeof(bool)) // handle boolean values { return currentValue != "0"; } if (packetPropertyType.BaseType != null && packetPropertyType.BaseType == typeof(ClientPacket)) // subpacket { Dictionary subpacketSerializationInfo = GetSerializationInformation(packetPropertyType); return DeserializeSubpacket(currentValue, packetPropertyType, subpacketSerializationInfo, packetIndexAttribute?.IsReturnPacket ?? false); } if (packetPropertyType.IsGenericType && packetPropertyType.GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>)) // subpacket list && packetPropertyType.GenericTypeArguments[0].BaseType == typeof(ClientPacket)) { return DeserializeSubpackets(currentValue, packetPropertyType, packetIndexAttribute?.RemoveSeparator ?? false, packetMatches, packetIndexAttribute?.Index, includesKeepAliveIdentity); } if (packetPropertyType.IsGenericType && packetPropertyType.GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>))) // simple list { return DeserializeSimpleList(currentValue, packetPropertyType); } if (Nullable.GetUnderlyingType(packetPropertyType) != null && string.IsNullOrEmpty(currentValue)) // empty nullable value { return null; } if (Nullable.GetUnderlyingType(packetPropertyType) == null) { return Convert.ChangeType(currentValue, packetPropertyType); // cast to specified type } if (packetPropertyType.GenericTypeArguments[0]?.BaseType == typeof(Enum)) { return Enum.Parse(packetPropertyType.GenericTypeArguments[0], currentValue); } if (!EnumerableOfAcceptedTypes.Contains(packetPropertyType)) { return Convert.ChangeType(currentValue, packetPropertyType.GenericTypeArguments[0]); } switch (packetPropertyType) { case Type _ when packetPropertyType == typeof(long): case Type _ when packetPropertyType == typeof(int): case Type _ when packetPropertyType == typeof(double): case Type _ when packetPropertyType == typeof(short): if (int.TryParse(currentValue, out int b) && b < 0) { currentValue = "0"; } break; case Type _ when packetPropertyType == typeof(long?): case Type _ when packetPropertyType == typeof(int?): case Type _ when packetPropertyType == typeof(double?): case Type _ when packetPropertyType == typeof(short?): if (currentValue == null) { currentValue = "0"; } if (int.TryParse(currentValue, out int c) && c < 0) { currentValue = "0"; } break; } return Convert.ChangeType(currentValue, packetPropertyType.GenericTypeArguments[0]); } private Dictionary GenerateSerializationInformations(Type serializationType) { string header = serializationType.GetCustomAttribute()?.Identification; if (string.IsNullOrEmpty(header)) { throw new Exception($"Packet header cannot be empty. PacketType: {serializationType.Name}"); } var packetsForPacketDefinition = new Dictionary(); foreach (PropertyInfo packetBasePropertyInfo in serializationType.GetProperties().Where(x => x.GetCustomAttributes(false).OfType().Any())) { PacketIndexAttribute indexAttribute = packetBasePropertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); if (indexAttribute != null) { packetsForPacketDefinition.Add(indexAttribute, packetBasePropertyInfo); } } _headersToType.Add(header, serializationType); _packetSerializationInformations.Add(serializationType, packetsForPacketDefinition); return _packetSerializationInformations[serializationType]; } private Dictionary GetSerializationInformation(Type serializationType) => _packetSerializationInformations.TryGetValue(serializationType, out Dictionary infos) ? infos : GenerateSerializationInformations(serializationType); private static void SetDeserializationInformation(ClientPacket packet, string packetContent, string packetHeader) { packet.OriginalContent = packetContent; packet.OriginalHeader = packetHeader; packet.IsReturnPacket = packetHeader.StartsWith("#"); } } }