/* * Copyright (c) 2006-2007, Second Life Reverse Engineering Team * All rights reserved. * * - Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Neither the name of the Second Life Reverse Engineering Team nor the names * of its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Threading; using System.Collections; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Globalization; using System.IO; using libsecondlife.Packets; namespace libsecondlife { /// /// This exception is thrown whenever a network operation is attempted /// without a network connection. /// public class NotConnectedException : ApplicationException { } /// /// NetworkManager is responsible for managing the network layer of /// libsecondlife. It tracks all the server connections, serializes /// outgoing traffic and deserializes incoming traffic, and provides /// instances of delegates for network-related events. /// public partial class NetworkManager { /// /// Holds a simulator reference and a packet, these structs are put in /// the packet inbox for decoding /// public struct IncomingPacket { /// Reference to the simulator that this packet came from public Simulator Simulator; /// The packet that needs to be processed public Packet Packet; } /// /// Explains why a simulator or the grid disconnected from us /// public enum DisconnectType { /// The client requested the logout or simulator disconnect ClientInitiated, /// The server notified us that it is disconnecting ServerInitiated, /// Either a socket was closed or network traffic timed out NetworkTimeout, /// The last active simulator shut down SimShutdown } /// /// Coupled with RegisterCallback(), this is triggered whenever a packet /// of a registered type is received /// /// /// public delegate void PacketCallback(Packet packet, Simulator simulator); /// /// Assigned by the OnConnected event. Raised when login was a success /// /// Reference to the SecondLife class that called the event public delegate void ConnectedCallback(object sender); /// /// Assigned by the OnLogoutReply callback. Raised upone receipt of a LogoutReply packet during logout process. /// /// public delegate void LogoutCallback(List inventoryItems); /// /// Triggered before a new connection to a simulator is established /// /// The connection to the new simulator won't be established /// until this callback returns /// The simulator that is being connected to /// Whether to continue connecting to the simulator or abort /// the connection public delegate bool SimConnectingCallback(Simulator simulator); /// /// Triggered when a new connection to a simulator is established /// /// The simulator that is being connected to public delegate void SimConnectedCallback(Simulator simulator); /// /// Triggered when a simulator other than the simulator that is currently /// being occupied disconnects for whatever reason /// /// The simulator that disconnected, which will become a null /// reference after the callback is finished /// Enumeration explaining the reason for the disconnect public delegate void SimDisconnectedCallback(Simulator simulator, DisconnectType reason); /// /// Triggered when we are logged out of the grid due to a simulator request, /// client request, network timeout, or any other cause /// /// Enumeration explaining the reason for the disconnect /// If we were logged out by the simulator, this /// is a message explaining why public delegate void DisconnectedCallback(DisconnectType reason, string message); /// /// Triggered when CurrentSim changes /// /// A reference to the old value of CurrentSim public delegate void CurrentSimChangedCallback(Simulator PreviousSimulator); /// /// Triggered when an event queue makes the initial connection /// /// Simulator this event queue is tied to public delegate void EventQueueRunningCallback(Simulator simulator); /// /// Event raised when the client was able to connected successfully. /// /// Uses the ConnectedCallback delegate. public event ConnectedCallback OnConnected; /// /// Event raised when a logout is confirmed by the simulator /// public event LogoutCallback OnLogoutReply; /// /// Event raised when a before a connection to a simulator is /// initialized /// public event SimConnectingCallback OnSimConnecting; /// /// Event raised when a connection to a simulator is established /// public event SimConnectedCallback OnSimConnected; /// /// An event for the connection to a simulator other than the currently /// occupied one disconnecting /// /// The Simulators list is locked when this event is /// triggered, do not attempt to modify the collection or acquire a /// lock on it when this callback is fired public event SimDisconnectedCallback OnSimDisconnected; /// /// An event for being logged out either through client request, server /// forced, or network error /// public event DisconnectedCallback OnDisconnected; /// /// An event for when CurrentSim changes /// public event CurrentSimChangedCallback OnCurrentSimChanged; /// /// Triggered when an event queue makes the initial connection /// public event EventQueueRunningCallback OnEventQueueRunning; /// Uniquely identifier associated with our connections to /// simulators public uint CircuitCode; /// The simulator that the logged in avatar is currently /// occupying public Simulator CurrentSim = null; /// All of the simulators we are currently connected to public List Simulators = new List(); /// /// Shows whether the network layer is logged in to the grid or not /// public bool Connected { get { return connected; } } public int InboxCount { get { return PacketInbox.Count; } } [Obsolete("AgentID has been moved to Self.AgentID")] public LLUUID AgentID { get { return Client.Self.AgentID; } } [Obsolete("SessionID has been moved to Self.SessionID")] public LLUUID SessionID { get { return Client.Self.SessionID; } } [Obsolete("SecureSessionID has been mvoed to Self.SecureSessionID")] public LLUUID SecureSessionID { get { return Client.Self.SecureSessionID; } } /// Handlers for incoming capability events internal CapsEventDictionary CapsEvents; /// Handlers for incoming packets internal PacketEventDictionary PacketEvents; /// Incoming packets that are awaiting handling internal BlockingQueue PacketInbox = new BlockingQueue(Settings.PACKET_INBOX_SIZE); private SecondLife Client; private Timer DisconnectTimer; private bool connected = false; /// /// Default constructor /// /// Reference to the SecondLife client public NetworkManager(SecondLife client) { Client = client; PacketEvents = new PacketEventDictionary(client); CapsEvents = new CapsEventDictionary(client); // Register the internal callbacks RegisterCallback(PacketType.RegionHandshake, new PacketCallback(RegionHandshakeHandler)); RegisterCallback(PacketType.StartPingCheck, new PacketCallback(StartPingCheckHandler)); RegisterCallback(PacketType.ParcelOverlay, new PacketCallback(ParcelOverlayHandler)); RegisterCallback(PacketType.EnableSimulator, new PacketCallback(EnableSimulatorHandler)); RegisterCallback(PacketType.DisableSimulator, new PacketCallback(DisableSimulatorHandler)); RegisterCallback(PacketType.KickUser, new PacketCallback(KickUserHandler)); RegisterCallback(PacketType.LogoutReply, new PacketCallback(LogoutReplyHandler)); RegisterCallback(PacketType.CompletePingCheck, new PacketCallback(PongHandler)); RegisterCallback(PacketType.SimStats, new PacketCallback(SimStatsHandler)); // GLOBAL SETTING: Don't force Expect-100: Continue headers on HTTP POST calls ServicePointManager.Expect100Continue = false; } /// /// Register an event handler for a packet. This is a low level event /// interface and should only be used if you are doing something not /// supported in libsecondlife /// /// Packet type to trigger events for /// Callback to fire when a packet of this type /// is received public void RegisterCallback(PacketType type, PacketCallback callback) { PacketEvents.RegisterEvent(type, callback); } /// /// Unregister an event handler for a packet. This is a low level event /// interface and should only be used if you are doing something not /// supported in libsecondlife /// /// Packet type this callback is registered with /// Callback to stop firing events for public void UnregisterCallback(PacketType type, PacketCallback callback) { PacketEvents.UnregisterEvent(type, callback); } /// /// Register a CAPS event handler. This is a low level event interface /// and should only be used if you are doing something not supported in /// libsecondlife /// /// Name of the CAPS event to register a handler for /// Callback to fire when a CAPS event is received public void RegisterEventCallback(string capsEvent, Caps.EventQueueCallback callback) { CapsEvents.RegisterEvent(capsEvent, callback); } /// /// Unregister a CAPS event handler. This is a low level event interface /// and should only be used if you are doing something not supported in /// libsecondlife /// /// Name of the CAPS event this callback is /// registered with /// Callback to stop firing events for public void UnregisterEventCallback(string capsEvent, Caps.EventQueueCallback callback) { CapsEvents.UnregisterEvent(capsEvent, callback); } /// /// Send a packet to the simulator the avatar is currently occupying /// /// Packet to send public void SendPacket(Packet packet) { if (CurrentSim != null && CurrentSim.Connected) CurrentSim.SendPacket(packet, true); } /// /// Send a packet to a specified simulator /// /// Packet to send /// Simulator to send the packet to public void SendPacket(Packet packet, Simulator simulator) { if (simulator != null) simulator.SendPacket(packet, true); } /// /// Send a raw byte array as a packet to the current simulator /// /// Byte array containing a packet /// Whether to set the second, third, and fourth /// bytes of the payload to the current sequence number public void SendPacket(byte[] payload, bool setSequence) { if (CurrentSim != null) CurrentSim.SendPacket(payload, setSequence); } /// /// Send a raw byte array as a packet to the specified simulator /// /// Byte array containing a packet /// Simulator to send the packet to /// Whether to set the second, third, and fourth /// bytes of the payload to the current sequence number public void SendPacket(byte[] payload, Simulator simulator, bool setSequence) { if (simulator != null) simulator.SendPacket(payload, setSequence); } /// /// Connect to a simulator /// /// IP address to connect to /// Port to connect to /// Handle for this simulator, to identify its /// location in the grid /// Whether to set CurrentSim to this new /// connection, use this if the avatar is moving in to this simulator /// URL of the capabilities server to use for /// this sim connection /// A Simulator object on success, otherwise null public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, string seedcaps) { IPEndPoint endPoint = new IPEndPoint(ip, (int)port); return Connect(endPoint, handle, setDefault, seedcaps); } /// /// Connect to a simulator /// /// IP address and port to connect to /// Handle for this simulator, to identify its /// location in the grid /// Whether to set CurrentSim to this new /// connection, use this if the avatar is moving in to this simulator /// URL of the capabilities server to use for /// this sim connection /// A Simulator object on success, otherwise null public Simulator Connect(IPEndPoint endPoint, ulong handle, bool setDefault, string seedcaps) { Simulator simulator = FindSimulator(endPoint); if (simulator == null) { // We're not tracking this sim, create a new Simulator object simulator = new Simulator(Client, endPoint, handle); // Immediately add this simulator to the list of current sims. It will be removed if the // connection fails lock (Simulators) Simulators.Add(simulator); } if (!simulator.Connected) { if (!connected) { // Mark that we are connecting/connected to the grid connected = true; // Start the packet decoding thread Thread decodeThread = new Thread(new ThreadStart(PacketHandler)); decodeThread.Start(); } // Fire the OnSimConnecting event if (OnSimConnecting != null) { try { if (!OnSimConnecting(simulator)) { // Callback is requesting that we abort this connection lock (Simulators) Simulators.Remove(simulator); return null; } } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } // Attempt to establish a connection to the simulator if (simulator.Connect(setDefault)) { if (DisconnectTimer == null) { // Start a timer that checks if we've been disconnected DisconnectTimer = new Timer(new TimerCallback(DisconnectTimer_Elapsed), null, Client.Settings.SIMULATOR_TIMEOUT, Client.Settings.SIMULATOR_TIMEOUT); } if (setDefault) SetCurrentSim(simulator, seedcaps); // Fire the simulator connection callback if one is registered if (OnSimConnected != null) { try { OnSimConnected(simulator); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } // If enabled, send an AgentThrottle packet to the server to increase our bandwidth if (Client.Settings.SEND_AGENT_THROTTLE) Client.Throttle.Set(simulator); return simulator; } else { // Connection failed, remove this simulator from our list and destroy it lock (Simulators) Simulators.Remove(simulator); return null; } } else if (setDefault) { // We're already connected to this server, but need to set it to the default SetCurrentSim(simulator, seedcaps); // Move in to this simulator Client.Self.CompleteAgentMovement(simulator); // Send an initial AgentUpdate to complete our movement in to the sim if (Client.Settings.SEND_AGENT_UPDATES) Client.Self.Movement.SendUpdate(true, simulator); return simulator; } else { // Already connected to this simulator and wasn't asked to set it as the default, // just return a reference to the existing object return simulator; } } /// /// Initiate a blocking logout request. This will return when the logout /// handshake has completed or when Settings.LOGOUT_TIMEOUT /// has expired and the network layer is manually shut down /// public void Logout() { AutoResetEvent logoutEvent = new AutoResetEvent(false); LogoutCallback callback = delegate(List inventoryItems) { logoutEvent.Set(); }; OnLogoutReply += callback; // Send the packet requesting a clean logout RequestLogout(); // Wait for a logout response. If the response is received, shutdown // will be fired in the callback. Otherwise we fire it manually with // a NetworkTimeout type if (!logoutEvent.WaitOne(Client.Settings.LOGOUT_TIMEOUT, false)) Shutdown(DisconnectType.NetworkTimeout); OnLogoutReply -= callback; } /// /// Initiate the logout process. Check if logout succeeded with the /// OnLogoutReply event, and if this does not fire the /// Shutdown() function needs to be manually called /// public void RequestLogout() { // No need to run the disconnect timer any more if (DisconnectTimer != null) DisconnectTimer.Dispose(); // This will catch a Logout when the client is not logged in if (CurrentSim == null || !connected) { Client.Log("Ignoring RequestLogout(), client is already logged out", Helpers.LogLevel.Warning); return; } Client.Log("Logging out", Helpers.LogLevel.Info); // Send a logout request to the current sim LogoutRequestPacket logout = new LogoutRequestPacket(); logout.AgentData.AgentID = Client.Self.AgentID; logout.AgentData.SessionID = Client.Self.SessionID; CurrentSim.SendPacket(logout, true); } /// /// /// /// public void DisconnectSim(Simulator sim, bool sendCloseCircuit) { if (sim != null) { sim.Disconnect(sendCloseCircuit); // Fire the SimDisconnected event if a handler is registered if (OnSimDisconnected != null) { try { OnSimDisconnected(sim, DisconnectType.NetworkTimeout); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } lock (Simulators) Simulators.Remove(sim); if (Simulators.Count == 0) Shutdown(DisconnectType.SimShutdown); } else { Client.Log("DisconnectSim() called with a null Simulator reference", Helpers.LogLevel.Warning); } } /// /// Shutdown will disconnect all the sims except for the current sim /// first, and then kill the connection to CurrentSim. This should only /// be called if the logout process times out on RequestLogout /// public void Shutdown(DisconnectType type) { Client.Log("NetworkManager shutdown initiated", Helpers.LogLevel.Info); // Send a CloseCircuit packet to simulators if we are initiating the disconnect bool sendCloseCircuit = (type == DisconnectType.ClientInitiated || type == DisconnectType.NetworkTimeout); lock (Simulators) { // Disconnect all simulators except the current one for (int i = 0; i < Simulators.Count; i++) { if (Simulators[i] != null && Simulators[i] != CurrentSim) { Simulators[i].Disconnect(sendCloseCircuit); // Fire the SimDisconnected event if a handler is registered if (OnSimDisconnected != null) { try { OnSimDisconnected(Simulators[i], type); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } Simulators.Clear(); } if (CurrentSim != null) { // Kill the connection to the curent simulator CurrentSim.Disconnect(sendCloseCircuit); // Fire the SimDisconnected event if a handler is registered if (OnSimDisconnected != null) { try { OnSimDisconnected(CurrentSim, type); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } // Clear out all of the packets that never had time to process PacketInbox.Close(); connected = false; // Fire the disconnected callback if (OnDisconnected != null) { try { OnDisconnected(DisconnectType.ClientInitiated, String.Empty); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } /// /// Searches through the list of currently connected simulators to find /// one attached to the given IPEndPoint /// /// IPEndPoint of the Simulator to search for /// A Simulator reference on success, otherwise null public Simulator FindSimulator(IPEndPoint endPoint) { lock (Simulators) { for (int i = 0; i < Simulators.Count; i++) { if (Simulators[i].IPEndPoint.Equals(endPoint)) return Simulators[i]; } } return null; } /// /// Fire an event when an event queue connects for capabilities /// /// Simulator the event queue is attached to internal void RaiseConnectedEvent(Simulator simulator) { if (OnEventQueueRunning != null) { try { OnEventQueueRunning(simulator); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } private void PacketHandler() { IncomingPacket incomingPacket = new IncomingPacket(); Packet packet = null; Simulator simulator = null; while (connected) { // Reset packet to null for the check below packet = null; if (PacketInbox.Dequeue(200, ref incomingPacket)) { packet = incomingPacket.Packet; simulator = incomingPacket.Simulator; if (packet != null) { // Skip the ACK handling on packets synthesized from CAPS messages if (packet.Header.Sequence != 0) { #region ACK accounting // TODO: Replace PacketArchive Queue<> with something more efficient // Check the archives to see whether we already received this packet lock (simulator.PacketArchive) { if (simulator.PacketArchive.Contains(packet.Header.Sequence)) { if (packet.Header.Resent) { Client.DebugLog("Received resent packet #" + packet.Header.Sequence); } else { Client.Log(String.Format("Received a duplicate of packet #{0}, current type: {1}", packet.Header.Sequence, packet.Type), Helpers.LogLevel.Warning); } // Avoid firing a callback twice for the same packet continue; } else { // Keep the PacketArchive size within a certain capacity while (simulator.PacketArchive.Count >= Settings.PACKET_ARCHIVE_SIZE) { simulator.PacketArchive.Dequeue(); simulator.PacketArchive.Dequeue(); simulator.PacketArchive.Dequeue(); simulator.PacketArchive.Dequeue(); } simulator.PacketArchive.Enqueue(packet.Header.Sequence); } } #endregion ACK accounting #region ACK handling // Handle appended ACKs if (packet.Header.AppendedAcks) { lock (simulator.NeedAck) { for (int i = 0; i < packet.Header.AckList.Length; i++) simulator.NeedAck.Remove(packet.Header.AckList[i]); } } // Handle PacketAck packets if (packet.Type == PacketType.PacketAck) { PacketAckPacket ackPacket = (PacketAckPacket)packet; lock (simulator.NeedAck) { for (int i = 0; i < ackPacket.Packets.Length; i++) simulator.NeedAck.Remove(ackPacket.Packets[i].ID); } } #endregion ACK handling } #region FireCallbacks if (Client.Settings.SYNC_PACKETCALLBACKS) { PacketEvents.RaiseEvent(PacketType.Default, packet, simulator); PacketEvents.RaiseEvent(packet.Type, packet, simulator); } else { PacketEvents.BeginRaiseEvent(PacketType.Default, packet, simulator); PacketEvents.BeginRaiseEvent(packet.Type, packet, simulator); } #endregion FireCallbacks } } } } private void SetCurrentSim(Simulator simulator, string seedcaps) { if (simulator != CurrentSim) { Simulator oldSim = CurrentSim; lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators simulator.SetSeedCaps(seedcaps); // If the current simulator changed fire the callback if (OnCurrentSimChanged != null && simulator != oldSim) { try { OnCurrentSimChanged(oldSim); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } } } #region Timers private void DisconnectTimer_Elapsed(object obj) { if (!connected || CurrentSim == null) { if (DisconnectTimer != null) DisconnectTimer.Dispose(); connected = false; } else if (CurrentSim.DisconnectCandidate) { // The currently occupied simulator hasn't sent us any traffic in a while, shutdown Client.Log("Network timeout for the current simulator (" + CurrentSim.ToString() + "), logging out", Helpers.LogLevel.Warning); if (DisconnectTimer != null) DisconnectTimer.Dispose(); connected = false; // Shutdown the network layer Shutdown(DisconnectType.NetworkTimeout); } else { #region Check for timed out simulators // Figure out which sims need to be disconnected, then fire // all of the events to avoid calling DisconnectSim() inside // the Simulators lock List disconnectedSims = null; // Check all of the connected sims for disconnects lock (Simulators) { for (int i = 0; i < Simulators.Count; i++) { if (Simulators[i].DisconnectCandidate) { // Avoid initializing a new List<> every time the timer // fires with this piece of code if (disconnectedSims == null) disconnectedSims = new List(); disconnectedSims.Add(Simulators[i]); } else { Simulators[i].DisconnectCandidate = true; } } } // Actually disconnect each sim we detected as disconnected if (disconnectedSims != null) { for (int i = 0; i < disconnectedSims.Count; i++) { if (disconnectedSims[i] != null) { // This sim hasn't received any network traffic since the // timer last elapsed, consider it disconnected Client.Log("Network timeout for simulator " + disconnectedSims[i].ToString() + ", disconnecting", Helpers.LogLevel.Warning); DisconnectSim(disconnectedSims[i], true); } } } #endregion Check for timed out simulators } } #endregion Timers #region Packet Callbacks /// /// Called to deal with LogoutReply packet and fires off callback /// /// Full packet of type LogoutReplyPacket /// private void LogoutReplyHandler(Packet packet, Simulator simulator) { LogoutReplyPacket logout = (LogoutReplyPacket)packet; if ((logout.AgentData.SessionID == Client.Self.SessionID) && (logout.AgentData.AgentID == Client.Self.AgentID)) { Client.DebugLog("Logout reply received"); // Deal with callbacks, if any if (OnLogoutReply != null) { List itemIDs = new List(); foreach (LogoutReplyPacket.InventoryDataBlock InventoryData in logout.InventoryData) { itemIDs.Add(InventoryData.ItemID); } try { OnLogoutReply(itemIDs); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } // If we are receiving a LogoutReply packet assume this is a client initiated shutdown Shutdown(DisconnectType.ClientInitiated); } else { Client.Log("Invalid Session or Agent ID received in Logout Reply... ignoring", Helpers.LogLevel.Warning); } } private void StartPingCheckHandler(Packet packet, Simulator simulator) { StartPingCheckPacket incomingPing = (StartPingCheckPacket)packet; CompletePingCheckPacket ping = new CompletePingCheckPacket(); ping.PingID.PingID = incomingPing.PingID.PingID; ping.Header.Reliable = false; // TODO: We can use OldestUnacked to correct transmission errors // I don't think that's right. As far as I can tell, the Viewer // only uses this to prune its duplicate-checking buffer. -bushing SendPacket(ping, simulator); } private void PongHandler(Packet packet, Simulator simulator) { CompletePingCheckPacket pong = (CompletePingCheckPacket)packet; String retval = "Pong2: " + (Environment.TickCount - simulator.Stats.LastPingSent); if ((pong.PingID.PingID - simulator.Stats.LastPingID + 1) != 0) retval += " (gap of " + (pong.PingID.PingID - simulator.Stats.LastPingID + 1) + ")"; simulator.Stats.LastLag = Environment.TickCount - simulator.Stats.LastPingSent; simulator.Stats.ReceivedPongs++; // Client.Log(retval, Helpers.LogLevel.Info); } private void SimStatsHandler(Packet packet, Simulator simulator) { if ( ! Client.Settings.ENABLE_SIMSTATS ) { return; } SimStatsPacket stats = (SimStatsPacket)packet; for ( int i = 0 ; i < stats.Stat.Length ; i++ ) { SimStatsPacket.StatBlock s = stats.Stat[i]; switch (s.StatID ) { case 0: simulator.Stats.Dilation = s.StatValue; break; case 1: simulator.Stats.FPS = Convert.ToInt32(s.StatValue); break; case 2: simulator.Stats.PhysicsFPS = s.StatValue; break; case 3: simulator.Stats.AgentUpdates = s.StatValue; break; case 4: simulator.Stats.FrameTime = s.StatValue; break; case 5: simulator.Stats.NetTime = s.StatValue; break; case 6: simulator.Stats.OtherTime = s.StatValue; break; case 7: simulator.Stats.PhysicsTime = s.StatValue; break; case 8: simulator.Stats.AgentTime = s.StatValue; break; case 9: simulator.Stats.ImageTime = s.StatValue; break; case 10: simulator.Stats.ScriptTime = s.StatValue; break; case 11: simulator.Stats.Objects = Convert.ToInt32(s.StatValue); break; case 12: simulator.Stats.ScriptedObjects = Convert.ToInt32(s.StatValue); break; case 13: simulator.Stats.Agents = Convert.ToInt32(s.StatValue); break; case 14: simulator.Stats.ChildAgents = Convert.ToInt32(s.StatValue); break; case 15: simulator.Stats.ActiveScripts = Convert.ToInt32(s.StatValue); break; case 16: simulator.Stats.LSLIPS = Convert.ToInt32(s.StatValue); break; case 17: simulator.Stats.INPPS = Convert.ToInt32(s.StatValue); break; case 18: simulator.Stats.OUTPPS = Convert.ToInt32(s.StatValue); break; case 19: simulator.Stats.PendingDownloads = Convert.ToInt32(s.StatValue); break; case 20: simulator.Stats.PendingUploads = Convert.ToInt32(s.StatValue); break; case 21: simulator.Stats.VirtualSize = Convert.ToInt32(s.StatValue); break; case 22: simulator.Stats.ResidentSize = Convert.ToInt32(s.StatValue); break; case 23: simulator.Stats.PendingLocalUploads = Convert.ToInt32(s.StatValue); break; case 24: simulator.Stats.UnackedBytes = Convert.ToInt32(s.StatValue); break; } } } private void RegionHandshakeHandler(Packet packet, Simulator simulator) { RegionHandshakePacket handshake = (RegionHandshakePacket)packet; simulator.ID = handshake.RegionInfo.CacheID; simulator.IsEstateManager = handshake.RegionInfo.IsEstateManager; simulator.Name = Helpers.FieldToUTF8String(handshake.RegionInfo.SimName); simulator.SimOwner = handshake.RegionInfo.SimOwner; simulator.TerrainBase0 = handshake.RegionInfo.TerrainBase0; simulator.TerrainBase1 = handshake.RegionInfo.TerrainBase1; simulator.TerrainBase2 = handshake.RegionInfo.TerrainBase2; simulator.TerrainBase3 = handshake.RegionInfo.TerrainBase3; simulator.TerrainDetail0 = handshake.RegionInfo.TerrainDetail0; simulator.TerrainDetail1 = handshake.RegionInfo.TerrainDetail1; simulator.TerrainDetail2 = handshake.RegionInfo.TerrainDetail2; simulator.TerrainDetail3 = handshake.RegionInfo.TerrainDetail3; simulator.TerrainHeightRange00 = handshake.RegionInfo.TerrainHeightRange00; simulator.TerrainHeightRange01 = handshake.RegionInfo.TerrainHeightRange01; simulator.TerrainHeightRange10 = handshake.RegionInfo.TerrainHeightRange10; simulator.TerrainHeightRange11 = handshake.RegionInfo.TerrainHeightRange11; simulator.TerrainStartHeight00 = handshake.RegionInfo.TerrainStartHeight00; simulator.TerrainStartHeight01 = handshake.RegionInfo.TerrainStartHeight01; simulator.TerrainStartHeight10 = handshake.RegionInfo.TerrainStartHeight10; simulator.TerrainStartHeight11 = handshake.RegionInfo.TerrainStartHeight11; simulator.WaterHeight = handshake.RegionInfo.WaterHeight; simulator.Flags = (Simulator.RegionFlags)handshake.RegionInfo.RegionFlags; simulator.BillableFactor = handshake.RegionInfo.BillableFactor; simulator.Access = (Simulator.SimAccess)handshake.RegionInfo.SimAccess; Client.Log("Received a region handshake for " + simulator.ToString(), Helpers.LogLevel.Info); // Send a RegionHandshakeReply RegionHandshakeReplyPacket reply = new RegionHandshakeReplyPacket(); reply.AgentData.AgentID = Client.Self.AgentID; reply.AgentData.SessionID = Client.Self.SessionID; reply.RegionInfo.Flags = 0; SendPacket(reply, simulator); // We're officially connected to this sim simulator.connected = true; simulator.ConnectedEvent.Set(); } private void ParcelOverlayHandler(Packet packet, Simulator simulator) { ParcelOverlayPacket overlay = (ParcelOverlayPacket)packet; if (overlay.ParcelData.SequenceID >= 0 && overlay.ParcelData.SequenceID <= 3) { Buffer.BlockCopy(overlay.ParcelData.Data, 0, simulator.ParcelOverlay, overlay.ParcelData.SequenceID * 1024, 1024); simulator.ParcelOverlaysReceived++; if (simulator.ParcelOverlaysReceived > 3) { // TODO: ParcelOverlaysReceived should become internal, and reset to zero every // time it hits four. Also need a callback here } } else { Client.Log("Parcel overlay with sequence ID of " + overlay.ParcelData.SequenceID + " received from " + simulator.ToString(), Helpers.LogLevel.Warning); } } private void EnableSimulatorHandler(Packet packet, Simulator simulator) { if (!Client.Settings.MULTIPLE_SIMS) return; EnableSimulatorPacket p = (EnableSimulatorPacket)packet; IPEndPoint endPoint = new IPEndPoint(p.SimulatorInfo.IP, p.SimulatorInfo.Port); // First, check to see if we've already started connecting to this sim if (FindSimulator(endPoint) != null) return; IPAddress address = new IPAddress(p.SimulatorInfo.IP); if (Connect(address, p.SimulatorInfo.Port, p.SimulatorInfo.Handle, false, LoginSeedCapability) == null) { Client.Log("Unabled to connect to new sim " + address + ":" + p.SimulatorInfo.Port, Helpers.LogLevel.Error); return; } } private void DisableSimulatorHandler(Packet packet, Simulator simulator) { Client.DebugLog("Received a DisableSimulator packet from " + simulator + ", shutting it down"); DisconnectSim(simulator, false); } private void KickUserHandler(Packet packet, Simulator simulator) { string message = Helpers.FieldToUTF8String(((KickUserPacket)packet).UserInfo.Reason); // Fire the callback to let client apps know we are shutting down if (OnDisconnected != null) { try { OnDisconnected(DisconnectType.ServerInitiated, message); } catch (Exception e) { Client.Log(e.ToString(), Helpers.LogLevel.Error); } } // Shutdown the network layer Shutdown(DisconnectType.ServerInitiated); } #endregion Packet Callbacks } }