/// <summary> /// Handles a packet, reading the flags and processing all fragments. /// </summary> /// <param name="packet">ClientPacket to handle</param> private void HandlePacket(ClientPacket packet) { log.DebugFormat("[{0}] Handling packet {1}", session.Account, packet.Header.Sequence); uint issacXor = packet.Header.HasFlag(PacketHeaderFlags.EncryptedChecksum) ? ConnectionData.IssacClient.GetOffset() : 0; if (!packet.VerifyChecksum(issacXor)) { log.WarnFormat("[{0}] Packet {1} has invalid checksum", session.Account, packet.Header.Sequence); } // depending on the current session state: // Set the next timeout tick value, to compare against in the WorldManager // Sessions that have gone past the AuthLoginRequest step will stay active for a longer period of time (exposed via configuration) // Sessions that in the AuthLoginRequest will have a short timeout, as set in the AuthenticationHandler.DefaultAuthTimeout. // Example: Applications that check uptime will stay in the AuthLoginRequest state. session.Network.TimeoutTick = (session.State == Enum.SessionState.AuthLoginRequest) ? DateTime.UtcNow.AddSeconds(WorldManager.DefaultSessionTimeout).Ticks : DateTime.UtcNow.AddSeconds(AuthenticationHandler.DefaultAuthTimeout).Ticks; // If we have an EchoRequest flag, we should flag to respond with an echo response on next send. if (packet.Header.HasFlag(PacketHeaderFlags.EchoRequest)) { FlagEcho(packet.HeaderOptional.EchoRequestClientTime); } // If we have an AcknowledgeSequence flag, we can clear our cached packet buffer up to that sequence. if (packet.Header.HasFlag(PacketHeaderFlags.AckSequence)) { AcknowledgeSequence(packet.HeaderOptional.Sequence); } if (packet.Header.HasFlag(PacketHeaderFlags.TimeSynch)) { log.DebugFormat("[{0}] Incoming TimeSync TS: {1}", session.Account, packet.HeaderOptional.TimeSynch); // Do something with this... // Based on network traces these are not 1:1. Server seems to send them every 20 seconds per port. // Client seems to send them alternatingly every 2 or 4 seconds per port. // We will send this at a 20 second time interval. I don't know what to do with these when we receive them at this point. } // If the client is requesting a retransmission, pull those packets from the queue and resend them. if (packet.Header.HasFlag(PacketHeaderFlags.RequestRetransmit)) { foreach (uint sequence in packet.HeaderOptional.RetransmitData) { Retransmit(sequence); } } // This should be set on the first packet to the server indicating the client is logging in. // This is the start of a three-way handshake between the client and server (LoginRequest, ConnectRequest, ConnectResponse) // Note this would be sent to each server a client would connect too (Login and each world). // In our current implimenation we handle all roles in this one server. if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest)) { AuthenticationHandler.HandleLoginRequest(packet, session); return; } // This should be set on the second packet to the server from the client. // This completes the three-way handshake. if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse)) { sendResync = true; AuthenticationHandler.HandleConnectResponse(packet, session); return; } // Process all fragments out of the packet foreach (ClientPacketFragment fragment in packet.Fragments) { ProcessFragment(fragment); } // Update the last received sequence. if (packet.Header.Sequence != 0) { lastReceivedPacketSequence = packet.Header.Sequence; } }
public static void HandlePacket(ConnectionType type, ClientPacket packet, Session session) { // CLinkStatusAverages::OnPingResponse if (packet.Header.HasFlag(PacketHeaderFlags.EchoRequest)) { var connectionData = (type == ConnectionType.Login ? session.LoginConnection : session.WorldConnection); // used to calculate round trip time (ping) // client calculates: currentTime - requestTime - serverDrift var echoResponse = new ServerPacket((ushort)(type == ConnectionType.Login ? 0x0B : 0x18), PacketHeaderFlags.EncryptedChecksum | PacketHeaderFlags.EchoResponse); echoResponse.Payload.Write(packet.HeaderOptional.ClientTime); echoResponse.Payload.Write((float)connectionData.ServerTime - packet.HeaderOptional.ClientTime); NetworkManager.SendPacket(type, echoResponse, session); } // ClientNet::HandleTimeSynch if (packet.Header.HasFlag(PacketHeaderFlags.TimeSynch)) { var connectionData = (type == ConnectionType.Login ? session.LoginConnection : session.WorldConnection); // used to update time at client and check for overspeed (60s desync and client will disconenct with speed hack warning) var timeSynchResponse = new ServerPacket((ushort)(type == ConnectionType.Login ? 0x0B : 0x18), PacketHeaderFlags.EncryptedChecksum | PacketHeaderFlags.TimeSynch); timeSynchResponse.Payload.Write(connectionData.ServerTime); NetworkManager.SendPacket(type, timeSynchResponse, session); } if (packet.Header.HasFlag(PacketHeaderFlags.RequestRetransmit)) { foreach (uint sequence in packet.HeaderOptional.RetransmitData) { CachedPacket cachedPacket; if (session.CachedPackets.TryGetValue(sequence, out cachedPacket)) { if (!cachedPacket.Packet.Header.HasFlag(PacketHeaderFlags.Retransmission)) { cachedPacket.Packet.Header.Flags |= PacketHeaderFlags.Retransmission; uint issacXor; cachedPacket.Packet.Header.Checksum = packet.CalculateChecksum(session, ConnectionType.World, cachedPacket.IssacXor, out issacXor); } NetworkManager.SendPacketDirect(ConnectionType.World, cachedPacket.Packet, session); } } } if (type == ConnectionType.Login) { if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest)) { AuthenticationHandler.HandleLoginRequest(packet, session); return; } if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse) && session.Authenticated) { AuthenticationHandler.HandleConnectResponse(packet, session); return; } } else { if (packet.Header.HasFlag(PacketHeaderFlags.WorldLoginRequest)) { AuthenticationHandler.HandleWorldLoginRequest(packet, session); return; } if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse)) { AuthenticationHandler.HandleWorldConnectResponse(packet, session); return; } } if (packet.Header.HasFlag(PacketHeaderFlags.Disconnect)) { HandleDisconnectResponse(packet, session); } foreach (ClientPacketFragment fragment in packet.Fragments) { var opcode = (FragmentOpcode)fragment.Payload.ReadUInt32(); if (!fragmentHandlers.ContainsKey(opcode)) { Console.WriteLine($"Received unhandled fragment opcode: 0x{((uint)opcode).ToString("X4")}"); } else { FragmentHandler fragmentHandler; if (fragmentHandlers.TryGetValue(opcode, out fragmentHandler)) { fragmentHandler.Invoke(fragment, session); } } } }
/// <summary> /// Handles a packet, reading the flags and processing all fragments. /// </summary> /// <param name="packet">ClientPacket to handle</param> private void HandlePacket(ClientPacket packet) { log.DebugFormat("[{0}] Handling packet {1}", session.Account, packet.Header.Sequence); uint issacXor = packet.Header.HasFlag(PacketHeaderFlags.EncryptedChecksum) ? ConnectionData.IssacClient.GetOffset() : 0; if (!packet.VerifyChecksum(issacXor)) { log.WarnFormat("[{0}] Packet {1} has invalid checksum", session.Account, packet.Header.Sequence); } // If we have an EchoRequest flag, we should flag to respond with an echo response on next send. if (packet.Header.HasFlag(PacketHeaderFlags.EchoRequest)) { FlagEcho(packet.HeaderOptional.EchoRequestClientTime); } // If we have an AcknowledgeSequence flag, we can clear our cached packet buffer up to that sequence. if (packet.Header.HasFlag(PacketHeaderFlags.AckSequence)) { AcknowledgeSequence(packet.HeaderOptional.Sequence); } if (packet.Header.HasFlag(PacketHeaderFlags.TimeSynch)) { // Do something with this... // Based on network traces these are not 1:1. Server seems to send them every 20 seconds per port. // Client seems to send them alternatingly every 2 or 4 seconds per port. // We will send this at a 20 second time interval. I don't know what to do with these when we receive them at this point. } // If the client is requesting a retransmission, pull those packets from the queue and resend them. if (packet.Header.HasFlag(PacketHeaderFlags.RequestRetransmit)) { foreach (uint sequence in packet.HeaderOptional.RetransmitData) { Retransmit(sequence); } } // This should be set on the first packet to the server indicating the client is logging in. // This is the start of a three-way handshake between the client and server (LoginRequest, ConnectRequest, ConnectResponse) // Note this would be sent to each server a client would connect too (Login and each world). // In our current implimenation we handle all roles in this one server. if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest)) { AuthenticationHandler.HandleLoginRequest(packet, session); return; } // This should be set on the second packet to the server from the client. // This comletes the three-way handshake. if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse)) { sendResync = true; AuthenticationHandler.HandleConnectResponse(packet, session); return; } // Process all fragments out of the packet foreach (ClientPacketFragment fragment in packet.Fragments) { ProcessFragment(fragment); } // Update the last received sequence. if (packet.Header.Sequence != 0) { lastReceivedPacketSequence = packet.Header.Sequence; } }
private static void HandleDisconnectResponse(ClientPacket packet, Session session) { WorldManager.Remove(session); }
public static void HandlePacket(ConnectionType type, ClientPacket packet, Session session) { if (!CheckPacketHeader(packet, session)) { // server treats all packets sent during the first 30 seconds as invalid packets due to server crash, this will move clients to the disconnect screen if (DateTime.Now < WorldManager.WorldStartTime.AddSeconds(30d)) { session.SendCharacterError(CharacterError.ServerCrash); } return; } // CLinkStatusAverages::OnPingResponse if (packet.Header.HasFlag(PacketHeaderFlags.EchoRequest)) { var connectionData = (type == ConnectionType.Login ? session.LoginConnection : session.WorldConnection); // used to calculate round trip time (ping) // client calculates: currentTime - requestTime - serverDrift var echoResponse = new ServerPacket((ushort)(type == ConnectionType.Login ? 0x0B : 0x18), PacketHeaderFlags.EncryptedChecksum | PacketHeaderFlags.EchoResponse); echoResponse.Payload.Write(packet.HeaderOptional.ClientTime); echoResponse.Payload.Write((float)connectionData.ServerTime - packet.HeaderOptional.ClientTime); NetworkManager.SendPacket(type, echoResponse, session); } // ClientNet::HandleTimeSynch if (packet.Header.HasFlag(PacketHeaderFlags.TimeSynch)) { var connectionData = (type == ConnectionType.Login ? session.LoginConnection : session.WorldConnection); // used to update time at client and check for overspeed (60s desync and client will disconenct with speed hack warning) var timeSynchResponse = new ServerPacket((ushort)(type == ConnectionType.Login ? 0x0B : 0x18), PacketHeaderFlags.EncryptedChecksum | PacketHeaderFlags.TimeSynch); timeSynchResponse.Payload.Write(connectionData.ServerTime); NetworkManager.SendPacket(type, timeSynchResponse, session); } if (packet.Header.HasFlag(PacketHeaderFlags.RequestRetransmit)) { foreach (uint sequence in packet.HeaderOptional.RetransmitData) { CachedPacket cachedPacket; if (session.CachedPackets.TryGetValue(sequence, out cachedPacket)) { if (!cachedPacket.Packet.Header.HasFlag(PacketHeaderFlags.Retransmission)) { cachedPacket.Packet.Header.Flags |= PacketHeaderFlags.Retransmission; uint issacXor; cachedPacket.Packet.Header.Checksum = packet.CalculateChecksum(session, ConnectionType.World, cachedPacket.IssacXor, out issacXor); } NetworkManager.SendPacketDirect(ConnectionType.World, cachedPacket.Packet, session); } } } if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest)) { AuthenticationHandler.HandleLoginRequest(packet, session); return; } if (packet.Header.HasFlag(PacketHeaderFlags.WorldLoginRequest)) { AuthenticationHandler.HandleWorldLoginRequest(packet, session); return; } if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse)) { if (type == ConnectionType.Login) { AuthenticationHandler.HandleConnectResponse(packet, session); return; } AuthenticationHandler.HandleWorldConnectResponse(packet, session); return; } if (packet.Header.HasFlag(PacketHeaderFlags.Disconnect)) { HandleDisconnectResponse(packet, session); } foreach (ClientPacketFragment fragment in packet.Fragments) { var opcode = (FragmentOpcode)fragment.Payload.ReadUInt32(); if (!fragmentHandlers.ContainsKey(opcode)) { Console.WriteLine($"Received unhandled fragment opcode: 0x{(uint)opcode:X4}"); } else { FragmentHandlerInfo fragmentHandlerInfo; if (fragmentHandlers.TryGetValue(opcode, out fragmentHandlerInfo)) { if (fragmentHandlerInfo.Attribute.State == session.State) { fragmentHandlerInfo.Handler.Invoke(fragment, session); } } } } }