// Copyright (c) CypherCore All rights reserved. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. using Framework.Constants; using Game.Entities; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Reflection; namespace Game.Networking { public static class PacketManager { public static void Initialize() { Assembly currentAsm = Assembly.GetExecutingAssembly(); foreach (var type in currentAsm.GetTypes()) { foreach (var methodInfo in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)) { foreach (var msgAttr in methodInfo.GetCustomAttributes()) { if (msgAttr == null) continue; if (msgAttr.Opcode == ClientOpcodes.Unknown) { Log.outError(LogFilter.Network, "Opcode {0} does not have a value", msgAttr.Opcode); continue; } if (_clientPacketTable.ContainsKey(msgAttr.Opcode)) { Log.outError(LogFilter.Network, "Tried to override OpcodeHandler of {0} with {1} (Opcode {2})", _clientPacketTable[msgAttr.Opcode].ToString(), methodInfo.Name, msgAttr.Opcode); continue; } var parameters = methodInfo.GetParameters(); if (parameters.Length == 0) { Log.outError(LogFilter.Network, "Method: {0} Has no paramters", methodInfo.Name); continue; } if (parameters[0].ParameterType.BaseType != typeof(ClientPacket)) { Log.outError(LogFilter.Network, "Method: {0} has wrong BaseType", methodInfo.Name); continue; } _clientPacketTable[msgAttr.Opcode] = new PacketHandler(methodInfo, msgAttr.Status, msgAttr.Processing, parameters[0].ParameterType); } } } } public static bool TryPeek(this ConcurrentQueue queue, out WorldPacket result, PacketFilter filter) { result = null; if (queue.IsEmpty) return false; if (!queue.TryPeek(out result)) return false; if (!filter.Process(result)) return false; return true; } public static PacketHandler GetHandler(ClientOpcodes opcode) { return _clientPacketTable.LookupByKey(opcode); } public static bool ContainsHandler(ClientOpcodes opcode) { return _clientPacketTable.ContainsKey(opcode); } static ConcurrentDictionary _clientPacketTable = new(); public static bool IsInstanceOnlyOpcode(ServerOpcodes opcode) { switch (opcode) { case ServerOpcodes.QuestGiverStatus: // ClientQuest case ServerOpcodes.DuelRequested: // Client case ServerOpcodes.DuelInBounds: // Client case ServerOpcodes.QueryTimeResponse: // Client case ServerOpcodes.DuelWinner: // Client case ServerOpcodes.DuelComplete: // Client case ServerOpcodes.DuelOutOfBounds: // Client case ServerOpcodes.AttackStop: // Client case ServerOpcodes.AttackStart: // Client case ServerOpcodes.MountResult: // Client return true; default: return false; } } } public class PacketHandler { public PacketHandler(MethodInfo info, SessionStatus status, PacketProcessing processingplace, Type type) { methodCaller = (Action)GetType().GetMethod("CreateDelegate", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(type).Invoke(null, new object[] { info }); sessionStatus = status; ProcessingPlace = processingplace; packetType = type; } public void Invoke(WorldSession session, WorldPacket packet) { if (packetType == null) return; using var clientPacket = (ClientPacket)Activator.CreateInstance(packetType, packet); clientPacket.Read(); clientPacket.LogPacket(session); methodCaller(session, clientPacket); } static Action CreateDelegate(MethodInfo method) where P1 : ClientPacket { // create first delegate. It is not fine because its // signature contains unknown types T and P1 Action d = (Action)method.CreateDelegate(typeof(Action)); // create another delegate having necessary signature. // It encapsulates first delegate with a closure return delegate (WorldSession target, ClientPacket p) { d(target, (P1)p); }; } Action methodCaller; Type packetType; public PacketProcessing ProcessingPlace { get; private set; } public SessionStatus sessionStatus { get; private set; } } public abstract class PacketFilter { protected PacketFilter(WorldSession pSession) { m_pSession = pSession; } public abstract bool Process(WorldPacket packet); public virtual bool ProcessUnsafe() { return false; } protected WorldSession m_pSession; } public class MapSessionFilter : PacketFilter { public MapSessionFilter(WorldSession pSession) : base(pSession) { } public override bool Process(WorldPacket packet) { PacketHandler opHandle = PacketManager.GetHandler((ClientOpcodes)packet.GetOpcode()); //check if packet handler is supposed to be safe if (opHandle.ProcessingPlace == PacketProcessing.Inplace) return true; //we do not process thread-unsafe packets if (opHandle.ProcessingPlace == PacketProcessing.ThreadUnsafe) return false; Player player = m_pSession.GetPlayer(); if (player == null) return false; //in Map.Update() we do not process packets where player is not in world! return player.IsInWorld; } } public class WorldSessionFilter : PacketFilter { public WorldSessionFilter(WorldSession pSession) : base(pSession) { } public override bool Process(WorldPacket packet) { PacketHandler opHandle = PacketManager.GetHandler((ClientOpcodes)packet.GetOpcode()); //check if packet handler is supposed to be safe if (opHandle.ProcessingPlace == PacketProcessing.Inplace) return true; //thread-unsafe packets should be processed in World.UpdateSessions() if (opHandle.ProcessingPlace == PacketProcessing.ThreadUnsafe) return true; //no player attached? . our client! ^^ Player player = m_pSession.GetPlayer(); if (player == null) return true; //lets process all packets for non-in-the-world player return !player.IsInWorld; } public override bool ProcessUnsafe() { return true; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class WorldPacketHandlerAttribute : Attribute { public WorldPacketHandlerAttribute(ClientOpcodes opcode) { Opcode = opcode; Status = SessionStatus.Loggedin; Processing = PacketProcessing.ThreadUnsafe; } public ClientOpcodes Opcode { get; private set; } public SessionStatus Status { get; set; } public PacketProcessing Processing { get; set; } } }