diff --git a/LibreMetaverse/AppearanceManager.cs b/LibreMetaverse/AppearanceManager.cs index 3c45460..24b89bd 100644 --- a/LibreMetaverse/AppearanceManager.cs +++ b/LibreMetaverse/AppearanceManager.cs @@ -2155,6 +2155,10 @@ namespace OpenMetaverse break; } } + + GetFolderWearables(COF.UUID, out List wearables, out List attachments); + Client.Appearance.AddAttachments(attachments, false, false); + return COF; } @@ -2396,40 +2400,29 @@ namespace OpenMetaverse { wearables = new List(); attachments = new List(); - var objects = Client.Inventory.FolderContents(folder, Client.Self.AgentID, false, true, - InventorySortOrder.ByName, INVENTORY_TIMEOUT); - - if (objects != null) + KeyValuePair> reply = Client.Inventory.FolderContentsWithReply(folder, Client.Self.AgentID, true, true, InventorySortOrder.ByName, INVENTORY_TIMEOUT, false); + foreach(InventoryBase entry in reply.Value) { - foreach (var ib in objects) + if(entry is InventoryWearable wearable) { - if (ib is InventoryWearable wearable) - { - Logger.DebugLog("Adding wearable " + wearable.Name, Client); - wearables.Add(wearable); - } - else if (ib is InventoryAttachment attachment) - { - Logger.DebugLog("Adding attachment (attachment) " + attachment.Name, Client); - attachments.Add(attachment); - } - else if (ib is InventoryObject inventoryObject) - { - Logger.DebugLog("Adding attachment (object) " + inventoryObject.Name, Client); - attachments.Add(inventoryObject); - } - else - { - Logger.DebugLog("Ignoring inventory item " + ib.Name, Client); - } + Logger.DebugLog("Adding wearable " + wearable.Name, Client); + wearables.Add(wearable); + } + else if (entry is InventoryAttachment attachment) + { + Logger.DebugLog("Adding attachment (attachment) " + attachment.Name, Client); + attachments.Add(attachment); + } + else if (entry is InventoryObject inventoryObject) + { + Logger.DebugLog("Adding attachment (object) " + inventoryObject.Name, Client); + attachments.Add(inventoryObject); + } + else + { + Logger.DebugLog("Ignoring inventory item " + entry.Name, Client); } } - else - { - Logger.Log("Failed to download folder contents of + " + folder, Helpers.LogLevel.Error, Client); - return false; - } - return true; } diff --git a/LibreMetaverse/NetworkManager.cs b/LibreMetaverse/NetworkManager.cs index 502e915..2494115 100644 --- a/LibreMetaverse/NetworkManager.cs +++ b/LibreMetaverse/NetworkManager.cs @@ -1,564 +1,564 @@ -/* - * Copyright (c) 2006-2016, openmetaverse.co - * 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 openmetaverse.co 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.Generic; -using System.Linq; -using System.Net; -using System.Threading.Channels; -using System.Threading.Tasks; -using OpenMetaverse.Packets; -using OpenMetaverse.Interfaces; -using OpenMetaverse.Messages.Linden; - -namespace OpenMetaverse -{ - /// - /// NetworkManager is responsible for managing the network layer of - /// OpenMetaverse. 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 - { - // TODO: Implement throttle class for incoming and outgoing packets - - - #region Enums - - /// - /// 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 - } - - #endregion Enums - - #region Structs - - /// - /// Holds a simulator reference and a decoded packet, these structs are put in - /// the packet inbox for event handling - /// - public class IncomingPacket - { - /// Reference to the simulator that this packet came from - public Simulator Simulator; - - /// Packet that needs to be processed - public Packet Packet; - } - - /// - /// Holds a simulator reference and a serialized packet, these structs are put in - /// the packet outbox for sending - /// - public class OutgoingPacket - { - /// Reference to the simulator this packet is destined for - public readonly Simulator Simulator; - /// Packet that needs to be sent - public readonly UDPPacketBuffer Buffer; - /// Sequence number of the wrapped packet - public uint SequenceNumber; - /// Number of times this packet has been resent - public int ResendCount; - /// Environment.TickCount when this packet was last sent over the wire - public int TickCount; - /// Type of the packet - public PacketType Type; - - public OutgoingPacket(Simulator simulator, UDPPacketBuffer buffer, PacketType type) - { - Simulator = simulator; - Buffer = buffer; - SequenceNumber = 0; - ResendCount = 0; - TickCount = 0; - Type = type; - } - } - - #endregion Structs - - #region Delegates - - /// The event subscribers, null if no subscribers - private EventHandler m_PacketSent; - - ///Raises the PacketSent Event - /// A PacketSentEventArgs object containing - /// the data sent from the simulator - protected virtual void OnPacketSent(PacketSentEventArgs e) - { - EventHandler handler = m_PacketSent; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_PacketSentLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler PacketSent - { - add { lock (m_PacketSentLock) { m_PacketSent += value; } } - remove { lock (m_PacketSentLock) { m_PacketSent -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_LoggedOut; - - ///Raises the LoggedOut Event - /// A LoggedOutEventArgs object containing - /// the data sent from the simulator - protected virtual void OnLoggedOut(LoggedOutEventArgs e) - { - EventHandler handler = m_LoggedOut; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_LoggedOutLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler LoggedOut - { - add { lock (m_LoggedOutLock) { m_LoggedOut += value; } } - remove { lock (m_LoggedOutLock) { m_LoggedOut -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_SimConnecting; - - ///Raises the SimConnecting Event - /// A SimConnectingEventArgs object containing - /// the data sent from the simulator - protected virtual void OnSimConnecting(SimConnectingEventArgs e) - { - EventHandler handler = m_SimConnecting; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_SimConnectingLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler SimConnecting - { - add { lock (m_SimConnectingLock) { m_SimConnecting += value; } } - remove { lock (m_SimConnectingLock) { m_SimConnecting -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_SimConnected; - - ///Raises the SimConnected Event - /// A SimConnectedEventArgs object containing - /// the data sent from the simulator - protected virtual void OnSimConnected(SimConnectedEventArgs e) - { - EventHandler handler = m_SimConnected; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_SimConnectedLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler SimConnected - { - add { lock (m_SimConnectedLock) { m_SimConnected += value; } } - remove { lock (m_SimConnectedLock) { m_SimConnected -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_SimDisconnected; - - ///Raises the SimDisconnected Event - /// A SimDisconnectedEventArgs object containing - /// the data sent from the simulator - protected virtual void OnSimDisconnected(SimDisconnectedEventArgs e) - { - EventHandler handler = m_SimDisconnected; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_SimDisconnectedLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler SimDisconnected - { - add { lock (m_SimDisconnectedLock) { m_SimDisconnected += value; } } - remove { lock (m_SimDisconnectedLock) { m_SimDisconnected -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_Disconnected; - - ///Raises the Disconnected Event - /// A DisconnectedEventArgs object containing - /// the data sent from the simulator - protected virtual void OnDisconnected(DisconnectedEventArgs e) - { - EventHandler handler = m_Disconnected; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_DisconnectedLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler Disconnected - { - add { lock (m_DisconnectedLock) { m_Disconnected += value; } } - remove { lock (m_DisconnectedLock) { m_Disconnected -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_SimChanged; - - ///Raises the SimChanged Event - /// A SimChangedEventArgs object containing - /// the data sent from the simulator - protected virtual void OnSimChanged(SimChangedEventArgs e) - { - EventHandler handler = m_SimChanged; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_SimChangedLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler SimChanged - { - add { lock (m_SimChangedLock) { m_SimChanged += value; } } - remove { lock (m_SimChangedLock) { m_SimChanged -= value; } } - } - - /// The event subscribers, null if no subscribers - private EventHandler m_EventQueueRunning; - - ///Raises the EventQueueRunning Event - /// A EventQueueRunningEventArgs object containing - /// the data sent from the simulator - protected virtual void OnEventQueueRunning(EventQueueRunningEventArgs e) - { - EventHandler handler = m_EventQueueRunning; - handler?.Invoke(this, e); - } - - /// Thread sync lock object - private readonly object m_EventQueueRunningLock = new object(); - - /// Raised when the simulator sends us data containing - /// ... - public event EventHandler EventQueueRunning - { - add { lock (m_EventQueueRunningLock) { m_EventQueueRunning += value; } } - remove { lock (m_EventQueueRunningLock) { m_EventQueueRunning -= value; } } - } - - #endregion Delegates - - #region Properties - - /// Unique identifier associated with our connections to - /// simulators - public uint CircuitCode { get; set; } - - /// The simulator that the logged in avatar is currently - /// occupying - public Simulator CurrentSim { get; set; } - - /// Shows whether the network layer is logged in to the - /// grid or not - public bool Connected { get; private set; } - - /// Number of packets in the incoming queue - public int InboxCount => _packetInboxCount; - - /// Number of packets in the outgoing queue - public int OutboxCount => _packetOutboxCount; - - #endregion Properties - - /// All of the simulators we are currently connected to - public List Simulators = new List(); - - /// Handlers for incoming capability events - internal CapsEventDictionary CapsEvents; - /// Handlers for incoming packets - internal PacketEventDictionary PacketEvents; - - /// Incoming packets that are awaiting handling - private Channel _packetInbox; - - private int _packetInboxCount = 0; - - /// Outgoing packets that are awaiting handling - private Channel _packetOutbox; - - private int _packetOutboxCount = 0; - - private readonly GridClient Client; - private Timer DisconnectTimer; - - private long lastpacketwarning = 0; - - /// - /// Default constructor - /// - /// Reference to the GridClient object - public NetworkManager(GridClient client) - { - Client = client; - - PacketEvents = new PacketEventDictionary(client); - CapsEvents = new CapsEventDictionary(client); - - // Register internal CAPS callbacks - RegisterEventCallback("EnableSimulator", EnableSimulatorHandler); - - // Register the internal callbacks - RegisterCallback(PacketType.RegionHandshake, RegionHandshakeHandler); - RegisterCallback(PacketType.StartPingCheck, StartPingCheckHandler, false); - RegisterCallback(PacketType.DisableSimulator, DisableSimulatorHandler); - RegisterCallback(PacketType.KickUser, KickUserHandler); - RegisterCallback(PacketType.LogoutReply, LogoutReplyHandler); - RegisterCallback(PacketType.CompletePingCheck, CompletePingCheckHandler, false); - RegisterCallback(PacketType.SimStats, SimStatsHandler, false); - - // 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 the library - /// - /// Packet type to trigger events for - /// Callback to fire when a packet of this type - /// is received - public void RegisterCallback(PacketType type, EventHandler callback) - { - RegisterCallback(type, callback, true); - } - - /// - /// 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 the library - /// - /// Packet type to trigger events for - /// Callback to fire when a packet of this type - /// is received - /// True if the callback should be ran - /// asynchronously. Only set this to false (synchronous for callbacks - /// that will always complete quickly) - /// If any callback for a packet type is marked as - /// asynchronous, all callbacks for that packet type will be fired - /// asynchronously - public void RegisterCallback(PacketType type, EventHandler callback, bool isAsync) - { - PacketEvents.RegisterEvent(type, callback, isAsync); - } - - /// - /// 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 the library - /// - /// Packet type this callback is registered with - /// Callback to stop firing events for - public void UnregisterCallback(PacketType type, EventHandler 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 - /// the library - /// - /// 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 - /// the library - /// - /// 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) - { - SendPacket(packet, CurrentSim); - } - - /// - /// 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 && Client.Network.Simulators.Count >= 1) - { - Logger.DebugLog("simulator object was null, using first found connected simulator", Client); - simulator = Client.Network.Simulators[0]; - } - if (simulator != null) - { - simulator.SendPacket(packet); - } - else - { - NetworkInvaildWarning("simulator", "SendPacket"); - } - } - - /// - /// Add a packet to the Inbox queue to process - /// - /// Incoming packet to process - public void EnqueueIncoming(IncomingPacket packet) - { - if (_packetInbox != null) - { - if (_packetInbox.Writer.TryWrite(packet)) - Interlocked.Increment(ref _packetInboxCount); - } - else - { - NetworkInvaildWarning("_packetInbox", "EnqueueIncoming"); - } - } - - /// - /// adds a debug message when you try to access a network item - /// while they are still null - /// - /// what - /// where - protected void NetworkInvaildWarning(string source,string function) - { - long now = DateTimeOffset.Now.ToUnixTimeSeconds(); - long dif = lastpacketwarning - now; - if (dif > 10) - { - lastpacketwarning = now; - Logger.Log(source+" is null (Are we disconnected?) - from: " + function, - Helpers.LogLevel.Debug); - } - } - - /// - /// Add a packet to the Inbox queue to process - /// - /// Incoming packet to process - public void EnqueueOutgoing(OutgoingPacket packet) - { - if (_packetOutbox != null) - { - if (_packetOutbox.Writer.TryWrite(packet)) - Interlocked.Increment(ref _packetOutboxCount); - } - else - { - NetworkInvaildWarning("_packetOutbox", "EnqueueOutgoing"); - } - } - - /// - /// Connect to simulator assuming legacy region size - /// - /// IP address to connect to - /// Port to connect to - /// - /// - /// - /// - public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, Uri seedcaps) - { - return Connect(ip, port, handle, setDefault, seedcaps, Simulator.DefaultRegionSizeX, Simulator.DefaultRegionSizeY); - } - /// - /// 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 - /// Size of the region in X meters - /// Size of the region in Y meters - /// A Simulator object on success, otherwise null - public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, Uri seedcaps, uint sizeX, uint sizeY) - { - IPEndPoint endPoint = new IPEndPoint(ip, port); - return Connect(endPoint, handle, setDefault, seedcaps, sizeX, sizeY); - } - +/* + * Copyright (c) 2006-2016, openmetaverse.co + * 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 openmetaverse.co 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.Generic; +using System.Linq; +using System.Net; +using System.Threading.Channels; +using System.Threading.Tasks; +using OpenMetaverse.Packets; +using OpenMetaverse.Interfaces; +using OpenMetaverse.Messages.Linden; + +namespace OpenMetaverse +{ + /// + /// NetworkManager is responsible for managing the network layer of + /// OpenMetaverse. 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 + { + // TODO: Implement throttle class for incoming and outgoing packets + + + #region Enums + + /// + /// 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 + } + + #endregion Enums + + #region Structs + + /// + /// Holds a simulator reference and a decoded packet, these structs are put in + /// the packet inbox for event handling + /// + public class IncomingPacket + { + /// Reference to the simulator that this packet came from + public Simulator Simulator; + + /// Packet that needs to be processed + public Packet Packet; + } + + /// + /// Holds a simulator reference and a serialized packet, these structs are put in + /// the packet outbox for sending + /// + public class OutgoingPacket + { + /// Reference to the simulator this packet is destined for + public readonly Simulator Simulator; + /// Packet that needs to be sent + public readonly UDPPacketBuffer Buffer; + /// Sequence number of the wrapped packet + public uint SequenceNumber; + /// Number of times this packet has been resent + public int ResendCount; + /// Environment.TickCount when this packet was last sent over the wire + public int TickCount; + /// Type of the packet + public PacketType Type; + + public OutgoingPacket(Simulator simulator, UDPPacketBuffer buffer, PacketType type) + { + Simulator = simulator; + Buffer = buffer; + SequenceNumber = 0; + ResendCount = 0; + TickCount = 0; + Type = type; + } + } + + #endregion Structs + + #region Delegates + + /// The event subscribers, null if no subscribers + private EventHandler m_PacketSent; + + ///Raises the PacketSent Event + /// A PacketSentEventArgs object containing + /// the data sent from the simulator + protected virtual void OnPacketSent(PacketSentEventArgs e) + { + EventHandler handler = m_PacketSent; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_PacketSentLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler PacketSent + { + add { lock (m_PacketSentLock) { m_PacketSent += value; } } + remove { lock (m_PacketSentLock) { m_PacketSent -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_LoggedOut; + + ///Raises the LoggedOut Event + /// A LoggedOutEventArgs object containing + /// the data sent from the simulator + protected virtual void OnLoggedOut(LoggedOutEventArgs e) + { + EventHandler handler = m_LoggedOut; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_LoggedOutLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler LoggedOut + { + add { lock (m_LoggedOutLock) { m_LoggedOut += value; } } + remove { lock (m_LoggedOutLock) { m_LoggedOut -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_SimConnecting; + + ///Raises the SimConnecting Event + /// A SimConnectingEventArgs object containing + /// the data sent from the simulator + protected virtual void OnSimConnecting(SimConnectingEventArgs e) + { + EventHandler handler = m_SimConnecting; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_SimConnectingLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler SimConnecting + { + add { lock (m_SimConnectingLock) { m_SimConnecting += value; } } + remove { lock (m_SimConnectingLock) { m_SimConnecting -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_SimConnected; + + ///Raises the SimConnected Event + /// A SimConnectedEventArgs object containing + /// the data sent from the simulator + protected virtual void OnSimConnected(SimConnectedEventArgs e) + { + EventHandler handler = m_SimConnected; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_SimConnectedLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler SimConnected + { + add { lock (m_SimConnectedLock) { m_SimConnected += value; } } + remove { lock (m_SimConnectedLock) { m_SimConnected -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_SimDisconnected; + + ///Raises the SimDisconnected Event + /// A SimDisconnectedEventArgs object containing + /// the data sent from the simulator + protected virtual void OnSimDisconnected(SimDisconnectedEventArgs e) + { + EventHandler handler = m_SimDisconnected; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_SimDisconnectedLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler SimDisconnected + { + add { lock (m_SimDisconnectedLock) { m_SimDisconnected += value; } } + remove { lock (m_SimDisconnectedLock) { m_SimDisconnected -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_Disconnected; + + ///Raises the Disconnected Event + /// A DisconnectedEventArgs object containing + /// the data sent from the simulator + protected virtual void OnDisconnected(DisconnectedEventArgs e) + { + EventHandler handler = m_Disconnected; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_DisconnectedLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler Disconnected + { + add { lock (m_DisconnectedLock) { m_Disconnected += value; } } + remove { lock (m_DisconnectedLock) { m_Disconnected -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_SimChanged; + + ///Raises the SimChanged Event + /// A SimChangedEventArgs object containing + /// the data sent from the simulator + protected virtual void OnSimChanged(SimChangedEventArgs e) + { + EventHandler handler = m_SimChanged; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_SimChangedLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler SimChanged + { + add { lock (m_SimChangedLock) { m_SimChanged += value; } } + remove { lock (m_SimChangedLock) { m_SimChanged -= value; } } + } + + /// The event subscribers, null if no subscribers + private EventHandler m_EventQueueRunning; + + ///Raises the EventQueueRunning Event + /// A EventQueueRunningEventArgs object containing + /// the data sent from the simulator + protected virtual void OnEventQueueRunning(EventQueueRunningEventArgs e) + { + EventHandler handler = m_EventQueueRunning; + handler?.Invoke(this, e); + } + + /// Thread sync lock object + private readonly object m_EventQueueRunningLock = new object(); + + /// Raised when the simulator sends us data containing + /// ... + public event EventHandler EventQueueRunning + { + add { lock (m_EventQueueRunningLock) { m_EventQueueRunning += value; } } + remove { lock (m_EventQueueRunningLock) { m_EventQueueRunning -= value; } } + } + + #endregion Delegates + + #region Properties + + /// Unique identifier associated with our connections to + /// simulators + public uint CircuitCode { get; set; } + + /// The simulator that the logged in avatar is currently + /// occupying + public Simulator CurrentSim { get; set; } + + /// Shows whether the network layer is logged in to the + /// grid or not + public bool Connected { get; private set; } + + /// Number of packets in the incoming queue + public int InboxCount => _packetInboxCount; + + /// Number of packets in the outgoing queue + public int OutboxCount => _packetOutboxCount; + + #endregion Properties + + /// All of the simulators we are currently connected to + public List Simulators = new List(); + + /// Handlers for incoming capability events + internal CapsEventDictionary CapsEvents; + /// Handlers for incoming packets + internal PacketEventDictionary PacketEvents; + + /// Incoming packets that are awaiting handling + private Channel _packetInbox; + + private int _packetInboxCount = 0; + + /// Outgoing packets that are awaiting handling + private Channel _packetOutbox; + + private int _packetOutboxCount = 0; + + private readonly GridClient Client; + private Timer DisconnectTimer; + + private long lastpacketwarning = 0; + + /// + /// Default constructor + /// + /// Reference to the GridClient object + public NetworkManager(GridClient client) + { + Client = client; + + PacketEvents = new PacketEventDictionary(client); + CapsEvents = new CapsEventDictionary(client); + + // Register internal CAPS callbacks + RegisterEventCallback("EnableSimulator", EnableSimulatorHandler); + + // Register the internal callbacks + RegisterCallback(PacketType.RegionHandshake, RegionHandshakeHandler); + RegisterCallback(PacketType.StartPingCheck, StartPingCheckHandler, false); + RegisterCallback(PacketType.DisableSimulator, DisableSimulatorHandler); + RegisterCallback(PacketType.KickUser, KickUserHandler); + RegisterCallback(PacketType.LogoutReply, LogoutReplyHandler); + RegisterCallback(PacketType.CompletePingCheck, CompletePingCheckHandler, false); + RegisterCallback(PacketType.SimStats, SimStatsHandler, false); + + // 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 the library + /// + /// Packet type to trigger events for + /// Callback to fire when a packet of this type + /// is received + public void RegisterCallback(PacketType type, EventHandler callback) + { + RegisterCallback(type, callback, true); + } + + /// + /// 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 the library + /// + /// Packet type to trigger events for + /// Callback to fire when a packet of this type + /// is received + /// True if the callback should be ran + /// asynchronously. Only set this to false (synchronous for callbacks + /// that will always complete quickly) + /// If any callback for a packet type is marked as + /// asynchronous, all callbacks for that packet type will be fired + /// asynchronously + public void RegisterCallback(PacketType type, EventHandler callback, bool isAsync) + { + PacketEvents.RegisterEvent(type, callback, isAsync); + } + + /// + /// 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 the library + /// + /// Packet type this callback is registered with + /// Callback to stop firing events for + public void UnregisterCallback(PacketType type, EventHandler 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 + /// the library + /// + /// 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 + /// the library + /// + /// 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) + { + SendPacket(packet, CurrentSim); + } + + /// + /// 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 && Client.Network.Simulators.Count >= 1) + { + Logger.DebugLog("simulator object was null, using first found connected simulator", Client); + simulator = Client.Network.Simulators[0]; + } + if (simulator != null) + { + simulator.SendPacket(packet); + } + else + { + NetworkInvaildWarning("simulator", "SendPacket"); + } + } + + /// + /// Add a packet to the Inbox queue to process + /// + /// Incoming packet to process + public void EnqueueIncoming(IncomingPacket packet) + { + if (_packetInbox != null) + { + if (_packetInbox.Writer.TryWrite(packet)) + Interlocked.Increment(ref _packetInboxCount); + } + else + { + NetworkInvaildWarning("_packetInbox", "EnqueueIncoming"); + } + } + + /// + /// adds a debug message when you try to access a network item + /// while they are still null + /// + /// what + /// where + protected void NetworkInvaildWarning(string source,string function) + { + long now = DateTimeOffset.Now.ToUnixTimeSeconds(); + long dif = lastpacketwarning - now; + if (dif > 10) + { + lastpacketwarning = now; + Logger.Log(source+" is null (Are we disconnected?) - from: " + function, + Helpers.LogLevel.Debug); + } + } + + /// + /// Add a packet to the Inbox queue to process + /// + /// Incoming packet to process + public void EnqueueOutgoing(OutgoingPacket packet) + { + if (_packetOutbox != null) + { + if (_packetOutbox.Writer.TryWrite(packet)) + Interlocked.Increment(ref _packetOutboxCount); + } + else + { + NetworkInvaildWarning("_packetOutbox", "EnqueueOutgoing"); + } + } + + /// + /// Connect to simulator assuming legacy region size + /// + /// IP address to connect to + /// Port to connect to + /// + /// + /// + /// + public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, Uri seedcaps) + { + return Connect(ip, port, handle, setDefault, seedcaps, Simulator.DefaultRegionSizeX, Simulator.DefaultRegionSizeY); + } + /// + /// 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 + /// Size of the region in X meters + /// Size of the region in Y meters + /// A Simulator object on success, otherwise null + public Simulator Connect(IPAddress ip, ushort port, ulong handle, bool setDefault, Uri seedcaps, uint sizeX, uint sizeY) + { + IPEndPoint endPoint = new IPEndPoint(ip, port); + return Connect(endPoint, handle, setDefault, seedcaps, sizeX, sizeY); + } + /// /// Connect to simulator assuming legacy region size /// @@ -569,894 +569,893 @@ namespace OpenMetaverse /// public Simulator Connect(IPEndPoint endPoint, ulong handle, bool setDefault, Uri seedcaps) { return Connect(endPoint, handle, setDefault, seedcaps, Simulator.DefaultRegionSizeX, Simulator.DefaultRegionSizeY); - } - /// - /// 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 - /// Size of the region in X meters - /// Size of the region in Y meters - /// A Simulator object on success, otherwise null - public Simulator Connect(IPEndPoint endPoint, ulong handle, bool setDefault, Uri seedcaps, uint sizeX, uint sizeY) - { - Simulator simulator = FindSimulator(endPoint); - - if (simulator == null) - { - // We're not tracking this sim, create a new Simulator object - simulator = new Simulator(Client, endPoint, handle, sizeX, sizeY); - - // Immediately add this simulator to the list of current sims. It will be removed if the - // connection fails - lock (Simulators) Simulators.Add(simulator); - } - - if (_packetInbox == null || _packetOutbox == null) - { - var options = new UnboundedChannelOptions() {SingleReader = true}; - - _packetInbox = Channel.CreateUnbounded(options); - _packetOutbox = Channel.CreateUnbounded(options); - - Task.Run(IncomingPacketHandler); - Task.Run(OutgoingPacketHandler); - } - - if (!simulator.Connected) - { - // Mark that we are connecting/connected to the grid - // - Connected = true; - - // raise the SimConnecting event and allow any event - // subscribers to cancel the connection - if (m_SimConnecting != null) - { - SimConnectingEventArgs args = new SimConnectingEventArgs(simulator); - OnSimConnecting(args); - - if (args.Cancel) - { - // Callback is requesting that we abort this connection - lock (Simulators) - { - Simulators.Remove(simulator); - } - return null; - } - } - - // 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(DisconnectTimer_Elapsed, null, - Client.Settings.SIMULATOR_TIMEOUT, Client.Settings.SIMULATOR_TIMEOUT); - } - - if (setDefault) - { - SetCurrentSim(simulator, seedcaps); - } - - // Raise the SimConnected event - if (m_SimConnected != null) - { - OnSimConnected(new SimConnectedEventArgs(simulator)); - } - - // 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) - { - // Move in to this simulator - simulator.handshakeComplete = false; - simulator.UseCircuitCode(true); - Client.Self.CompleteAgentMovement(simulator); - - // We're already connected to this server, but need to set it to the default - SetCurrentSim(simulator, seedcaps); - - // 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; - } - } - - /// - /// Begins the non-blocking logout. Makes sure that the LoggedOut event is - /// called even if the server does not send a logout reply, and Shutdown() - /// is properly called. - /// - public void BeginLogout() - { - // Wait for a logout response (by way of the LoggedOut event. If the - // response is received, shutdown will be fired in the callback. - // Otherwise we fire it manually with a NetworkTimeout type after LOGOUT_TIMEOUT - System.Timers.Timer timeout = new System.Timers.Timer(); - - void Callback(object sender, LoggedOutEventArgs e) - { - Shutdown(DisconnectType.ClientInitiated); - timeout.Stop(); - } - - LoggedOut += Callback; - - timeout.Interval = Client.Settings.LOGOUT_TIMEOUT; - timeout.Elapsed += delegate - { - timeout.Stop(); - Shutdown(DisconnectType.NetworkTimeout); - OnLoggedOut(new LoggedOutEventArgs(new List())); - }; - timeout.Start(); - - // Send the packet requesting a clean logout - RequestLogout(); - - LoggedOut -= Callback; - } - - /// - /// 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); - - void Callback(object sender, LoggedOutEventArgs e) - { - logoutEvent.Set(); - } - - LoggedOut += 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); - } - - LoggedOut -= Callback; - } - - /// - /// Initiate the logout process. 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(); - DisconnectTimer = null; - } - - // This will catch a Logout when the client is not logged in - if (CurrentSim == null || !Connected) - { - Logger.Log("Ignoring RequestLogout(), client is already logged out", Helpers.LogLevel.Warning, Client); - return; - } - - Logger.Log("Logging out", Helpers.LogLevel.Info, Client); - - // Send a logout request to the current sim - LogoutRequestPacket logout = new LogoutRequestPacket - { - AgentData = - { - AgentID = Client.Self.AgentID, - SessionID = Client.Self.SessionID - } - }; - SendPacket(logout); - } - - /// - /// Close a connection to the given simulator - /// - /// - /// - public void DisconnectSim(Simulator simulator, bool sendCloseCircuit) - { - if (simulator != null) - { - simulator.Disconnect(sendCloseCircuit); - - // Fire the SimDisconnected event if a handler is registered - if (m_SimDisconnected != null) - { - OnSimDisconnected(new SimDisconnectedEventArgs(simulator, DisconnectType.NetworkTimeout)); - } - - int simulatorsCount; - lock (Simulators) - { - Simulators.Remove(simulator); - simulatorsCount = Simulators.Count; - } - - if (simulatorsCount == 0) - { - Shutdown(DisconnectType.SimShutdown, $"You have disconnected from {simulator.Name}."); - } - } - else - { - NetworkInvaildWarning("simulator", "DisconnectSim"); - } - } - - - /// - /// This method disconnects Client from all simulators leaving CurrentSim - /// last to disconnect. - /// - /// - /// This should only be called if the logout process times out on - /// RequestLogout - /// - /// Type of shutdown - public void Shutdown(DisconnectType type) - { - Shutdown(type, type.ToString()); - } - - /// - /// This method disconnects Client from all simulators leaving CurrentSim - /// last to disconnect. - /// - /// - /// This should only be called if the logout process times out on - /// RequestLogout - /// - /// Type of shutdown - /// Shutdown message - public void Shutdown(DisconnectType type, string message) - { - Logger.Log("NetworkManager shutdown initiated", Helpers.LogLevel.Info, Client); - - // 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 - foreach (var sim in Simulators.Where(t => t != null && t != CurrentSim)) - { - sim.Disconnect(sendCloseCircuit); - - // Fire the SimDisconnected event if a handler is registered - if (m_SimDisconnected != null) - { - OnSimDisconnected(new SimDisconnectedEventArgs(sim, type)); - } - } - - Simulators.Clear(); - } - - if (CurrentSim != null) - { - // Kill the connection to the current simulator - CurrentSim.Disconnect(sendCloseCircuit); - - // Fire the SimDisconnected event if a handler is registered - if (m_SimDisconnected != null) - { - OnSimDisconnected(new SimDisconnectedEventArgs(CurrentSim, type)); - } - } + } + /// + /// 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 + /// Size of the region in X meters + /// Size of the region in Y meters + /// A Simulator object on success, otherwise null + public Simulator Connect(IPEndPoint endPoint, ulong handle, bool setDefault, Uri seedcaps, uint sizeX, uint sizeY) + { + Simulator simulator = FindSimulator(endPoint); + + if (simulator == null) + { + // We're not tracking this sim, create a new Simulator object + simulator = new Simulator(Client, endPoint, handle, sizeX, sizeY); + + // Immediately add this simulator to the list of current sims. It will be removed if the + // connection fails + lock (Simulators) Simulators.Add(simulator); + } + + if (_packetInbox == null || _packetOutbox == null) + { + var options = new UnboundedChannelOptions() {SingleReader = true}; + + _packetInbox = Channel.CreateUnbounded(options); + _packetOutbox = Channel.CreateUnbounded(options); + + Task.Run(IncomingPacketHandler); + Task.Run(OutgoingPacketHandler); + } + + if (!simulator.Connected) + { + // Mark that we are connecting/connected to the grid + // + Connected = true; + + // raise the SimConnecting event and allow any event + // subscribers to cancel the connection + if (m_SimConnecting != null) + { + SimConnectingEventArgs args = new SimConnectingEventArgs(simulator); + OnSimConnecting(args); + + if (args.Cancel) + { + // Callback is requesting that we abort this connection + lock (Simulators) + { + Simulators.Remove(simulator); + } + return null; + } + } + + // 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(DisconnectTimer_Elapsed, null, + Client.Settings.SIMULATOR_TIMEOUT, Client.Settings.SIMULATOR_TIMEOUT); + } + + if (setDefault) + { + SetCurrentSim(simulator, seedcaps); + } + + // Raise the SimConnected event + if (m_SimConnected != null) + { + OnSimConnected(new SimConnectedEventArgs(simulator)); + } + + // 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) + { + // Move in to this simulator + simulator.handshakeComplete = false; + simulator.UseCircuitCode(true); + Client.Self.CompleteAgentMovement(simulator); + + // We're already connected to this server, but need to set it to the default + SetCurrentSim(simulator, seedcaps); + + // 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; + } + } + + /// + /// Begins the non-blocking logout. Makes sure that the LoggedOut event is + /// called even if the server does not send a logout reply, and Shutdown() + /// is properly called. + /// + public void BeginLogout() + { + // Wait for a logout response (by way of the LoggedOut event. If the + // response is received, shutdown will be fired in the callback. + // Otherwise we fire it manually with a NetworkTimeout type after LOGOUT_TIMEOUT + System.Timers.Timer timeout = new System.Timers.Timer(); + + void Callback(object sender, LoggedOutEventArgs e) + { + Shutdown(DisconnectType.ClientInitiated); + timeout.Stop(); + } + + LoggedOut += Callback; + + timeout.Interval = Client.Settings.LOGOUT_TIMEOUT; + timeout.Elapsed += delegate + { + timeout.Stop(); + Shutdown(DisconnectType.NetworkTimeout); + OnLoggedOut(new LoggedOutEventArgs(new List())); + }; + timeout.Start(); + + // Send the packet requesting a clean logout + RequestLogout(); + + LoggedOut -= Callback; + } + + /// + /// 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); + + void Callback(object sender, LoggedOutEventArgs e) + { + logoutEvent.Set(); + } + + LoggedOut += 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); + } + + LoggedOut -= Callback; + } + + /// + /// Initiate the logout process. 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(); + DisconnectTimer = null; + } + + // This will catch a Logout when the client is not logged in + if (CurrentSim == null || !Connected) + { + Logger.Log("Ignoring RequestLogout(), client is already logged out", Helpers.LogLevel.Warning, Client); + return; + } + + Logger.Log("Logging out", Helpers.LogLevel.Info, Client); + + // Send a logout request to the current sim + LogoutRequestPacket logout = new LogoutRequestPacket + { + AgentData = + { + AgentID = Client.Self.AgentID, + SessionID = Client.Self.SessionID + } + }; + SendPacket(logout); + } + + /// + /// Close a connection to the given simulator + /// + /// + /// + public void DisconnectSim(Simulator simulator, bool sendCloseCircuit) + { + if (simulator != null) + { + simulator.Disconnect(sendCloseCircuit); + + // Fire the SimDisconnected event if a handler is registered + if (m_SimDisconnected != null) + { + OnSimDisconnected(new SimDisconnectedEventArgs(simulator, DisconnectType.NetworkTimeout)); + } + + int simulatorsCount; + lock (Simulators) + { + Simulators.Remove(simulator); + simulatorsCount = Simulators.Count; + } + + if (simulatorsCount == 0) + { + Shutdown(DisconnectType.SimShutdown, $"You have disconnected from {simulator.Name}."); + } + } + else + { + NetworkInvaildWarning("simulator", "DisconnectSim"); + } + } + + + /// + /// This method disconnects Client from all simulators leaving CurrentSim + /// last to disconnect. + /// + /// + /// This should only be called if the logout process times out on + /// RequestLogout + /// + /// Type of shutdown + public void Shutdown(DisconnectType type) + { + Shutdown(type, type.ToString()); + } + + /// + /// This method disconnects Client from all simulators leaving CurrentSim + /// last to disconnect. + /// + /// + /// This should only be called if the logout process times out on + /// RequestLogout + /// + /// Type of shutdown + /// Shutdown message + public void Shutdown(DisconnectType type, string message) + { + Logger.Log("NetworkManager shutdown initiated", Helpers.LogLevel.Info, Client); + + // 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 + foreach (var sim in Simulators.Where(t => t != null && t != CurrentSim)) + { + sim.Disconnect(sendCloseCircuit); + + // Fire the SimDisconnected event if a handler is registered + if (m_SimDisconnected != null) + { + OnSimDisconnected(new SimDisconnectedEventArgs(sim, type)); + } + } + + Simulators.Clear(); + } + + if (CurrentSim != null) + { + // Kill the connection to the current simulator + CurrentSim.Disconnect(sendCloseCircuit); + + // Fire the SimDisconnected event if a handler is registered + if (m_SimDisconnected != null) + { + OnSimDisconnected(new SimDisconnectedEventArgs(CurrentSim, type)); + } + } if (_packetInbox != null) { _packetInbox.Writer.Complete(); - } + } if (_packetOutbox != null) { _packetOutbox.Writer.Complete(); - } - - _packetInbox = null; - _packetOutbox = null; - - Interlocked.Exchange(ref _packetInboxCount, 0); - Interlocked.Exchange(ref _packetOutboxCount, 0); - - Connected = false; - - // Fire the disconnected callback - if (m_Disconnected != null) - { - OnDisconnected(new DisconnectedEventArgs(type, message)); - } - } - - /// - /// 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) - { - foreach (var sim in Simulators.Where(t => t.IPEndPoint.Equals(endPoint))) - { - return sim; - } - } - - return null; - } - - internal void RaisePacketSentEvent(byte[] data, int bytesSent, Simulator simulator) - { - if (m_PacketSent != null) - { - OnPacketSent(new PacketSentEventArgs(data, bytesSent, simulator)); - } - } - - /// - /// Fire an event when an event queue connects for capabilities - /// - /// Simulator the event queue is attached to - internal void RaiseConnectedEvent(Simulator simulator) - { - if (m_EventQueueRunning != null) - { - OnEventQueueRunning(new EventQueueRunningEventArgs(simulator)); - } - } - - private async Task OutgoingPacketHandler() - { - if (_packetOutbox != null) - { - var reader = _packetOutbox.Reader; - - // FIXME: This is kind of ridiculous. Port the HTB code from Simian over ASAP! - var stopwatch = new System.Diagnostics.Stopwatch(); - - while (await reader.WaitToReadAsync() && Connected) - { - while (reader.TryRead(out var outgoingPacket)) - { - Interlocked.Decrement(ref _packetOutboxCount); - - var simulator = outgoingPacket.Simulator; - - stopwatch.Stop(); - if (stopwatch.ElapsedMilliseconds < 10) - { - //Logger.DebugLog(String.Format("Rate limiting, last packet was {0}ms ago", ms)); - Thread.Sleep(10 - (int)stopwatch.ElapsedMilliseconds); - } - - simulator.SendPacketFinal(outgoingPacket); - stopwatch.Start(); - } - } - } - else - { - NetworkInvaildWarning("_packetOutbox", "OutgoingPacketHandler"); - } - - } - - private async Task IncomingPacketHandler() - { - if (_packetInbox != null) - { - var reader = _packetInbox.Reader; - - while (await reader.WaitToReadAsync() && Connected) - { - while (reader.TryRead(out var incomingPacket)) - { - Interlocked.Decrement(ref _packetInboxCount); - - var packet = incomingPacket.Packet; - var simulator = incomingPacket.Simulator; - - if (packet == null) continue; - - // Skip blacklisted packets - if (UDPBlacklist.Contains(packet.Type.ToString())) - { - Logger.Log($"Discarding Blacklisted packet {packet.Type} from {simulator.IPEndPoint}", - Helpers.LogLevel.Warning); - return; - } - - // Fire the callback(s), if any - PacketEvents.RaiseEvent(packet.Type, packet, simulator); - } - } - } - else - { - NetworkInvaildWarning("_packetInbox", "IncomingPacketHandler"); - } - } - - private void SetCurrentSim(Simulator simulator, Uri seedcaps) - { - if (simulator == CurrentSim) return; - - Simulator oldSim = CurrentSim; - lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators - - simulator.SetSeedCaps(seedcaps); - - // If the current simulator changed fire the callback - if (m_SimChanged != null && simulator != oldSim) - { - OnSimChanged(new SimChangedEventArgs(oldSim)); - } - } - - #region Timers - - private void DisconnectTimer_Elapsed(object obj) - { - if (!Connected || CurrentSim == null) - { - if (DisconnectTimer != null) - { - DisconnectTimer.Dispose(); - DisconnectTimer = null; - } - Connected = false; - } - else if (CurrentSim.DisconnectCandidate) - { - // The currently occupied simulator hasn't sent us any traffic in a while, shutdown - Logger.Log($"Network timeout for the current simulator ({CurrentSim}), logging out", - Helpers.LogLevel.Warning, Client); - - if (DisconnectTimer != null) - { - DisconnectTimer.Dispose(); - DisconnectTimer = null; - } - - Connected = false; - - // Shutdown the network layer - Shutdown(DisconnectType.NetworkTimeout); - } - else - { - // Mark the current simulator as potentially disconnected each time this timer fires. - // If the timer is fired again before any packets are received, an actual disconnect - // sequence will be triggered - CurrentSim.DisconnectCandidate = true; - } - } - - #endregion Timers - - #region Packet Callbacks - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void LogoutReplyHandler(object sender, PacketReceivedEventArgs e) - { - LogoutReplyPacket logout = (LogoutReplyPacket)e.Packet; - - if ((logout.AgentData.SessionID == Client.Self.SessionID) && (logout.AgentData.AgentID == Client.Self.AgentID)) - { - Logger.DebugLog("Logout reply received", Client); - - // Deal with callbacks, if any - if (m_LoggedOut != null) - { - var itemIDs = logout.InventoryData.Select(inventoryData => inventoryData.ItemID).ToList(); - - OnLoggedOut(new LoggedOutEventArgs(itemIDs)); - } - - // If we are receiving a LogoutReply packet assume this is a client initiated shutdown - Shutdown(DisconnectType.ClientInitiated); - } - else - { - Logger.Log("Invalid Session or Agent ID received in Logout Reply... ignoring", Helpers.LogLevel.Warning, Client); - } - } - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void StartPingCheckHandler(object sender, PacketReceivedEventArgs e) - { - StartPingCheckPacket incomingPing = (StartPingCheckPacket)e.Packet; - CompletePingCheckPacket ping = new CompletePingCheckPacket - { - PingID = {PingID = incomingPing.PingID.PingID}, - 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, e.Simulator); - } - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void CompletePingCheckHandler(object sender, PacketReceivedEventArgs e) - { - CompletePingCheckPacket pong = (CompletePingCheckPacket)e.Packet; - //String retval = "Pong2: " + (Environment.TickCount - e.Simulator.Stats.LastPingSent); - //if ((pong.PingID.PingID - e.Simulator.Stats.LastPingID + 1) != 0) - // retval += " (gap of " + (pong.PingID.PingID - e.Simulator.Stats.LastPingID + 1) + ")"; - - e.Simulator.Stats.LastLag = Environment.TickCount - e.Simulator.Stats.LastPingSent; - e.Simulator.Stats.ReceivedPongs++; - // Client.Log(retval, Helpers.LogLevel.Info); - } - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void SimStatsHandler(object sender, PacketReceivedEventArgs e) - { - if (!Client.Settings.ENABLE_SIMSTATS) - { - return; - } - SimStatsPacket stats = (SimStatsPacket)e.Packet; - foreach (SimStatsPacket.StatBlock s in stats.Stat) - { - switch (s.StatID) - { - case 0: - e.Simulator.Stats.Dilation = s.StatValue; - break; - case 1: - e.Simulator.Stats.FPS = Convert.ToInt32(s.StatValue); - break; - case 2: - e.Simulator.Stats.PhysicsFPS = s.StatValue; - break; - case 3: - e.Simulator.Stats.AgentUpdates = s.StatValue; - break; - case 4: - e.Simulator.Stats.FrameTime = s.StatValue; - break; - case 5: - e.Simulator.Stats.NetTime = s.StatValue; - break; - case 6: - e.Simulator.Stats.OtherTime = s.StatValue; - break; - case 7: - e.Simulator.Stats.PhysicsTime = s.StatValue; - break; - case 8: - e.Simulator.Stats.AgentTime = s.StatValue; - break; - case 9: - e.Simulator.Stats.ImageTime = s.StatValue; - break; - case 10: - e.Simulator.Stats.ScriptTime = s.StatValue; - break; - case 11: - e.Simulator.Stats.Objects = Convert.ToInt32(s.StatValue); - break; - case 12: - e.Simulator.Stats.ScriptedObjects = Convert.ToInt32(s.StatValue); - break; - case 13: - e.Simulator.Stats.Agents = Convert.ToInt32(s.StatValue); - break; - case 14: - e.Simulator.Stats.ChildAgents = Convert.ToInt32(s.StatValue); - break; - case 15: - e.Simulator.Stats.ActiveScripts = Convert.ToInt32(s.StatValue); - break; - case 16: - e.Simulator.Stats.LSLIPS = Convert.ToInt32(s.StatValue); - break; - case 17: - e.Simulator.Stats.INPPS = Convert.ToInt32(s.StatValue); - break; - case 18: - e.Simulator.Stats.OUTPPS = Convert.ToInt32(s.StatValue); - break; - case 19: - e.Simulator.Stats.PendingDownloads = Convert.ToInt32(s.StatValue); - break; - case 20: - e.Simulator.Stats.PendingUploads = Convert.ToInt32(s.StatValue); - break; - case 21: - e.Simulator.Stats.VirtualSize = Convert.ToInt32(s.StatValue); - break; - case 22: - e.Simulator.Stats.ResidentSize = Convert.ToInt32(s.StatValue); - break; - case 23: - e.Simulator.Stats.PendingLocalUploads = Convert.ToInt32(s.StatValue); - break; - case 24: - e.Simulator.Stats.UnackedBytes = Convert.ToInt32(s.StatValue); - break; - } - } - } - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void RegionHandshakeHandler(object sender, PacketReceivedEventArgs e) - { - RegionHandshakePacket handshake = (RegionHandshakePacket)e.Packet; - Simulator simulator = e.Simulator; - e.Simulator.ID = handshake.RegionInfo.CacheID; - - simulator.IsEstateManager = handshake.RegionInfo.IsEstateManager; - simulator.Name = Utils.BytesToString(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 = (RegionFlags)handshake.RegionInfo.RegionFlags; - simulator.BillableFactor = handshake.RegionInfo.BillableFactor; - simulator.Access = (SimAccess)handshake.RegionInfo.SimAccess; - - simulator.RegionID = handshake.RegionInfo2.RegionID; - simulator.ColoLocation = Utils.BytesToString(handshake.RegionInfo3.ColoName); - simulator.CPUClass = handshake.RegionInfo3.CPUClassID; - simulator.CPURatio = handshake.RegionInfo3.CPURatio; - simulator.ProductName = Utils.BytesToString(handshake.RegionInfo3.ProductName); - simulator.ProductSku = Utils.BytesToString(handshake.RegionInfo3.ProductSKU); - - if (handshake.RegionInfo4 != null && handshake.RegionInfo4.Length > 0) - { - simulator.Protocols = (RegionProtocols)handshake.RegionInfo4[0].RegionProtocols; - // Yes, overwrite region flags if we have extended version of them - simulator.Flags = (RegionFlags)handshake.RegionInfo4[0].RegionFlagsExtended; - } - - // Send a RegionHandshakeReply - RegionHandshakeReplyPacket reply = new RegionHandshakeReplyPacket - { - AgentData = - { - AgentID = Client.Self.AgentID, - SessionID = Client.Self.SessionID - }, - RegionInfo = {Flags = (uint) RegionProtocols.SelfAppearanceSupport} - }; - SendPacket(reply, simulator); - - // We're officially connected to this sim - simulator.connected = true; - simulator.handshakeComplete = true; - simulator.ConnectedEvent.Set(); - } - - protected void EnableSimulatorHandler(string capsKey, IMessage message, Simulator simulator) - { - if (!Client.Settings.MULTIPLE_SIMS) return; - - EnableSimulatorMessage msg = (EnableSimulatorMessage)message; - - foreach (EnableSimulatorMessage.SimulatorInfoBlock t in msg.Simulators) - { - IPAddress ip = t.IP; - ushort port = (ushort)t.Port; - ulong handle = t.RegionHandle; - - IPEndPoint endPoint = new IPEndPoint(ip, port); - - if (FindSimulator(endPoint) != null) return; - - if (Connect(ip, port, handle, false, null, t.RegionSizeX, t.RegionSizeY) == null) - { - Logger.Log($"Unable to connect to new sim {ip}:{port}", - Helpers.LogLevel.Error, Client); - } - } - } - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void DisableSimulatorHandler(object sender, PacketReceivedEventArgs e) - { - DisconnectSim(e.Simulator, false); - } - - /// Process an incoming packet and raise the appropriate events - /// The sender - /// The EventArgs object containing the packet data - protected void KickUserHandler(object sender, PacketReceivedEventArgs e) - { - string message = Utils.BytesToString(((KickUserPacket)e.Packet).UserInfo.Reason); - - // Shutdown the network layer - Shutdown(DisconnectType.ServerInitiated, message); - } - - #endregion Packet Callbacks - } - #region EventArgs - - public class PacketReceivedEventArgs : EventArgs - { - public Packet Packet { get; } - - public Simulator Simulator { get; } - - public PacketReceivedEventArgs(Packet packet, Simulator simulator) - { - Packet = packet; - Simulator = simulator; - } - } - - public class LoggedOutEventArgs : EventArgs - { - private readonly List m_InventoryItems; - public List InventoryItems; - - public LoggedOutEventArgs(List inventoryItems) - { - this.m_InventoryItems = inventoryItems; - } - } - - public class PacketSentEventArgs : EventArgs - { - public byte[] Data { get; } - public int SentBytes { get; } - public Simulator Simulator { get; } - - public PacketSentEventArgs(byte[] data, int bytesSent, Simulator simulator) - { - Data = data; - SentBytes = bytesSent; - Simulator = simulator; - } - } - - public class SimConnectingEventArgs : EventArgs - { - public Simulator Simulator { get; } - - public bool Cancel { get; set; } - - public SimConnectingEventArgs(Simulator simulator) - { - Simulator = simulator; - Cancel = false; - } - } - - public class SimConnectedEventArgs : EventArgs - { - public Simulator Simulator { get; } - - public SimConnectedEventArgs(Simulator simulator) - { - Simulator = simulator; - } - } - - public class SimDisconnectedEventArgs : EventArgs - { - public Simulator Simulator { get; } - - public NetworkManager.DisconnectType Reason { get; } - - public SimDisconnectedEventArgs(Simulator simulator, NetworkManager.DisconnectType reason) - { - Simulator = simulator; - Reason = reason; - } - } - - public class DisconnectedEventArgs : EventArgs - { - public NetworkManager.DisconnectType Reason { get; } - - public string Message { get; } - - public DisconnectedEventArgs(NetworkManager.DisconnectType reason, string message) - { - Reason = reason; - Message = message; - } - } - - public class SimChangedEventArgs : EventArgs - { - public Simulator PreviousSimulator { get; } - - public SimChangedEventArgs(Simulator previousSimulator) - { - PreviousSimulator = previousSimulator; - } - } - - public class EventQueueRunningEventArgs : EventArgs - { - public Simulator Simulator { get; } - - public EventQueueRunningEventArgs(Simulator simulator) - { - Simulator = simulator; - } - } - #endregion -} + } + + _packetInbox = null; + _packetOutbox = null; + + Interlocked.Exchange(ref _packetInboxCount, 0); + Interlocked.Exchange(ref _packetOutboxCount, 0); + + Connected = false; + + // Fire the disconnected callback + if (m_Disconnected != null) + { + OnDisconnected(new DisconnectedEventArgs(type, message)); + } + } + + /// + /// 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) + { + foreach (var sim in Simulators.Where(t => t.IPEndPoint.Equals(endPoint))) + { + return sim; + } + } + + return null; + } + + internal void RaisePacketSentEvent(byte[] data, int bytesSent, Simulator simulator) + { + if (m_PacketSent != null) + { + OnPacketSent(new PacketSentEventArgs(data, bytesSent, simulator)); + } + } + + /// + /// Fire an event when an event queue connects for capabilities + /// + /// Simulator the event queue is attached to + internal void RaiseConnectedEvent(Simulator simulator) + { + if (m_EventQueueRunning != null) + { + OnEventQueueRunning(new EventQueueRunningEventArgs(simulator)); + } + } + + private async Task OutgoingPacketHandler() + { + if (_packetOutbox != null) + { + var reader = _packetOutbox.Reader; + + // FIXME: This is kind of ridiculous. Port the HTB code from Simian over ASAP! + var stopwatch = new System.Diagnostics.Stopwatch(); + + while (await reader.WaitToReadAsync() && Connected) + { + while (reader.TryRead(out var outgoingPacket)) + { + Interlocked.Decrement(ref _packetOutboxCount); + + var simulator = outgoingPacket.Simulator; + + stopwatch.Stop(); + if (stopwatch.ElapsedMilliseconds < 10) + { + //Logger.DebugLog(String.Format("Rate limiting, last packet was {0}ms ago", ms)); + Thread.Sleep(10 - (int)stopwatch.ElapsedMilliseconds); + } + + simulator.SendPacketFinal(outgoingPacket); + stopwatch.Start(); + } + } + } + else + { + NetworkInvaildWarning("_packetOutbox", "OutgoingPacketHandler"); + } + + } + + private async Task IncomingPacketHandler() + { + if (_packetInbox != null) + { + var reader = _packetInbox.Reader; + + while (await reader.WaitToReadAsync() && Connected) + { + while (reader.TryRead(out var incomingPacket)) + { + Interlocked.Decrement(ref _packetInboxCount); + + var packet = incomingPacket.Packet; + var simulator = incomingPacket.Simulator; + + if (packet == null) continue; + + // Skip blacklisted packets + if (UDPBlacklist.Contains(packet.Type.ToString())) + { + Logger.Log($"Discarding Blacklisted packet {packet.Type} from {simulator.IPEndPoint}", + Helpers.LogLevel.Warning); + return; + } + // Fire the callback(s), if any + PacketEvents.RaiseEvent(packet.Type, packet, simulator); + } + } + } + else + { + NetworkInvaildWarning("_packetInbox", "IncomingPacketHandler"); + } + } + + private void SetCurrentSim(Simulator simulator, Uri seedcaps) + { + if (simulator == CurrentSim) return; + + Simulator oldSim = CurrentSim; + lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators + + simulator.SetSeedCaps(seedcaps); + + // If the current simulator changed fire the callback + if (m_SimChanged != null && simulator != oldSim) + { + OnSimChanged(new SimChangedEventArgs(oldSim)); + } + } + + #region Timers + + private void DisconnectTimer_Elapsed(object obj) + { + if (!Connected || CurrentSim == null) + { + if (DisconnectTimer != null) + { + DisconnectTimer.Dispose(); + DisconnectTimer = null; + } + Connected = false; + } + else if (CurrentSim.DisconnectCandidate) + { + // The currently occupied simulator hasn't sent us any traffic in a while, shutdown + Logger.Log($"Network timeout for the current simulator ({CurrentSim}), logging out", + Helpers.LogLevel.Warning, Client); + + if (DisconnectTimer != null) + { + DisconnectTimer.Dispose(); + DisconnectTimer = null; + } + + Connected = false; + + // Shutdown the network layer + Shutdown(DisconnectType.NetworkTimeout); + } + else + { + // Mark the current simulator as potentially disconnected each time this timer fires. + // If the timer is fired again before any packets are received, an actual disconnect + // sequence will be triggered + CurrentSim.DisconnectCandidate = true; + } + } + + #endregion Timers + + #region Packet Callbacks + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void LogoutReplyHandler(object sender, PacketReceivedEventArgs e) + { + LogoutReplyPacket logout = (LogoutReplyPacket)e.Packet; + + if ((logout.AgentData.SessionID == Client.Self.SessionID) && (logout.AgentData.AgentID == Client.Self.AgentID)) + { + Logger.DebugLog("Logout reply received", Client); + + // Deal with callbacks, if any + if (m_LoggedOut != null) + { + var itemIDs = logout.InventoryData.Select(inventoryData => inventoryData.ItemID).ToList(); + + OnLoggedOut(new LoggedOutEventArgs(itemIDs)); + } + + // If we are receiving a LogoutReply packet assume this is a client initiated shutdown + Shutdown(DisconnectType.ClientInitiated); + } + else + { + Logger.Log("Invalid Session or Agent ID received in Logout Reply... ignoring", Helpers.LogLevel.Warning, Client); + } + } + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void StartPingCheckHandler(object sender, PacketReceivedEventArgs e) + { + StartPingCheckPacket incomingPing = (StartPingCheckPacket)e.Packet; + CompletePingCheckPacket ping = new CompletePingCheckPacket + { + PingID = {PingID = incomingPing.PingID.PingID}, + 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, e.Simulator); + } + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void CompletePingCheckHandler(object sender, PacketReceivedEventArgs e) + { + CompletePingCheckPacket pong = (CompletePingCheckPacket)e.Packet; + //String retval = "Pong2: " + (Environment.TickCount - e.Simulator.Stats.LastPingSent); + //if ((pong.PingID.PingID - e.Simulator.Stats.LastPingID + 1) != 0) + // retval += " (gap of " + (pong.PingID.PingID - e.Simulator.Stats.LastPingID + 1) + ")"; + + e.Simulator.Stats.LastLag = Environment.TickCount - e.Simulator.Stats.LastPingSent; + e.Simulator.Stats.ReceivedPongs++; + // Client.Log(retval, Helpers.LogLevel.Info); + } + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void SimStatsHandler(object sender, PacketReceivedEventArgs e) + { + if (!Client.Settings.ENABLE_SIMSTATS) + { + return; + } + SimStatsPacket stats = (SimStatsPacket)e.Packet; + foreach (SimStatsPacket.StatBlock s in stats.Stat) + { + switch (s.StatID) + { + case 0: + e.Simulator.Stats.Dilation = s.StatValue; + break; + case 1: + e.Simulator.Stats.FPS = Convert.ToInt32(s.StatValue); + break; + case 2: + e.Simulator.Stats.PhysicsFPS = s.StatValue; + break; + case 3: + e.Simulator.Stats.AgentUpdates = s.StatValue; + break; + case 4: + e.Simulator.Stats.FrameTime = s.StatValue; + break; + case 5: + e.Simulator.Stats.NetTime = s.StatValue; + break; + case 6: + e.Simulator.Stats.OtherTime = s.StatValue; + break; + case 7: + e.Simulator.Stats.PhysicsTime = s.StatValue; + break; + case 8: + e.Simulator.Stats.AgentTime = s.StatValue; + break; + case 9: + e.Simulator.Stats.ImageTime = s.StatValue; + break; + case 10: + e.Simulator.Stats.ScriptTime = s.StatValue; + break; + case 11: + e.Simulator.Stats.Objects = Convert.ToInt32(s.StatValue); + break; + case 12: + e.Simulator.Stats.ScriptedObjects = Convert.ToInt32(s.StatValue); + break; + case 13: + e.Simulator.Stats.Agents = Convert.ToInt32(s.StatValue); + break; + case 14: + e.Simulator.Stats.ChildAgents = Convert.ToInt32(s.StatValue); + break; + case 15: + e.Simulator.Stats.ActiveScripts = Convert.ToInt32(s.StatValue); + break; + case 16: + e.Simulator.Stats.LSLIPS = Convert.ToInt32(s.StatValue); + break; + case 17: + e.Simulator.Stats.INPPS = Convert.ToInt32(s.StatValue); + break; + case 18: + e.Simulator.Stats.OUTPPS = Convert.ToInt32(s.StatValue); + break; + case 19: + e.Simulator.Stats.PendingDownloads = Convert.ToInt32(s.StatValue); + break; + case 20: + e.Simulator.Stats.PendingUploads = Convert.ToInt32(s.StatValue); + break; + case 21: + e.Simulator.Stats.VirtualSize = Convert.ToInt32(s.StatValue); + break; + case 22: + e.Simulator.Stats.ResidentSize = Convert.ToInt32(s.StatValue); + break; + case 23: + e.Simulator.Stats.PendingLocalUploads = Convert.ToInt32(s.StatValue); + break; + case 24: + e.Simulator.Stats.UnackedBytes = Convert.ToInt32(s.StatValue); + break; + } + } + } + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void RegionHandshakeHandler(object sender, PacketReceivedEventArgs e) + { + RegionHandshakePacket handshake = (RegionHandshakePacket)e.Packet; + Simulator simulator = e.Simulator; + e.Simulator.ID = handshake.RegionInfo.CacheID; + + simulator.IsEstateManager = handshake.RegionInfo.IsEstateManager; + simulator.Name = Utils.BytesToString(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 = (RegionFlags)handshake.RegionInfo.RegionFlags; + simulator.BillableFactor = handshake.RegionInfo.BillableFactor; + simulator.Access = (SimAccess)handshake.RegionInfo.SimAccess; + + simulator.RegionID = handshake.RegionInfo2.RegionID; + simulator.ColoLocation = Utils.BytesToString(handshake.RegionInfo3.ColoName); + simulator.CPUClass = handshake.RegionInfo3.CPUClassID; + simulator.CPURatio = handshake.RegionInfo3.CPURatio; + simulator.ProductName = Utils.BytesToString(handshake.RegionInfo3.ProductName); + simulator.ProductSku = Utils.BytesToString(handshake.RegionInfo3.ProductSKU); + + if (handshake.RegionInfo4 != null && handshake.RegionInfo4.Length > 0) + { + simulator.Protocols = (RegionProtocols)handshake.RegionInfo4[0].RegionProtocols; + // Yes, overwrite region flags if we have extended version of them + simulator.Flags = (RegionFlags)handshake.RegionInfo4[0].RegionFlagsExtended; + } + + // Send a RegionHandshakeReply + RegionHandshakeReplyPacket reply = new RegionHandshakeReplyPacket + { + AgentData = + { + AgentID = Client.Self.AgentID, + SessionID = Client.Self.SessionID + }, + RegionInfo = {Flags = (uint) RegionProtocols.SelfAppearanceSupport} + }; + SendPacket(reply, simulator); + + // We're officially connected to this sim + simulator.connected = true; + simulator.handshakeComplete = true; + simulator.ConnectedEvent.Set(); + } + + protected void EnableSimulatorHandler(string capsKey, IMessage message, Simulator simulator) + { + if (!Client.Settings.MULTIPLE_SIMS) return; + + EnableSimulatorMessage msg = (EnableSimulatorMessage)message; + + foreach (EnableSimulatorMessage.SimulatorInfoBlock t in msg.Simulators) + { + IPAddress ip = t.IP; + ushort port = (ushort)t.Port; + ulong handle = t.RegionHandle; + + IPEndPoint endPoint = new IPEndPoint(ip, port); + + if (FindSimulator(endPoint) != null) return; + + if (Connect(ip, port, handle, false, null, t.RegionSizeX, t.RegionSizeY) == null) + { + Logger.Log($"Unable to connect to new sim {ip}:{port}", + Helpers.LogLevel.Error, Client); + } + } + } + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void DisableSimulatorHandler(object sender, PacketReceivedEventArgs e) + { + DisconnectSim(e.Simulator, false); + } + + /// Process an incoming packet and raise the appropriate events + /// The sender + /// The EventArgs object containing the packet data + protected void KickUserHandler(object sender, PacketReceivedEventArgs e) + { + string message = Utils.BytesToString(((KickUserPacket)e.Packet).UserInfo.Reason); + + // Shutdown the network layer + Shutdown(DisconnectType.ServerInitiated, message); + } + + #endregion Packet Callbacks + } + #region EventArgs + + public class PacketReceivedEventArgs : EventArgs + { + public Packet Packet { get; } + + public Simulator Simulator { get; } + + public PacketReceivedEventArgs(Packet packet, Simulator simulator) + { + Packet = packet; + Simulator = simulator; + } + } + + public class LoggedOutEventArgs : EventArgs + { + private readonly List m_InventoryItems; + public List InventoryItems; + + public LoggedOutEventArgs(List inventoryItems) + { + this.m_InventoryItems = inventoryItems; + } + } + + public class PacketSentEventArgs : EventArgs + { + public byte[] Data { get; } + public int SentBytes { get; } + public Simulator Simulator { get; } + + public PacketSentEventArgs(byte[] data, int bytesSent, Simulator simulator) + { + Data = data; + SentBytes = bytesSent; + Simulator = simulator; + } + } + + public class SimConnectingEventArgs : EventArgs + { + public Simulator Simulator { get; } + + public bool Cancel { get; set; } + + public SimConnectingEventArgs(Simulator simulator) + { + Simulator = simulator; + Cancel = false; + } + } + + public class SimConnectedEventArgs : EventArgs + { + public Simulator Simulator { get; } + + public SimConnectedEventArgs(Simulator simulator) + { + Simulator = simulator; + } + } + + public class SimDisconnectedEventArgs : EventArgs + { + public Simulator Simulator { get; } + + public NetworkManager.DisconnectType Reason { get; } + + public SimDisconnectedEventArgs(Simulator simulator, NetworkManager.DisconnectType reason) + { + Simulator = simulator; + Reason = reason; + } + } + + public class DisconnectedEventArgs : EventArgs + { + public NetworkManager.DisconnectType Reason { get; } + + public string Message { get; } + + public DisconnectedEventArgs(NetworkManager.DisconnectType reason, string message) + { + Reason = reason; + Message = message; + } + } + + public class SimChangedEventArgs : EventArgs + { + public Simulator PreviousSimulator { get; } + + public SimChangedEventArgs(Simulator previousSimulator) + { + PreviousSimulator = previousSimulator; + } + } + + public class EventQueueRunningEventArgs : EventArgs + { + public Simulator Simulator { get; } + + public EventQueueRunningEventArgs(Simulator simulator) + { + Simulator = simulator; + } + } + #endregion +} diff --git a/LibreMetaverse/_Packets_.cs b/LibreMetaverse/_Packets_.cs index c654743..63c7390 100644 --- a/LibreMetaverse/_Packets_.cs +++ b/LibreMetaverse/_Packets_.cs @@ -76676,6 +76676,7 @@ namespace OpenMetaverse.Packets public byte[] MediaURL; public byte[] PSBlock; public byte[] ExtraParams; + public string A_TextDecoded; public UUID Sound; public UUID OwnerID; public float Gain; @@ -76760,6 +76761,11 @@ namespace OpenMetaverse.Packets length = bytes[i++]; Text = new byte[length]; Buffer.BlockCopy(bytes, i, Text, 0, length); i += length; + A_TextDecoded = ""; + if (Text.Length > 0) + { + A_TextDecoded = Utils.BytesToString(Text); + } TextColor = new byte[4]; Buffer.BlockCopy(bytes, i, TextColor, 0, 4); i += 4; length = bytes[i++]; diff --git a/SecondBotEvents/Services/DiscordService.cs b/SecondBotEvents/Services/DiscordService.cs index 26b9681..fffb755 100644 --- a/SecondBotEvents/Services/DiscordService.cs +++ b/SecondBotEvents/Services/DiscordService.cs @@ -836,32 +836,39 @@ namespace SecondBotEvents.Services { return; } - ITextChannel statusChannel = GetChannel("status", StatusPrefill(), "bot"); - if ((e.changed == false) && (LastStatusMessageId != 0)) + try { + ITextChannel statusChannel = GetChannel("status", StatusPrefill(), "bot"); + if ((e.changed == false) && (LastStatusMessageId != 0)) + { + if (dif < 15) + { + return; + } + LastUpdatedSystemStatus = SecondbotHelpers.UnixTimeNow(); + if (messageHasEndDot == false) + { + await statusChannel. + ModifyMessageAsync(LastStatusMessageId, m => m.Content = LastMessageContent + "."); + messageHasEndDot = true; + return; + } + await statusChannel. + ModifyMessageAsync(LastStatusMessageId, m => m.Content = LastMessageContent); + messageHasEndDot = false; + return; + } if (dif < 15) { return; } - LastUpdatedSystemStatus = SecondbotHelpers.UnixTimeNow(); - if (messageHasEndDot == false) - { - await statusChannel. - ModifyMessageAsync(LastStatusMessageId, m => m.Content = LastMessageContent + "."); - messageHasEndDot = true; - return; - } - await statusChannel. - ModifyMessageAsync(LastStatusMessageId, m => m.Content = LastMessageContent); - messageHasEndDot = false; - return; + LastStatusMessageId = SendMessageToChannel(statusChannel, e.message); + LastMessageContent = e.message; } - if (dif < 15) + catch { return; } - LastStatusMessageId = SendMessageToChannel(statusChannel, e.message); - LastMessageContent = e.message; } protected async void CleanChannel(ITextChannel channel)