397 lines
No EOL
18 KiB
C#
397 lines
No EOL
18 KiB
C#
// 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<Type> EnumerableOfAcceptedTypes = new[]
|
|
{
|
|
typeof(int),
|
|
typeof(double),
|
|
typeof(long),
|
|
typeof(short),
|
|
typeof(int?),
|
|
typeof(double?),
|
|
typeof(long?),
|
|
typeof(short?)
|
|
};
|
|
|
|
private readonly Dictionary<string, Type> _headersToType = new();
|
|
private readonly Dictionary<Type, Dictionary<PacketIndexAttribute, PropertyInfo>> _packetSerializationInformations = new();
|
|
|
|
public PacketDeserializer(IEnumerable<ClientPacketRegistered> 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
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes a string into a PacketDefinition
|
|
/// </summary>
|
|
/// <param name="packetContent">The content to deseralize</param>
|
|
/// <param name="packetType">The type of the packet to deserialize to</param>
|
|
/// <param name="includesKeepAliveIdentity">
|
|
/// Include the keep alive identity or exclude it
|
|
/// </param>
|
|
/// <returns>The deserialized packet.</returns>
|
|
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<PacketIndexAttribute, PropertyInfo> 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<PacketIndexAttribute, PropertyInfo> serializationInformation, bool includesKeepAliveIdentity)
|
|
{
|
|
MatchCollection matches = Regex.Matches(packetContent, @"([^\040]+[\.][^\040]+[\040]?)+((?=\040)|$)|([^\040]+)((?=\040)|$)");
|
|
|
|
if (matches.Count <= 0)
|
|
{
|
|
return deserializedPacket;
|
|
}
|
|
|
|
foreach (KeyValuePair<PacketIndexAttribute, PropertyInfo> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts for instance -1.12.1.8.-1.-1.-1.-1.-1 to eg. List<byte?> </summary>
|
|
/// <param name="currentValues">String to convert</param>
|
|
/// <param name="genericListType">
|
|
/// Type
|
|
/// of the property to convert
|
|
/// </param>
|
|
/// <returns>The string as converted List</returns>
|
|
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<PacketIndexAttribute, PropertyInfo> subpacketSerializationInfo,
|
|
bool isReturnPacket = false)
|
|
{
|
|
string[] subpacketValues = currentSubValues.Split(isReturnPacket ? '^' : '.');
|
|
object newSubpacket = Activator.CreateInstance(packetBasePropertyType);
|
|
|
|
foreach (KeyValuePair<PacketIndexAttribute, PropertyInfo> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="currentValue">The value as String</param>
|
|
/// <param name="packetBasePropertyType">Type of the Property to convert to</param>
|
|
/// <param name="shouldRemoveSeparator"></param>
|
|
/// <param name="packetMatchCollections"></param>
|
|
/// <param name="currentIndex"></param>
|
|
/// <param name="includesKeepAliveIdentity"></param>
|
|
/// <returns></returns>
|
|
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<PacketIndexAttribute, PropertyInfo> 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>();
|
|
|
|
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<PacketIndexAttribute, PropertyInfo> 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<PacketIndexAttribute, PropertyInfo> GenerateSerializationInformations(Type serializationType)
|
|
{
|
|
string header = serializationType.GetCustomAttribute<PacketHeaderAttribute>()?.Identification;
|
|
|
|
if (string.IsNullOrEmpty(header))
|
|
{
|
|
throw new Exception($"Packet header cannot be empty. PacketType: {serializationType.Name}");
|
|
}
|
|
|
|
var packetsForPacketDefinition = new Dictionary<PacketIndexAttribute, PropertyInfo>();
|
|
|
|
foreach (PropertyInfo packetBasePropertyInfo in serializationType.GetProperties().Where(x => x.GetCustomAttributes(false).OfType<PacketIndexAttribute>().Any()))
|
|
{
|
|
PacketIndexAttribute indexAttribute = packetBasePropertyInfo.GetCustomAttributes(false).OfType<PacketIndexAttribute>().FirstOrDefault();
|
|
|
|
if (indexAttribute != null)
|
|
{
|
|
packetsForPacketDefinition.Add(indexAttribute, packetBasePropertyInfo);
|
|
}
|
|
}
|
|
|
|
_headersToType.Add(header, serializationType);
|
|
_packetSerializationInformations.Add(serializationType, packetsForPacketDefinition);
|
|
return _packetSerializationInformations[serializationType];
|
|
}
|
|
|
|
private Dictionary<PacketIndexAttribute, PropertyInfo> GetSerializationInformation(Type serializationType) =>
|
|
_packetSerializationInformations.TryGetValue(serializationType, out Dictionary<PacketIndexAttribute, PropertyInfo> infos)
|
|
? infos
|
|
: GenerateSerializationInformations(serializationType);
|
|
|
|
private static void SetDeserializationInformation(ClientPacket packet, string packetContent, string packetHeader)
|
|
{
|
|
packet.OriginalContent = packetContent;
|
|
packet.OriginalHeader = packetHeader;
|
|
packet.IsReturnPacket = packetHeader.StartsWith("#");
|
|
}
|
|
}
|
|
} |