server-master/srcs/WingsAPI.Packets/PacketDeserializer.cs
2026-02-10 18:21:30 +01:00

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("#");
}
}
}